Merge branch 'development' of github.com:Stremio/stremio-web into server-notification

This commit is contained in:
svetlagasheva 2021-08-04 10:26:38 +03:00
commit 59a2b7e3ca
11 changed files with 153 additions and 18 deletions

6
package-lock.json generated
View file

@ -8117,9 +8117,9 @@
"integrity": "sha512-yT3No1gIWKLV2BhQIeSgG94EzXxmEqXJLulO+pFpziqWNUbmmEKeE+nRvW5wtoIK4SLy+v0bLd0b6HBH3KFfWw=="
},
"@stremio/stremio-core-web": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.22.0.tgz",
"integrity": "sha512-PqvIXKwYmRYwzh0BFCEb9orZ3533OVLmLNbV1mCf9dmHG+BX+mREv9IB5+/yJZal2GMLnEBPfgeNxvIDYU6sww==",
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.23.0.tgz",
"integrity": "sha512-eZyBEWuB90y6lTG47Y4lKUnlM4bpVhmKh96DUHMPtMKOsyHRutOkuKXYjAwP0WjCbe57/vbalI04Fout7SZwWg==",
"requires": {
"@babel/runtime": "7.10.0"
},

View file

@ -16,7 +16,7 @@
"@babel/runtime": "7.12.5",
"@sentry/browser": "5.11.1",
"@stremio/stremio-colors": "4.0.1",
"@stremio/stremio-core-web": "0.22.0",
"@stremio/stremio-core-web": "0.23.0",
"@stremio/stremio-icons": "3.0.5",
"@stremio/stremio-video": "0.0.7",
"a-color-picker": "1.2.1",

View file

