mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge pull request #232 from Stremio/m3u-playlist-fallback
Allow to download a m3u playlist in case of a playback error
This commit is contained in:
commit
1f23da7e7c
2 changed files with 74 additions and 9 deletions
|
|
@ -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 ?
|
||||
<BufferingLoader className={styles['layer']} />
|
||||
:
|
||||
error !== null ?
|
||||
<div className={classnames(styles['layer'], styles['error-layer'])}>
|
||||
<div className={styles['error-label']}>{error.message}</div>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
null
|
||||
}
|
||||
<div
|
||||
className={styles['layer']}
|
||||
onClick={onVideoClick}
|
||||
onDoubleClick={onVideoDoubleClick}
|
||||
/>
|
||||
{
|
||||
error !== null ?
|
||||
<div className={classnames(styles['layer'], styles['error-layer'])}>
|
||||
<div className={styles['error-label']}>{error.message}</div>
|
||||
{
|
||||
playlist ?
|
||||
<div className={styles['error-details']}>
|
||||
<Button className={styles['error-details-button']} title={'Download MU3 Playlist'} href={playlist.file} download={playlist.name}>
|
||||
<Icon className={styles['icon']} icon={'ic_downloads'} />
|
||||
<div className={styles['label']}>Download Playlist</div>
|
||||
</Button>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
subtitlesMenuOpen || infoMenuOpen ?
|
||||
<div className={styles['layer']} />
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue