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:
Nikola Hristov 2021-07-17 12:40:28 +03:00 committed by GitHub
commit 1f23da7e7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 9 deletions

View file

@ -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']} />

View file

@ -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 {