@ -3,6 +3,7 @@
const CHROMECAST_RECEIVER_APP_ID = '1634F54B';
const SUBTITLES_SIZES = [75, 100, 125, 150, 175, 200, 250];
const SUBTITLES_FONTS = ['Roboto', 'Arial', 'Halvetica', 'Times New Roman', 'Verdana', 'Courier', 'Lucida Console', 'sans-serif', 'serif', 'monospace'];
const SEEK_TIME_DURATIONS = [5000, 10000, 15000, 20000, 25000, 30000];
const CATALOG_PREVIEW_SIZE = 10;
const CATALOG_PAGE_SIZE = 100;
const NONE_EXTRA_VALUE = 'None';
@ -28,6 +29,7 @@ module.exports = {
CHROMECAST_RECEIVER_APP_ID,
SUBTITLES_SIZES,
SUBTITLES_FONTS,
SEEK_TIME_DURATIONS,
CATALOG_PREVIEW_SIZE,
CATALOG_PAGE_SIZE,
NONE_EXTRA_VALUE,

View file

@ -23,7 +23,7 @@ const ALLOWED_LINK_REDIRECTS = [
routesRegexp.metadetails.regexp
];
const MetaPreview = ({ className, compact, name, logo, background, runtime, releaseInfo, released, description, links, trailerStreams, inLibrary, toggleInLibrary }) => {
const MetaPreview = ({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary }) => {
const [shareModalOpen, openShareModal, closeShareModal] = useBinaryState(false);
const linksGroups = React.useMemo(() => {
return Array.isArray(links) ?
@ -70,6 +70,21 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
:
new Map();
}, [links]);
const showHref = React.useMemo(() => {
return deepLinks ?
typeof deepLinks.player === 'string' ?
deepLinks.player
:
typeof deepLinks.metaDetailsStreams === 'string' ?
deepLinks.metaDetailsStreams
:
typeof deepLinks.metaDetailsVideos === 'string' ?
deepLinks.metaDetailsVideos
:
null
:
null;
}, [deepLinks]);
const trailerHref = React.useMemo(() => {
if (!Array.isArray(trailerStreams) || trailerStreams.length === 0) {
return null;
@ -195,7 +210,19 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
null
}
{
linksGroups.has(CONSTANTS.SHARE_LINK_CATEGORY) ?
typeof showHref === 'string' && compact ?
<ActionButton
className={styles['action-button']}
icon={'ic_play'}
label={'Show'}
tabIndex={compact ? -1 : 0}
href={showHref}
/>
:
null
}
{
linksGroups.has(CONSTANTS.SHARE_LINK_CATEGORY) && !compact ?
<React.Fragment>
<ActionButton
className={styles['action-button']}
@ -236,6 +263,11 @@ MetaPreview.propTypes = {
releaseInfo: PropTypes.string,
released: PropTypes.instanceOf(Date),
description: PropTypes.string,
deepLinks: PropTypes.shape({
metaDetailsVideos: PropTypes.string,
metaDetailsStreams: PropTypes.string,
player: PropTypes.string
}),
links: PropTypes.arrayOf(PropTypes.shape({
category: PropTypes.string,
name: PropTypes.string,

View file

@ -182,6 +182,7 @@ const Addons = ({ urlParams, queryParams }) => {
className={styles['addon-url-input']}
type={'text'}
placeholder={'Paste addon URL'}
autoFocus={true}
onSubmit={addAddonOnSubmit}
/>
</ModalDialog>

View file

@ -165,6 +165,7 @@ const Discover = ({ urlParams, queryParams }) => {
releaseInfo={selectedMetaItem.releaseInfo}
released={selectedMetaItem.released}
description={selectedMetaItem.description}
deepLinks={selectedMetaItem.deepLinks}
trailerStreams={selectedMetaItem.trailerStreams}
inLibrary={selectedMetaItem.inLibrary}
toggleInLibrary={selectedMetaItem.inLibrary ? removeFromLibrary : addToLibrary}

View file

@ -6,13 +6,15 @@ 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');
const SubtitlesMenu = require('./SubtitlesMenu');
const Video = require('./Video');
const usePlayer = require('./usePlayer');
const usePlaylist = require('./usePlaylist');
const useSettings = require('./useSettings');
const styles = require('./styles');
@ -22,6 +24,7 @@ const Player = ({ urlParams, queryParams }) => {
return queryParams.has('forceTranscoding');
}, [queryParams]);
const [player, updateLibraryItemState, pushToLibrary] = usePlayer(urlParams);
const playlist = usePlaylist(player);
const [settings, updateSettings] = useSettings();
const streamingServer = useStreamingServer();
const routeFocused = useRouteFocused();
@ -233,7 +236,7 @@ const Player = ({ urlParams, queryParams }) => {
streamingServer.selected.transportUrl
:
null,
chromecastTransport: chromecast.transport
chromecastTransport: chromecast.active ? chromecast.transport : null
}
});
}
@ -331,14 +334,16 @@ const Player = ({ urlParams, queryParams }) => {
}
case 'ArrowRight': {
if (!subtitlesMenuOpen && !infoMenuOpen && videoState.time !== null) {
onSeekRequested(videoState.time + 15000);
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
onSeekRequested(videoState.time + (settings.seekTimeDuration * seekTimeMultiplier));
}
break;
}
case 'ArrowLeft': {
if (!subtitlesMenuOpen && !infoMenuOpen && videoState.time !== null) {
onSeekRequested(videoState.time - 15000);
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
onSeekRequested(videoState.time - (settings.seekTimeDuration * seekTimeMultiplier));
}
break;
@ -386,7 +391,7 @@ const Player = ({ urlParams, queryParams }) => {
return () => {
window.removeEventListener('keydown', onKeyDown);
};
}, [player, routeFocused, subtitlesMenuOpen, infoMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.subtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu]);
}, [player, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.subtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu]);
React.useLayoutEffect(() => {
return () => {
setImmersedDebounced.cancel();
@ -415,18 +420,30 @@ 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']} title={error.message}>{error.message}</div>
{
playlist ?
<Button className={styles['playlist-button']} title={'Download M3U Playlist'} href={playlist.href} download={playlist.name}>
<Icon className={styles['icon']} icon={'ic_downloads'} />
<div className={styles['label']}>Download Playlist</div>
</Button>
:
null
}
</div>
:
null
}
{
subtitlesMenuOpen || infoMenuOpen ?
<div className={styles['layer']} />

View file

@ -40,7 +40,7 @@ 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;
@ -53,6 +53,39 @@ html:not(.active-slider-within) {
color: @color-surface-light5-90;
text-align: center;
}
.playlist-button {
flex: none;
display: flex;
flex-direction: row;
align-items: center;
height: 3.5rem;
max-width: 16rem;
margin-top: 1.5rem;
padding: 0.5rem 1rem;
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: 1;
max-height: 2.4em;
font-size: 1.1rem;
font-weight: 500;
color: @color-surface-light5-90;
text-align: center;
}
}
}
&.nav-bar-layer {

View file

@ -0,0 +1,16 @@
const React = require('react');
const usePlaylist = (player) => {
return React.useMemo(() => {
if (player.selected === null || typeof player.selected.stream.url !== 'string') {
return null;
}
const name = `${player.title}.m3u`;
const m3u = `#EXTM3U\n\n#EXTINF:0,${encodeURIComponent(player.title)}\n${encodeURI(player.selected.stream.url)}`;
const href = `data:application/octet-stream;charset=utf-8;base64,${window.btoa(m3u)}`;
return { name, href };
}, [player]);
};
module.exports = usePlaylist;

View file

@ -27,6 +27,7 @@ const Settings = () => {
subtitlesTextColorInput,
subtitlesBackgroundColorInput,
subtitlesOutlineColorInput,
seekTimeDurationSelect,
bingeWatchingCheckbox,
playInBackgroundCheckbox,
playInExternalPlayerCheckbox,
@ -303,6 +304,15 @@ const Settings = () => {
{...subtitlesOutlineColorInput}
/>
</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Rewind & Fast-forward duration</div>
</div>
<Multiselect
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
{...seekTimeDurationSelect}
/>
</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Auto-play next episode</div>

View file

@ -110,6 +110,28 @@ const useProfileSettingsInputs = (profile) => {
});
}
}), [profile.settings]);
const seekTimeDurationSelect = useDeepEqualMemo(() => ({
options: CONSTANTS.SEEK_TIME_DURATIONS.map((size) => ({
value: `${size}`,
label: `${size / 1000} seconds`
})),
selected: [`${profile.settings.seekTimeDuration}`],
renderLabelText: () => {
return `${profile.settings.seekTimeDuration / 1000} seconds`;
},
onSelect: (event) => {
core.transport.dispatch({
action: 'Ctx',
args: {
action: 'UpdateSettings',
args: {
...profile.settings,
seekTimeDuration: parseInt(event.value, 10)
}
}
});
}
}), [profile.settings]);
const bingeWatchingCheckbox = useDeepEqualMemo(() => ({
checked: profile.settings.bingeWatching,
onClick: () => {
@ -192,6 +214,7 @@ const useProfileSettingsInputs = (profile) => {
subtitlesTextColorInput,
subtitlesBackgroundColorInput,
subtitlesOutlineColorInput,
seekTimeDurationSelect,
bingeWatchingCheckbox,
playInBackgroundCheckbox,
playInExternalPlayerCheckbox,