diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index d3102aa15..42fc7a2aa 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -6,7 +6,8 @@ const classnames = require('classnames'); const debounce = require('lodash.debounce'); const { useRouteFocused } = require('stremio-router'); const { useServices } = require('stremio/services'); -const { HorizontalNavBar, useDeepEqualEffect, useFullscreen, useBinaryState, useToast, useStreamingServer } = require('stremio/common'); +const { HorizontalNavBar, Button, useDeepEqualEffect, useFullscreen, useBinaryState, useToast, useStreamingServer } = require('stremio/common'); +const Icon = require('@stremio/stremio-icons/dom'); const BufferingLoader = require('./BufferingLoader'); const ControlBar = require('./ControlBar'); const InfoMenu = require('./InfoMenu'); @@ -63,6 +64,18 @@ const Player = ({ urlParams, queryParams }) => { videoRef.current.dispatch(args); } }, []); + const playlist = React.useMemo(() => { + if (player.selected === null || typeof player.selected.stream.url !== 'string') { + return null; + } + + const m3u = `#EXTM3U\n\n#EXTINF:0,${encodeURIComponent(player.title)}\n${encodeURI(player.selected.stream.url)}`; + const base64File = `data:application/octet-stream;charset=utf-8;base64,${window.btoa(m3u)}`; + return { + name: `${player.title}.m3u`, + file: base64File + }; + }, [player]); const onImplementationChanged = React.useCallback((manifest) => { manifest.props.forEach((propName) => { dispatch({ type: 'observeProp', propName }); @@ -415,18 +428,32 @@ const Player = ({ urlParams, queryParams }) => { videoState.buffering ? : - error !== null ? -
-
{error.message}
-
- : - null + null }
+ { + error !== null ? +
+
{error.message}
+ { + playlist ? +
+ +
+ : + null + } +
+ : + null + } { subtitlesMenuOpen || infoMenuOpen ?
diff --git a/src/routes/Player/styles.less b/src/routes/Player/styles.less index 8c84da749..10c1921c5 100644 --- a/src/routes/Player/styles.less +++ b/src/routes/Player/styles.less @@ -40,19 +40,57 @@ html:not(.active-slider-within) { &.error-layer { display: flex; - flex-direction: row; + flex-direction: column; align-items: center; justify-content: center; background-color: @color-background-dark5; .error-label { - flex: 0 1 auto; + flex: auto; padding: 0 8rem; max-height: 4.8em; font-size: 2rem; color: @color-surface-light5-90; text-align: center; } + + .error-details { + display: flex; + flex: auto; + font-size: 1.5rem; + margin-top: 1.5rem; + color: @color-surface-light5-90; + + .error-details-button { + display: flex; + flex: none; + flex-direction: row; + align-items: center; + height: 3.5rem; + padding: 0.5rem 1rem; + font-weight: 500; + background-color: @color-accent3; + + &:hover, &:focus { + background-color: @color-accent3-light1; + } + + .icon { + flex: none; + width: 1.5rem; + height: 1.5rem; + margin-right: 1rem; + fill: @color-surface-light5-90; + } + + .label { + flex: auto; + max-height: 2.4em; + font-size: 1.1rem; + color: @color-surface-light5-90; + } + } + } } &.nav-bar-layer {