mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge pull request #152 from Stremio/player-meta-preview
Player meta preview
This commit is contained in:
commit
dae56660dd
30 changed files with 374 additions and 351 deletions
|
|
@ -4,6 +4,8 @@ const CATALOG_PREVIEW_SIZE = 10;
|
||||||
const CATALOG_PAGE_SIZE = 100;
|
const CATALOG_PAGE_SIZE = 100;
|
||||||
const NONE_EXTRA_VALUE = 'None';
|
const NONE_EXTRA_VALUE = 'None';
|
||||||
const SKIP_EXTRA_NAME = 'skip';
|
const SKIP_EXTRA_NAME = 'skip';
|
||||||
|
const IMDB_LINK_CATEGORY = 'imdb';
|
||||||
|
const SHARE_LINK_CATEGORY = 'share';
|
||||||
const TYPE_PRIORITIES = {
|
const TYPE_PRIORITIES = {
|
||||||
movie: 10,
|
movie: 10,
|
||||||
series: 9,
|
series: 9,
|
||||||
|
|
@ -25,5 +27,7 @@ module.exports = {
|
||||||
CATALOG_PAGE_SIZE,
|
CATALOG_PAGE_SIZE,
|
||||||
NONE_EXTRA_VALUE,
|
NONE_EXTRA_VALUE,
|
||||||
SKIP_EXTRA_NAME,
|
SKIP_EXTRA_NAME,
|
||||||
|
IMDB_LINK_CATEGORY,
|
||||||
|
SHARE_LINK_CATEGORY,
|
||||||
TYPE_PRIORITIES
|
TYPE_PRIORITIES
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const Button = require('stremio/common/Button');
|
||||||
const Image = require('stremio/common/Image');
|
const Image = require('stremio/common/Image');
|
||||||
const ModalDialog = require('stremio/common/ModalDialog');
|
const ModalDialog = require('stremio/common/ModalDialog');
|
||||||
const SharePrompt = require('stremio/common/SharePrompt');
|
const SharePrompt = require('stremio/common/SharePrompt');
|
||||||
|
const CONSTANTS = require('stremio/common/CONSTANTS');
|
||||||
const routesRegexp = require('stremio/common/routesRegexp');
|
const routesRegexp = require('stremio/common/routesRegexp');
|
||||||
const useBinaryState = require('stremio/common/useBinaryState');
|
const useBinaryState = require('stremio/common/useBinaryState');
|
||||||
const ActionButton = require('./ActionButton');
|
const ActionButton = require('./ActionButton');
|
||||||
|
|
@ -15,8 +16,6 @@ const MetaLinks = require('./MetaLinks');
|
||||||
const MetaPreviewPlaceholder = require('./MetaPreviewPlaceholder');
|
const MetaPreviewPlaceholder = require('./MetaPreviewPlaceholder');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const IMDB_LINK_CATEGORY = 'imdb';
|
|
||||||
const SHARE_LINK_CATEGORY = 'share';
|
|
||||||
const ALLOWED_LINK_REDIRECTS = [
|
const ALLOWED_LINK_REDIRECTS = [
|
||||||
routesRegexp.search.regexp,
|
routesRegexp.search.regexp,
|
||||||
routesRegexp.discover.regexp,
|
routesRegexp.discover.regexp,
|
||||||
|
|
@ -30,12 +29,12 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
||||||
links
|
links
|
||||||
.filter((link) => link && typeof link.category === 'string' && typeof link.url === 'string')
|
.filter((link) => link && typeof link.category === 'string' && typeof link.url === 'string')
|
||||||
.reduce((linksGroups, { category, name, url }) => {
|
.reduce((linksGroups, { category, name, url }) => {
|
||||||
if (category === IMDB_LINK_CATEGORY) {
|
if (category === CONSTANTS.IMDB_LINK_CATEGORY) {
|
||||||
linksGroups[category] = {
|
linksGroups[category] = {
|
||||||
label: name,
|
label: name,
|
||||||
href: `https://www.stremio.com/warning#${encodeURIComponent(`https://www.imdb.com/title/${encodeURIComponent(url)}`)}`
|
href: `https://www.stremio.com/warning#${encodeURIComponent(`https://www.imdb.com/title/${encodeURIComponent(url)}`)}`
|
||||||
};
|
};
|
||||||
} else if (category === SHARE_LINK_CATEGORY) {
|
} else if (category === CONSTANTS.SHARE_LINK_CATEGORY) {
|
||||||
linksGroups[category] = {
|
linksGroups[category] = {
|
||||||
label: name,
|
label: name,
|
||||||
href: url
|
href: url
|
||||||
|
|
@ -96,7 +95,7 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(typeof releaseInfo === 'string' && releaseInfo.length > 0) || (released instanceof Date && !isNaN(released.getTime())) || (typeof runtime === 'string' && runtime.length > 0) || typeof linksGroups[IMDB_LINK_CATEGORY] === 'object' ?
|
(typeof releaseInfo === 'string' && releaseInfo.length > 0) || (released instanceof Date && !isNaN(released.getTime())) || (typeof runtime === 'string' && runtime.length > 0) || typeof linksGroups[CONSTANTS.IMDB_LINK_CATEGORY] === 'object' ?
|
||||||
<div className={styles['runtime-release-info-container']}>
|
<div className={styles['runtime-release-info-container']}>
|
||||||
{
|
{
|
||||||
typeof runtime === 'string' && runtime.length > 0 ?
|
typeof runtime === 'string' && runtime.length > 0 ?
|
||||||
|
|
@ -114,16 +113,16 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
typeof linksGroups[IMDB_LINK_CATEGORY] === 'object' ?
|
typeof linksGroups[CONSTANTS.IMDB_LINK_CATEGORY] === 'object' ?
|
||||||
<Button
|
<Button
|
||||||
className={styles['imdb-button-container']}
|
className={styles['imdb-button-container']}
|
||||||
title={linksGroups[IMDB_LINK_CATEGORY].label}
|
title={linksGroups[CONSTANTS.IMDB_LINK_CATEGORY].label}
|
||||||
href={linksGroups[IMDB_LINK_CATEGORY].href}
|
href={linksGroups[CONSTANTS.IMDB_LINK_CATEGORY].href}
|
||||||
target={'_blank'}
|
target={'_blank'}
|
||||||
{...(compact ? { tabIndex: -1 } : null)}
|
{...(compact ? { tabIndex: -1 } : null)}
|
||||||
>
|
>
|
||||||
<Icon className={styles['icon']} icon={'ic_imdbnoframe'} />
|
<Icon className={styles['icon']} icon={'ic_imdbnoframe'} />
|
||||||
<div className={styles['label']}>{linksGroups[IMDB_LINK_CATEGORY].label}</div>
|
<div className={styles['label']}>{linksGroups[CONSTANTS.IMDB_LINK_CATEGORY].label}</div>
|
||||||
</Button>
|
</Button>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|
@ -149,8 +148,8 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
||||||
{
|
{
|
||||||
Object.keys(linksGroups)
|
Object.keys(linksGroups)
|
||||||
.filter((category) => {
|
.filter((category) => {
|
||||||
return category !== IMDB_LINK_CATEGORY &&
|
return category !== CONSTANTS.IMDB_LINK_CATEGORY &&
|
||||||
category !== SHARE_LINK_CATEGORY;
|
category !== CONSTANTS.SHARE_LINK_CATEGORY;
|
||||||
})
|
})
|
||||||
.map((category, index) => (
|
.map((category, index) => (
|
||||||
<MetaLinks
|
<MetaLinks
|
||||||
|
|
@ -188,7 +187,7 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
!compact && typeof linksGroups[SHARE_LINK_CATEGORY] === 'object' ?
|
typeof linksGroups[CONSTANTS.SHARE_LINK_CATEGORY] === 'object' ?
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
className={styles['action-button']}
|
className={styles['action-button']}
|
||||||
|
|
@ -202,7 +201,7 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
||||||
<ModalDialog title={'Share'} onCloseRequest={closeShareModal}>
|
<ModalDialog title={'Share'} onCloseRequest={closeShareModal}>
|
||||||
<SharePrompt
|
<SharePrompt
|
||||||
className={styles['share-prompt']}
|
className={styles['share-prompt']}
|
||||||
url={linksGroups[SHARE_LINK_CATEGORY].href}
|
url={linksGroups[CONSTANTS.SHARE_LINK_CATEGORY].href}
|
||||||
/>
|
/>
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
:
|
:
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,6 @@ html.active-slider-within {
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider-container {
|
.slider-container {
|
||||||
--track-size: 0.5rem;
|
|
||||||
--thumb-size: 1.5rem;
|
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,7 @@ const PropTypes = require('prop-types');
|
||||||
const classnames = require('classnames');
|
const classnames = require('classnames');
|
||||||
const Icon = require('stremio-icons/dom');
|
const Icon = require('stremio-icons/dom');
|
||||||
const { Button } = require('stremio/common');
|
const { Button } = require('stremio/common');
|
||||||
const MetaPreviewButton = require('./MetaPreviewButton');
|
|
||||||
const MuteButton = require('./MuteButton');
|
|
||||||
const PlayPauseButton = require('./PlayPauseButton');
|
|
||||||
const SeekBar = require('./SeekBar');
|
const SeekBar = require('./SeekBar');
|
||||||
const SubtitlesButton = require('./SubtitlesButton');
|
|
||||||
const VolumeSlider = require('./VolumeSlider');
|
const VolumeSlider = require('./VolumeSlider');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
|
|
@ -19,17 +15,55 @@ const ControlBar = ({
|
||||||
volume,
|
volume,
|
||||||
muted,
|
muted,
|
||||||
subtitlesTracks,
|
subtitlesTracks,
|
||||||
metaResource,
|
info,
|
||||||
onPlayRequested,
|
onPlayRequested,
|
||||||
onPauseRequested,
|
onPauseRequested,
|
||||||
onMuteRequested,
|
onMuteRequested,
|
||||||
onUnmuteRequested,
|
onUnmuteRequested,
|
||||||
onVolumeChangeRequested,
|
onVolumeChangeRequested,
|
||||||
onSeekRequested,
|
onSeekRequested,
|
||||||
onToggleSubtitlesPicker,
|
onToggleSubtitlesMenu,
|
||||||
onToggleMetaPreview,
|
onToggleInfoMenu,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
const onSubtitlesButtonMouseDown = React.useCallback((event) => {
|
||||||
|
event.nativeEvent.subtitlesMenuClosePrevented = true;
|
||||||
|
}, []);
|
||||||
|
const onInfoButtonMouseDown = React.useCallback((event) => {
|
||||||
|
event.nativeEvent.infoMenuClosePrevented = true;
|
||||||
|
}, []);
|
||||||
|
const onPlayPauseButtonClick = React.useCallback(() => {
|
||||||
|
if (paused) {
|
||||||
|
if (typeof onPlayRequested === 'function') {
|
||||||
|
onPlayRequested();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof onPauseRequested === 'function') {
|
||||||
|
onPauseRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [paused, onPlayRequested, onPauseRequested]);
|
||||||
|
const onMuteButtonClick = React.useCallback(() => {
|
||||||
|
if (muted) {
|
||||||
|
if (typeof onUnmuteRequested === 'function') {
|
||||||
|
onUnmuteRequested();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof onMuteRequested === 'function') {
|
||||||
|
onMuteRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [muted, onMuteRequested, onUnmuteRequested]);
|
||||||
|
const onSubtitlesButtonClick = React.useCallback(() => {
|
||||||
|
if (typeof onToggleSubtitlesMenu === 'function') {
|
||||||
|
onToggleSubtitlesMenu();
|
||||||
|
}
|
||||||
|
}, [onToggleSubtitlesMenu]);
|
||||||
|
const onInfoButtonClick = React.useCallback(() => {
|
||||||
|
if (typeof onToggleInfoMenu === 'function') {
|
||||||
|
onToggleInfoMenu();
|
||||||
|
}
|
||||||
|
}, [onToggleInfoMenu]);
|
||||||
return (
|
return (
|
||||||
<div {...props} className={classnames(className, styles['control-bar-container'])}>
|
<div {...props} className={classnames(className, styles['control-bar-container'])}>
|
||||||
<SeekBar
|
<SeekBar
|
||||||
|
|
@ -39,19 +73,21 @@ const ControlBar = ({
|
||||||
onSeekRequested={onSeekRequested}
|
onSeekRequested={onSeekRequested}
|
||||||
/>
|
/>
|
||||||
<div className={styles['control-bar-buttons-container']}>
|
<div className={styles['control-bar-buttons-container']}>
|
||||||
<PlayPauseButton
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': typeof paused !== 'boolean' })} title={paused ? 'Play' : 'Pause'} tabIndex={-1} onClick={onPlayPauseButtonClick}>
|
||||||
className={styles['control-bar-button']}
|
<Icon className={styles['icon']} icon={typeof paused !== 'boolean' || paused ? 'ic_play' : 'ic_pause'} />
|
||||||
paused={paused}
|
</Button>
|
||||||
onPlayRequested={onPlayRequested}
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': typeof muted !== 'boolean' })} title={muted ? 'Unmute' : 'Mute'} tabIndex={-1} onClick={onMuteButtonClick}>
|
||||||
onPauseRequested={onPauseRequested}
|
<Icon
|
||||||
/>
|
className={styles['icon']}
|
||||||
<MuteButton
|
icon={
|
||||||
className={styles['control-bar-button']}
|
(typeof muted === 'boolean' && muted) ? 'ic_volume0' :
|
||||||
volume={volume}
|
(volume === null || isNaN(volume)) ? 'ic_volume3' :
|
||||||
muted={muted}
|
volume < 30 ? 'ic_volume1' :
|
||||||
onMuteRequested={onMuteRequested}
|
volume < 70 ? 'ic_volume2' :
|
||||||
onUnmuteRequested={onUnmuteRequested}
|
'ic_volume3'
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
<VolumeSlider
|
<VolumeSlider
|
||||||
className={styles['volume-slider']}
|
className={styles['volume-slider']}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
|
|
@ -59,23 +95,19 @@ const ControlBar = ({
|
||||||
/>
|
/>
|
||||||
<div className={styles['spacing']} />
|
<div className={styles['spacing']} />
|
||||||
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
||||||
<Icon className={'icon'} icon={'ic_network'} />
|
<Icon className={styles['icon']} icon={'ic_network'} />
|
||||||
</Button>
|
</Button>
|
||||||
<MetaPreviewButton
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': typeof info !== 'object' || info === null })} tabIndex={-1} onMouseDown={onInfoButtonMouseDown} onClick={onInfoButtonClick}>
|
||||||
className={styles['control-bar-button']}
|
<Icon className={styles['icon']} icon={'ic_info'} />
|
||||||
metaResource={metaResource}
|
|
||||||
onToggleMetaPreview={onToggleMetaPreview}
|
|
||||||
/>
|
|
||||||
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
|
||||||
<Icon className={'icon'} icon={'ic_cast'} />
|
|
||||||
</Button>
|
</Button>
|
||||||
<SubtitlesButton
|
|
||||||
className={styles['control-bar-button']}
|
|
||||||
subtitlesTracks={subtitlesTracks}
|
|
||||||
onToggleSubtitlesPicker={onToggleSubtitlesPicker}
|
|
||||||
/>
|
|
||||||
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
||||||
<Icon className={'icon'} icon={'ic_videos'} />
|
<Icon className={styles['icon']} icon={'ic_cast'} />
|
||||||
|
</Button>
|
||||||
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': !Array.isArray(subtitlesTracks) || subtitlesTracks.length === 0 })} tabIndex={-1} onMouseDown={onSubtitlesButtonMouseDown} onClick={onSubtitlesButtonClick}>
|
||||||
|
<Icon className={styles['icon']} icon={'ic_sub'} />
|
||||||
|
</Button>
|
||||||
|
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
||||||
|
<Icon className={styles['icon']} icon={'ic_videos'} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -84,21 +116,21 @@ const ControlBar = ({
|
||||||
|
|
||||||
ControlBar.propTypes = {
|
ControlBar.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
paused: PropTypes.any,
|
paused: PropTypes.bool,
|
||||||
time: PropTypes.any,
|
time: PropTypes.number,
|
||||||
duration: PropTypes.any,
|
duration: PropTypes.number,
|
||||||
volume: PropTypes.any,
|
volume: PropTypes.number,
|
||||||
muted: PropTypes.any,
|
muted: PropTypes.bool,
|
||||||
subtitlesTracks: PropTypes.any,
|
subtitlesTracks: PropTypes.array,
|
||||||
metaResource: PropTypes.any,
|
info: PropTypes.object,
|
||||||
onPlayRequested: PropTypes.any,
|
onPlayRequested: PropTypes.func,
|
||||||
onPauseRequested: PropTypes.any,
|
onPauseRequested: PropTypes.func,
|
||||||
onMuteRequested: PropTypes.any,
|
onMuteRequested: PropTypes.func,
|
||||||
onUnmuteRequested: PropTypes.any,
|
onUnmuteRequested: PropTypes.func,
|
||||||
onVolumeChangeRequested: PropTypes.any,
|
onVolumeChangeRequested: PropTypes.func,
|
||||||
onSeekRequested: PropTypes.any,
|
onSeekRequested: PropTypes.func,
|
||||||
onToggleSubtitlesPicker: PropTypes.any,
|
onToggleSubtitlesMenu: PropTypes.func,
|
||||||
onToggleMetaPreview: PropTypes.any
|
onToggleInfoMenu: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = ControlBar;
|
module.exports = ControlBar;
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
const React = require('react');
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
const classnames = require('classnames');
|
|
||||||
const Icon = require('stremio-icons/dom');
|
|
||||||
const { Button } = require('stremio/common');
|
|
||||||
|
|
||||||
const MetaPreviewButton = ({ className, metaResource, onToggleMetaPreview }) => {
|
|
||||||
const onMouseDown = React.useCallback((event) => {
|
|
||||||
event.nativeEvent.metaPreviewClosePrevented = true;
|
|
||||||
}, []);
|
|
||||||
const onClick = React.useCallback(() => {
|
|
||||||
if (typeof onToggleMetaPreview === 'function') {
|
|
||||||
onToggleMetaPreview();
|
|
||||||
}
|
|
||||||
}, [onToggleMetaPreview]);
|
|
||||||
return (
|
|
||||||
<Button className={classnames(className, { 'disabled': metaResource === null || metaResource.content.type !== 'Ready' })} tabIndex={-1} onMouseDown={onMouseDown} onClick={onClick}>
|
|
||||||
<Icon className={'icon'} icon={'ic_info'} />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
MetaPreviewButton.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
metaResource: PropTypes.object,
|
|
||||||
onToggleMetaPreview: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = MetaPreviewButton;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
const MetaPreviewButton = require('./MetaPreviewButton');
|
|
||||||
|
|
||||||
module.exports = MetaPreviewButton;
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
const React = require('react');
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
const classnames = require('classnames');
|
|
||||||
const Icon = require('stremio-icons/dom');
|
|
||||||
const { Button } = require('stremio/common');
|
|
||||||
|
|
||||||
const MuteButton = ({ className, muted, volume, onMuteRequested, onUnmuteRequested }) => {
|
|
||||||
const toggleMuted = React.useCallback(() => {
|
|
||||||
if (muted) {
|
|
||||||
if (typeof onUnmuteRequested === 'function') {
|
|
||||||
onUnmuteRequested();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (typeof onMuteRequested === 'function') {
|
|
||||||
onMuteRequested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [muted, onMuteRequested, onUnmuteRequested]);
|
|
||||||
const icon = (typeof muted === 'boolean' && muted) ? 'ic_volume0' :
|
|
||||||
(volume === null || isNaN(volume)) ? 'ic_volume3' :
|
|
||||||
volume < 30 ? 'ic_volume1' :
|
|
||||||
volume < 70 ? 'ic_volume2' :
|
|
||||||
'ic_volume3';
|
|
||||||
return (
|
|
||||||
<Button className={classnames(className, { 'disabled': typeof muted !== 'boolean' })} title={muted ? 'Unmute' : 'Mute'} tabIndex={-1} onClick={toggleMuted}>
|
|
||||||
<Icon className={'icon'} icon={icon} />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
MuteButton.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
muted: PropTypes.bool,
|
|
||||||
volume: PropTypes.number,
|
|
||||||
onMuteRequested: PropTypes.func,
|
|
||||||
onUnmuteRequested: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = MuteButton;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
const MuteButton = require('./MuteButton');
|
|
||||||
|
|
||||||
module.exports = MuteButton;
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
const React = require('react');
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
const classnames = require('classnames');
|
|
||||||
const Icon = require('stremio-icons/dom');
|
|
||||||
const { Button } = require('stremio/common');
|
|
||||||
|
|
||||||
const PlayPauseButton = ({ className, paused, onPlayRequested, onPauseRequested }) => {
|
|
||||||
const togglePaused = React.useCallback(() => {
|
|
||||||
if (paused) {
|
|
||||||
if (typeof onPlayRequested === 'function') {
|
|
||||||
onPlayRequested();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (typeof onPauseRequested === 'function') {
|
|
||||||
onPauseRequested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [paused, onPlayRequested, onPauseRequested]);
|
|
||||||
return (
|
|
||||||
<Button className={classnames(className, { 'disabled': typeof paused !== 'boolean' })} title={paused ? 'Play' : 'Pause'} tabIndex={-1} onClick={togglePaused}>
|
|
||||||
<Icon
|
|
||||||
className={'icon'}
|
|
||||||
icon={typeof paused !== 'boolean' || paused ? 'ic_play' : 'ic_pause'}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
PlayPauseButton.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
paused: PropTypes.bool,
|
|
||||||
onPlayRequested: PropTypes.func,
|
|
||||||
onPauseRequested: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = PlayPauseButton;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
const PlayPauseButton = require('./PlayPauseButton');
|
|
||||||
|
|
||||||
module.exports = PlayPauseButton;
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
const React = require('react');
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
const classnames = require('classnames');
|
|
||||||
const Icon = require('stremio-icons/dom');
|
|
||||||
const { Button } = require('stremio/common');
|
|
||||||
|
|
||||||
const SubtitlesButton = ({ className, subtitlesTracks, onToggleSubtitlesPicker }) => {
|
|
||||||
const onMouseDown = React.useCallback((event) => {
|
|
||||||
event.nativeEvent.subtitlesPickerClosePrevented = true;
|
|
||||||
}, []);
|
|
||||||
const onClick = React.useCallback(() => {
|
|
||||||
if (typeof onToggleSubtitlesPicker === 'function') {
|
|
||||||
onToggleSubtitlesPicker();
|
|
||||||
}
|
|
||||||
}, [onToggleSubtitlesPicker]);
|
|
||||||
return (
|
|
||||||
<Button className={classnames(className, { 'disabled': !Array.isArray(subtitlesTracks) || subtitlesTracks.length === 0 })} tabIndex={-1} onMouseDown={onMouseDown} onClick={onClick}>
|
|
||||||
<Icon className={'icon'} icon={'ic_sub'} />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
SubtitlesButton.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
subtitlesTracks: PropTypes.array,
|
|
||||||
onToggleSubtitlesPicker: PropTypes.func
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = SubtitlesButton;
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
const SubtitlesButton = require('./SubtitlesButton');
|
|
||||||
|
|
||||||
module.exports = SubtitlesButton;
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
padding: 0 1.5rem;
|
padding: 0 1.5rem;
|
||||||
|
|
||||||
.seek-bar {
|
.seek-bar {
|
||||||
|
--track-size: 0.5rem;
|
||||||
|
--thumb-size: 1.5rem;
|
||||||
|
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,12 +24,12 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:global(.disabled) {
|
&:global(.disabled) {
|
||||||
:global(.icon) {
|
.icon {
|
||||||
fill: @color-surface;
|
fill: @color-surface;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.icon) {
|
.icon {
|
||||||
flex: none;
|
flex: none;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
|
|
|
||||||
43
src/routes/Player/InfoMenu/InfoMenu.js
Normal file
43
src/routes/Player/InfoMenu/InfoMenu.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
const React = require('react');
|
||||||
|
const PropTypes = require('prop-types');
|
||||||
|
const classnames = require('classnames');
|
||||||
|
const { MetaPreview, CONSTANTS } = require('stremio/common');
|
||||||
|
const styles = require('./styles');
|
||||||
|
|
||||||
|
const InfoMenu = ({ className, ...props }) => {
|
||||||
|
// TODO handle stream and addon
|
||||||
|
const metaItem = React.useMemo(() => {
|
||||||
|
return props.metaItem !== null ?
|
||||||
|
{
|
||||||
|
...props.metaItem,
|
||||||
|
links: props.metaItem.links.filter(({ category }) => category === CONSTANTS.SHARE_LINK_CATEGORY)
|
||||||
|
}
|
||||||
|
:
|
||||||
|
null;
|
||||||
|
}, [props.metaItem]);
|
||||||
|
const onMouseDown = React.useCallback((event) => {
|
||||||
|
event.nativeEvent.infoMenuClosePrevented = true;
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div className={classnames(className, styles['info-menu-container'])} onMouseDown={onMouseDown}>
|
||||||
|
<MetaPreview
|
||||||
|
className={styles['meta-preview']}
|
||||||
|
compact={true}
|
||||||
|
name={metaItem.name}
|
||||||
|
logo={metaItem.logo}
|
||||||
|
runtime={metaItem.runtime}
|
||||||
|
releaseInfo={metaItem.releaseInfo}
|
||||||
|
released={metaItem.released}
|
||||||
|
description={metaItem.description}
|
||||||
|
links={metaItem.links}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
InfoMenu.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
metaItem: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = InfoMenu;
|
||||||
3
src/routes/Player/InfoMenu/index.js
Normal file
3
src/routes/Player/InfoMenu/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
const InfoMenu = require('./InfoMenu');
|
||||||
|
|
||||||
|
module.exports = InfoMenu;
|
||||||
3
src/routes/Player/InfoMenu/styles.less
Normal file
3
src/routes/Player/InfoMenu/styles.less
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.info-menu-container {
|
||||||
|
width: 30rem;
|
||||||
|
}
|
||||||
|
|
@ -4,26 +4,30 @@ const classnames = require('classnames');
|
||||||
const debounce = require('lodash.debounce');
|
const debounce = require('lodash.debounce');
|
||||||
const { useRouteFocused } = require('stremio-router');
|
const { useRouteFocused } = require('stremio-router');
|
||||||
const { useServices } = require('stremio/services');
|
const { useServices } = require('stremio/services');
|
||||||
const { HorizontalNavBar, useDeepEqualEffect, useDeepEqualMemo, useFullscreen, useBinaryState, useToast } = require('stremio/common');
|
const { HorizontalNavBar, useDeepEqualEffect, useFullscreen, useBinaryState, useToast, useProfile } = require('stremio/common');
|
||||||
const BufferingLoader = require('./BufferingLoader');
|
const BufferingLoader = require('./BufferingLoader');
|
||||||
const ControlBar = require('./ControlBar');
|
const ControlBar = require('./ControlBar');
|
||||||
const SubtitlesPicker = require('./SubtitlesPicker');
|
const InfoMenu = require('./InfoMenu');
|
||||||
|
const SubtitlesMenu = require('./SubtitlesMenu');
|
||||||
const Video = require('./Video');
|
const Video = require('./Video');
|
||||||
|
const useInfo = require('./useInfo');
|
||||||
const usePlayer = require('./usePlayer');
|
const usePlayer = require('./usePlayer');
|
||||||
const useSettings = require('./useSettings');
|
const useSettings = require('./useSettings');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const Player = ({ urlParams }) => {
|
const Player = ({ urlParams }) => {
|
||||||
const { core } = useServices();
|
const { core } = useServices();
|
||||||
const player = usePlayer(urlParams);
|
const profile = useProfile();
|
||||||
const [settings, updateSettings] = useSettings();
|
const [player, updateLibraryItemState, pushToLibrary] = usePlayer(urlParams);
|
||||||
|
const [settings, updateSettings] = useSettings(profile);
|
||||||
|
const info = useInfo(player, profile);
|
||||||
const routeFocused = useRouteFocused();
|
const routeFocused = useRouteFocused();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const [, , , toggleFullscreen] = useFullscreen();
|
const [, , , toggleFullscreen] = useFullscreen();
|
||||||
const [immersed, setImmersed] = React.useState(true);
|
const [immersed, setImmersed] = React.useState(true);
|
||||||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||||
const [subtitlesPickerOpen, , closeSubtitlesPicker, toggleSubtitlesPicker] = useBinaryState(false);
|
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||||
const [metaPreviewOpen, , closeMetaPreview, toggleMetaPreview] = useBinaryState(false);
|
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
|
||||||
const [error, setError] = React.useState(null);
|
const [error, setError] = React.useState(null);
|
||||||
const [videoState, setVideoState] = React.useReducer(
|
const [videoState, setVideoState] = React.useReducer(
|
||||||
(videoState, nextVideoState) => ({ ...videoState, ...nextVideoState }),
|
(videoState, nextVideoState) => ({ ...videoState, ...nextVideoState }),
|
||||||
|
|
@ -63,7 +67,7 @@ const Player = ({ urlParams }) => {
|
||||||
const onPropChanged = React.useCallback((propName, propValue) => {
|
const onPropChanged = React.useCallback((propName, propValue) => {
|
||||||
setVideoState({ [propName]: propValue });
|
setVideoState({ [propName]: propValue });
|
||||||
}, []);
|
}, []);
|
||||||
const onEnded = useDeepEqualMemo(() => () => {
|
const onEnded = React.useCallback(() => {
|
||||||
core.dispatch({ action: 'Unload' }, 'player');
|
core.dispatch({ action: 'Unload' }, 'player');
|
||||||
if (player.lib_item !== null) {
|
if (player.lib_item !== null) {
|
||||||
core.dispatch({
|
core.dispatch({
|
||||||
|
|
@ -78,7 +82,7 @@ const Player = ({ urlParams }) => {
|
||||||
// TODO go to next video
|
// TODO go to next video
|
||||||
}
|
}
|
||||||
window.history.back();
|
window.history.back();
|
||||||
}, [player.next_video, player.lib_item]);
|
}, [player]);
|
||||||
const onError = React.useCallback((error) => {
|
const onError = React.useCallback((error) => {
|
||||||
if (error.critical) {
|
if (error.critical) {
|
||||||
setError(error);
|
setError(error);
|
||||||
|
|
@ -102,9 +106,11 @@ const Player = ({ urlParams }) => {
|
||||||
const onPlayRequested = React.useCallback(() => {
|
const onPlayRequested = React.useCallback(() => {
|
||||||
dispatch({ propName: 'paused', propValue: false });
|
dispatch({ propName: 'paused', propValue: false });
|
||||||
}, []);
|
}, []);
|
||||||
|
const onPlayRequestedDebounced = React.useCallback(debounce(onPlayRequested, 200), []);
|
||||||
const onPauseRequested = React.useCallback(() => {
|
const onPauseRequested = React.useCallback(() => {
|
||||||
dispatch({ propName: 'paused', propValue: true });
|
dispatch({ propName: 'paused', propValue: true });
|
||||||
}, []);
|
}, []);
|
||||||
|
const onPauseRequestedDebounced = React.useCallback(debounce(onPauseRequested, 200), []);
|
||||||
const onMuteRequested = React.useCallback(() => {
|
const onMuteRequested = React.useCallback(() => {
|
||||||
dispatch({ propName: 'muted', propValue: true });
|
dispatch({ propName: 'muted', propValue: true });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -132,18 +138,23 @@ const Player = ({ urlParams }) => {
|
||||||
const onVideoClick = React.useCallback(() => {
|
const onVideoClick = React.useCallback(() => {
|
||||||
if (videoState.paused !== null) {
|
if (videoState.paused !== null) {
|
||||||
if (videoState.paused) {
|
if (videoState.paused) {
|
||||||
onPlayRequested();
|
onPlayRequestedDebounced();
|
||||||
} else {
|
} else {
|
||||||
onPauseRequested();
|
onPauseRequestedDebounced();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [videoState.paused]);
|
}, [videoState.paused]);
|
||||||
|
const onVideoDoubleClick = React.useCallback(() => {
|
||||||
|
onPlayRequestedDebounced.cancel();
|
||||||
|
onPauseRequestedDebounced.cancel();
|
||||||
|
toggleFullscreen();
|
||||||
|
}, [toggleFullscreen]);
|
||||||
const onContainerMouseDown = React.useCallback((event) => {
|
const onContainerMouseDown = React.useCallback((event) => {
|
||||||
if (!event.nativeEvent.subtitlesPickerClosePrevented) {
|
if (!event.nativeEvent.subtitlesMenuClosePrevented) {
|
||||||
closeSubtitlesPicker();
|
closeSubtitlesMenu();
|
||||||
}
|
}
|
||||||
if (!event.nativeEvent.metaPreviewClosePrevented) {
|
if (!event.nativeEvent.infoMenuClosePrevented) {
|
||||||
closeMetaPreview();
|
closeInfoMenu();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const onContainerMouseMove = React.useCallback((event) => {
|
const onContainerMouseMove = React.useCallback((event) => {
|
||||||
|
|
@ -181,7 +192,14 @@ const Player = ({ urlParams }) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
commandName: 'addSubtitlesTracks',
|
commandName: 'addSubtitlesTracks',
|
||||||
commandArgs: {
|
commandArgs: {
|
||||||
tracks: player.selected.stream.subtitles
|
tracks: player.selected.stream.subtitles.map(({ url, lang }) => ({
|
||||||
|
url,
|
||||||
|
lang,
|
||||||
|
origin: player.selected.stream.addon !== null ?
|
||||||
|
player.selected.stream.addon.manifest.name
|
||||||
|
:
|
||||||
|
'EMBEDDED IN STREAM'
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +211,11 @@ const Player = ({ urlParams }) => {
|
||||||
tracks: player.subtitles_resources
|
tracks: player.subtitles_resources
|
||||||
.filter((subtitles_resource) => subtitles_resource.content.type === 'Ready')
|
.filter((subtitles_resource) => subtitles_resource.content.type === 'Ready')
|
||||||
.reduce((tracks, subtitles_resource) => {
|
.reduce((tracks, subtitles_resource) => {
|
||||||
return tracks.concat(subtitles_resource.content.content);
|
return tracks.concat(subtitles_resource.content.content.map(({ url, lang }) => ({
|
||||||
|
url,
|
||||||
|
lang,
|
||||||
|
origin: subtitles_resource.addon !== null ? subtitles_resource.addon.manifest.name : subtitles_resource.request.base
|
||||||
|
})));
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -215,36 +237,30 @@ const Player = ({ urlParams }) => {
|
||||||
}, [settings.subtitles_offset]);
|
}, [settings.subtitles_offset]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (videoState.time !== null && !isNaN(videoState.time) && videoState.duration !== null && !isNaN(videoState.duration)) {
|
if (videoState.time !== null && !isNaN(videoState.time) && videoState.duration !== null && !isNaN(videoState.duration)) {
|
||||||
core.dispatch({
|
updateLibraryItemState(videoState.time, videoState.duration);
|
||||||
action: 'Player',
|
|
||||||
args: {
|
|
||||||
action: 'UpdateLibraryItemState',
|
|
||||||
args: {
|
|
||||||
time: videoState.time,
|
|
||||||
duration: videoState.duration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 'player');
|
|
||||||
}
|
}
|
||||||
}, [videoState.time, videoState.duration]);
|
}, [videoState.time, videoState.duration]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const interval = setInterval(() => {
|
if (!Array.isArray(videoState.subtitlesTracks) || videoState.subtitlesTracks.length === 0) {
|
||||||
core.dispatch({
|
closeSubtitlesMenu();
|
||||||
action: 'Player',
|
}
|
||||||
args: {
|
}, [videoState.subtitlesTracks]);
|
||||||
action: 'PushToLibrary'
|
React.useEffect(() => {
|
||||||
}
|
if (info === null) {
|
||||||
}, 'player');
|
closeInfoMenu();
|
||||||
}, 30000);
|
}
|
||||||
|
}, [info]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const intervalId = setInterval(pushToLibrary, 30000);
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(intervalId);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event) => {
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
case 'Space': {
|
case 'Space': {
|
||||||
if (!subtitlesPickerOpen && !metaPreviewOpen && videoState.paused !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && videoState.paused !== null) {
|
||||||
if (videoState.paused) {
|
if (videoState.paused) {
|
||||||
onPlayRequested();
|
onPlayRequested();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -255,46 +271,52 @@ const Player = ({ urlParams }) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowRight': {
|
case 'ArrowRight': {
|
||||||
if (!subtitlesPickerOpen && !metaPreviewOpen && videoState.time !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && videoState.time !== null) {
|
||||||
onSeekRequested(videoState.time + 15000);
|
onSeekRequested(videoState.time + 15000);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowLeft': {
|
case 'ArrowLeft': {
|
||||||
if (!subtitlesPickerOpen && !metaPreviewOpen && videoState.time !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && videoState.time !== null) {
|
||||||
onSeekRequested(videoState.time - 15000);
|
onSeekRequested(videoState.time - 15000);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowUp': {
|
case 'ArrowUp': {
|
||||||
if (!subtitlesPickerOpen && !metaPreviewOpen && videoState.volume !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && videoState.volume !== null) {
|
||||||
onVolumeChangeRequested(videoState.volume + 5);
|
onVolumeChangeRequested(videoState.volume + 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowDown': {
|
case 'ArrowDown': {
|
||||||
if (!subtitlesPickerOpen && !metaPreviewOpen && videoState.volume !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && videoState.volume !== null) {
|
||||||
onVolumeChangeRequested(videoState.volume - 5);
|
onVolumeChangeRequested(videoState.volume - 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'KeyS': {
|
case 'KeyS': {
|
||||||
closeMetaPreview();
|
closeInfoMenu();
|
||||||
toggleSubtitlesPicker();
|
if (Array.isArray(videoState.subtitlesTracks) && videoState.subtitlesTracks.length > 0) {
|
||||||
|
toggleSubtitlesMenu();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'KeyM': {
|
case 'KeyM': {
|
||||||
closeSubtitlesPicker();
|
closeSubtitlesMenu();
|
||||||
toggleMetaPreview();
|
if (info !== null) {
|
||||||
|
toggleInfoMenu();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'Escape': {
|
case 'Escape': {
|
||||||
closeSubtitlesPicker();
|
closeSubtitlesMenu();
|
||||||
closeMetaPreview();
|
closeInfoMenu();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -305,14 +327,16 @@ const Player = ({ urlParams }) => {
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', onKeyDown);
|
window.removeEventListener('keydown', onKeyDown);
|
||||||
};
|
};
|
||||||
}, [routeFocused, subtitlesPickerOpen, metaPreviewOpen, videoState.paused, videoState.time, videoState.volume, toggleSubtitlesPicker, toggleMetaPreview]);
|
}, [routeFocused, subtitlesMenuOpen, infoMenuOpen, info, videoState.paused, videoState.time, videoState.volume, videoState.subtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu]);
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
setImmersedDebounced.cancel();
|
setImmersedDebounced.cancel();
|
||||||
|
onPlayRequestedDebounced.cancel();
|
||||||
|
onPauseRequestedDebounced.cancel();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && videoState.paused !== null && !videoState.paused && !subtitlesPickerOpen && !metaPreviewOpen })}
|
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen })}
|
||||||
onMouseDown={onContainerMouseDown}
|
onMouseDown={onContainerMouseDown}
|
||||||
onMouseMove={onContainerMouseMove}
|
onMouseMove={onContainerMouseMove}
|
||||||
onMouseOver={onContainerMouseMove}
|
onMouseOver={onContainerMouseMove}
|
||||||
|
|
@ -341,17 +365,17 @@ const Player = ({ urlParams }) => {
|
||||||
<div
|
<div
|
||||||
className={styles['layer']}
|
className={styles['layer']}
|
||||||
onClick={onVideoClick}
|
onClick={onVideoClick}
|
||||||
onDoubleClick={toggleFullscreen}
|
onDoubleClick={onVideoDoubleClick}
|
||||||
/>
|
/>
|
||||||
|
{
|
||||||
|
subtitlesMenuOpen || infoMenuOpen ?
|
||||||
|
<div className={styles['layer']} />
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
<HorizontalNavBar
|
<HorizontalNavBar
|
||||||
className={classnames(styles['layer'], styles['nav-bar-layer'])}
|
className={classnames(styles['layer'], styles['nav-bar-layer'])}
|
||||||
title={
|
title={info !== null ? info.title : ''}
|
||||||
// TODO consider use video.title and fallback to stream.title
|
|
||||||
player.meta_resource !== null && player.meta_resource.content.type === 'Ready' ?
|
|
||||||
player.meta_resource.content.content.name
|
|
||||||
:
|
|
||||||
null
|
|
||||||
}
|
|
||||||
backButton={true}
|
backButton={true}
|
||||||
fullscreenButton={true}
|
fullscreenButton={true}
|
||||||
onMouseMove={onBarMouseMove}
|
onMouseMove={onBarMouseMove}
|
||||||
|
|
@ -365,21 +389,21 @@ const Player = ({ urlParams }) => {
|
||||||
volume={videoState.volume}
|
volume={videoState.volume}
|
||||||
muted={videoState.muted}
|
muted={videoState.muted}
|
||||||
subtitlesTracks={videoState.subtitlesTracks}
|
subtitlesTracks={videoState.subtitlesTracks}
|
||||||
metaResource={null}
|
info={info}
|
||||||
onPlayRequested={onPlayRequested}
|
onPlayRequested={onPlayRequested}
|
||||||
onPauseRequested={onPauseRequested}
|
onPauseRequested={onPauseRequested}
|
||||||
onMuteRequested={onMuteRequested}
|
onMuteRequested={onMuteRequested}
|
||||||
onUnmuteRequested={onUnmuteRequested}
|
onUnmuteRequested={onUnmuteRequested}
|
||||||
onVolumeChangeRequested={onVolumeChangeRequested}
|
onVolumeChangeRequested={onVolumeChangeRequested}
|
||||||
onSeekRequested={onSeekRequested}
|
onSeekRequested={onSeekRequested}
|
||||||
onToggleSubtitlesPicker={toggleSubtitlesPicker}
|
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
||||||
onToggleMetaPreview={toggleMetaPreview}
|
onToggleInfoMenu={toggleInfoMenu}
|
||||||
onMouseMove={onBarMouseMove}
|
onMouseMove={onBarMouseMove}
|
||||||
onMouseOver={onBarMouseMove}
|
onMouseOver={onBarMouseMove}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
subtitlesPickerOpen ?
|
subtitlesMenuOpen ?
|
||||||
<SubtitlesPicker
|
<SubtitlesMenu
|
||||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
tracks={videoState.subtitlesTracks}
|
tracks={videoState.subtitlesTracks}
|
||||||
selectedTrackId={videoState.selectedSubtitlesTrackId}
|
selectedTrackId={videoState.selectedSubtitlesTrackId}
|
||||||
|
|
@ -397,14 +421,17 @@ const Player = ({ urlParams }) => {
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{/* {
|
{
|
||||||
metaPreviewOpen ?
|
infoMenuOpen ?
|
||||||
<div className={classnames(styles['layer'], styles['menu-layer'])} onMouseDown={(event) => event.nativeEvent.metaPreviewClosePrevented = true}>
|
<InfoMenu
|
||||||
<div style={{ width: 300, height: 800, background: 'red' }} />
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
</div>
|
stream={info !== null ? info.stream : null}
|
||||||
|
addon={info !== null ? info.addon : null}
|
||||||
|
metaItem={info !== null ? info.metaItem : null}
|
||||||
|
/>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
} */}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
fill: @color-surface-light5;
|
fill: @color-surface-light5-90;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6,14 +6,14 @@ const DiscreteSelectInput = require('./DiscreteSelectInput');
|
||||||
const styles = require('./styles');
|
const styles = require('./styles');
|
||||||
|
|
||||||
const ORIGIN_PRIORITIES = {
|
const ORIGIN_PRIORITIES = {
|
||||||
'EMBEDDED': 1,
|
'EMBEDDED IN VIDEO': 1,
|
||||||
'Stream': 2
|
'EMBEDDED IN STREAM': 2
|
||||||
};
|
};
|
||||||
const LANGUAGE_PRIORITIES = {
|
const LANGUAGE_PRIORITIES = {
|
||||||
'eng': 1
|
'eng': 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubtitlesPicker = (props) => {
|
const SubtitlesMenu = (props) => {
|
||||||
const languages = React.useMemo(() => {
|
const languages = React.useMemo(() => {
|
||||||
return Array.isArray(props.tracks) ?
|
return Array.isArray(props.tracks) ?
|
||||||
props.tracks
|
props.tracks
|
||||||
|
|
@ -51,7 +51,7 @@ const SubtitlesPicker = (props) => {
|
||||||
[];
|
[];
|
||||||
}, [props.tracks, selectedLanguage]);
|
}, [props.tracks, selectedLanguage]);
|
||||||
const onMouseDown = React.useCallback((event) => {
|
const onMouseDown = React.useCallback((event) => {
|
||||||
event.nativeEvent.subtitlesPickerClosePrevented = true;
|
event.nativeEvent.subtitlesMenuClosePrevented = true;
|
||||||
}, []);
|
}, []);
|
||||||
const languageOnClick = React.useCallback((event) => {
|
const languageOnClick = React.useCallback((event) => {
|
||||||
const trackId = Array.isArray(props.tracks) ?
|
const trackId = Array.isArray(props.tracks) ?
|
||||||
|
|
@ -101,7 +101,7 @@ const SubtitlesPicker = (props) => {
|
||||||
}
|
}
|
||||||
}, [props.offset, props.onOffsetChanged]);
|
}, [props.offset, props.onOffsetChanged]);
|
||||||
return (
|
return (
|
||||||
<div className={classnames(props.className, styles['subtitles-picker-container'])} onMouseDown={onMouseDown}>
|
<div className={classnames(props.className, styles['subtitles-menu-container'])} onMouseDown={onMouseDown}>
|
||||||
<div className={styles['languages-container']}>
|
<div className={styles['languages-container']}>
|
||||||
<div className={styles['languages-header']}>Languages</div>
|
<div className={styles['languages-header']}>Languages</div>
|
||||||
<div className={styles['languages-list']}>
|
<div className={styles['languages-list']}>
|
||||||
|
|
@ -182,7 +182,7 @@ const SubtitlesPicker = (props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
SubtitlesPicker.propTypes = {
|
SubtitlesMenu.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
tracks: PropTypes.arrayOf(PropTypes.shape({
|
tracks: PropTypes.arrayOf(PropTypes.shape({
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
|
|
@ -202,4 +202,4 @@ SubtitlesPicker.propTypes = {
|
||||||
onOffsetChanged: PropTypes.func
|
onOffsetChanged: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = SubtitlesPicker;
|
module.exports = SubtitlesMenu;
|
||||||
3
src/routes/Player/SubtitlesMenu/index.js
Normal file
3
src/routes/Player/SubtitlesMenu/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
const SubtitlesMenu = require('./SubtitlesMenu');
|
||||||
|
|
||||||
|
module.exports = SubtitlesMenu;
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
@import (reference) '~stremio-colors/dist/less/stremio-colors.less';
|
@import (reference) '~stremio-colors/dist/less/stremio-colors.less';
|
||||||
|
|
||||||
.subtitles-picker-container {
|
.subtitles-menu-container {
|
||||||
height: 23rem;
|
height: 23rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
background-color: @color-background-dark1;
|
|
||||||
|
|
||||||
.languages-container, .variants-container, .subtitles-settings-container {
|
.languages-container, .variants-container, .subtitles-settings-container {
|
||||||
flex: none;
|
flex: none;
|
||||||
|
|
@ -49,7 +48,7 @@
|
||||||
height: 0.5rem;
|
height: 0.5rem;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
background-color: @color-accent3;
|
background-color: @color-accent3-90;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +99,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:global(.disabled) {
|
&:global(.disabled) {
|
||||||
color: @color-surface;
|
color: @color-surface-90;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
const SubtitlesPicker = require('./SubtitlesPicker');
|
|
||||||
|
|
||||||
module.exports = SubtitlesPicker;
|
|
||||||
|
|
@ -26,7 +26,7 @@ html:not(.active-slider-within) {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: @color-background-dark2;
|
background-color: @color-background-dark5;
|
||||||
|
|
||||||
.layer {
|
.layer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -104,6 +104,7 @@ html:not(.active-slider-within) {
|
||||||
bottom: 8rem;
|
bottom: 8rem;
|
||||||
max-height: calc(100% - 13.5rem);
|
max-height: calc(100% - 13.5rem);
|
||||||
max-width: calc(100% - 4rem);
|
max-width: calc(100% - 4rem);
|
||||||
|
background-color: @color-background-dark1;
|
||||||
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
box-shadow: 0 1.35rem 2.7rem @color-background-dark5-40,
|
||||||
0 1.1rem 0.85rem @color-background-dark5-20;
|
0 1.1rem 0.85rem @color-background-dark5-20;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
||||||
41
src/routes/Player/useInfo.js
Normal file
41
src/routes/Player/useInfo.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
const React = require('react');
|
||||||
|
|
||||||
|
const useInfo = (player, profile) => {
|
||||||
|
const info = React.useMemo(() => {
|
||||||
|
if (player.selected === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = player.selected.stream;
|
||||||
|
const addon = stream.addon;
|
||||||
|
const metaItem = player.meta_resource !== null && player.meta_resource.content.type === 'Ready' ?
|
||||||
|
player.meta_resource.content.content
|
||||||
|
:
|
||||||
|
null;
|
||||||
|
const video = metaItem !== null ?
|
||||||
|
metaItem.videos.reduce((result, video) => {
|
||||||
|
if (video.id === player.selected.video_id) {
|
||||||
|
return video;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, null)
|
||||||
|
:
|
||||||
|
null;
|
||||||
|
const streamTitle = typeof stream.title === 'string' ? stream.title : '';
|
||||||
|
const metaItemTitle = metaItem !== null ? metaItem.name : '';
|
||||||
|
const videoTitle = video !== null && typeof video.title === 'string' && video.title.length > 0 ? video.title : '';
|
||||||
|
const seriesInfo = video !== null && !isNaN(video.season) && !isNaN(video.episode) ? `${video.season}x${video.episode}` : '';
|
||||||
|
const title = metaItemTitle.length > 0 ?
|
||||||
|
metaItemTitle
|
||||||
|
.concat(videoTitle.length > 0 || seriesInfo.length > 0 ? ' -' : '')
|
||||||
|
.concat(videoTitle.length > 0 ? ` ${videoTitle}` : '')
|
||||||
|
.concat(seriesInfo.length > 0 ? ` (${seriesInfo})` : '')
|
||||||
|
:
|
||||||
|
streamTitle;
|
||||||
|
return { stream, addon, metaItem, title };
|
||||||
|
}, [player, profile]);
|
||||||
|
return info;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = useInfo;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const pako = require('pako');
|
const pako = require('pako');
|
||||||
|
const { useServices } = require('stremio/services');
|
||||||
const { useModelState } = require('stremio/common');
|
const { useModelState } = require('stremio/common');
|
||||||
|
|
||||||
const initPlayerState = () => ({
|
const initPlayerState = () => ({
|
||||||
|
|
@ -15,23 +16,13 @@ const mapPlayerStateWithCtx = (player, ctx) => {
|
||||||
{
|
{
|
||||||
stream: {
|
stream: {
|
||||||
...player.selected.stream,
|
...player.selected.stream,
|
||||||
subtitles: Array.isArray(player.selected.stream.subtitles) ?
|
addon: ctx.profile.addons.reduce((result, addon) => {
|
||||||
player.selected.stream.subtitles.map(({ url, lang }) => ({
|
if (player.selected.stream_resource_request !== null && addon.transportUrl === player.selected.stream_resource_request.base) {
|
||||||
url,
|
return addon;
|
||||||
lang,
|
}
|
||||||
origin: ctx.profile.addons.reduce((origin, addon) => {
|
|
||||||
if (player.selected.stream_resource_request !== null && addon.transportUrl === player.selected.stream_resource_request.base) {
|
|
||||||
return typeof addon.manifest.name === 'string' && addon.manifest.name.length > 0 ?
|
|
||||||
addon.manifest.name
|
|
||||||
:
|
|
||||||
addon.manifest.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return origin;
|
return result;
|
||||||
}, player.selected.stream_resource_request !== null ? player.selected.stream_resource_request.base : 'Stream')
|
}, null)
|
||||||
}))
|
|
||||||
:
|
|
||||||
[]
|
|
||||||
},
|
},
|
||||||
stream_resource_request: player.selected.stream_resource_request,
|
stream_resource_request: player.selected.stream_resource_request,
|
||||||
meta_resource_request: player.selected.meta_resource_request,
|
meta_resource_request: player.selected.meta_resource_request,
|
||||||
|
|
@ -40,48 +31,54 @@ const mapPlayerStateWithCtx = (player, ctx) => {
|
||||||
}
|
}
|
||||||
:
|
:
|
||||||
null;
|
null;
|
||||||
const meta_resource = player.meta_resource;
|
const meta_resource = player.meta_resource !== null && player.meta_resource.content.type === 'Ready' ?
|
||||||
|
{
|
||||||
|
request: player.meta_resource.request,
|
||||||
|
content: {
|
||||||
|
type: 'Ready',
|
||||||
|
content: {
|
||||||
|
...player.meta_resource.content.content,
|
||||||
|
released: new Date(
|
||||||
|
typeof player.meta_resource.content.content.released === 'string' ?
|
||||||
|
player.meta_resource.content.content.released
|
||||||
|
:
|
||||||
|
NaN
|
||||||
|
),
|
||||||
|
videos: player.meta_resource.content.content.videos.map((video) => ({
|
||||||
|
...video,
|
||||||
|
released: new Date(
|
||||||
|
typeof video.released === 'string' ?
|
||||||
|
video.released
|
||||||
|
:
|
||||||
|
NaN
|
||||||
|
),
|
||||||
|
// TODO add watched and progress
|
||||||
|
href: `#/metadetails/${player.meta_resource.content.content.type}/${player.meta_resource.content.content.id}/${video.id}`
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:
|
||||||
|
player.meta_resource;
|
||||||
const subtitles_resources = player.subtitles_resources.map((subtitles_resource) => {
|
const subtitles_resources = player.subtitles_resources.map((subtitles_resource) => {
|
||||||
const request = subtitles_resource.request;
|
const request = subtitles_resource.request;
|
||||||
const origin = ctx.profile.addons.reduce((origin, addon) => {
|
const addon = ctx.profile.addons.reduce((result, addon) => {
|
||||||
if (addon.transportUrl === subtitles_resource.request.base) {
|
if (addon.transportUrl === subtitles_resource.request.base) {
|
||||||
return typeof addon.manifest.name === 'string' && addon.manifest.name.length > 0 ?
|
return addon;
|
||||||
addon.manifest.name
|
|
||||||
:
|
|
||||||
addon.manifest.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return origin;
|
return result;
|
||||||
}, subtitles_resource.request.base);
|
}, null);
|
||||||
const content = subtitles_resource.content.type === 'Ready' ?
|
const content = subtitles_resource.content;
|
||||||
{
|
return { request, addon, content };
|
||||||
type: 'Ready',
|
|
||||||
content: subtitles_resource.content.content.map(({ url, lang }) => ({
|
|
||||||
url,
|
|
||||||
lang,
|
|
||||||
origin
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
:
|
|
||||||
subtitles_resource.content;
|
|
||||||
return {
|
|
||||||
request,
|
|
||||||
origin,
|
|
||||||
content
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
const next_video = player.next_video;
|
const next_video = player.next_video;
|
||||||
const lib_item = player.lib_item;
|
const lib_item = player.lib_item;
|
||||||
return {
|
return { selected, meta_resource, subtitles_resources, next_video, lib_item };
|
||||||
selected,
|
|
||||||
meta_resource,
|
|
||||||
subtitles_resources,
|
|
||||||
next_video,
|
|
||||||
lib_item
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const usePlayer = (urlParams) => {
|
const usePlayer = (urlParams) => {
|
||||||
|
const { core } = useServices();
|
||||||
const loadPlayerAction = React.useMemo(() => {
|
const loadPlayerAction = React.useMemo(() => {
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
|
|
@ -123,7 +120,10 @@ const usePlayer = (urlParams) => {
|
||||||
}
|
}
|
||||||
:
|
:
|
||||||
null,
|
null,
|
||||||
video_id: urlParams.videoId
|
video_id: typeof urlParams.videoId === 'string' ?
|
||||||
|
urlParams.videoId
|
||||||
|
:
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -133,12 +133,30 @@ const usePlayer = (urlParams) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [urlParams]);
|
}, [urlParams]);
|
||||||
return useModelState({
|
const updateLibraryItemState = React.useCallback((time, duration) => {
|
||||||
|
core.dispatch({
|
||||||
|
action: 'Player',
|
||||||
|
args: {
|
||||||
|
action: 'UpdateLibraryItemState',
|
||||||
|
args: { time, duration }
|
||||||
|
}
|
||||||
|
}, 'player');
|
||||||
|
}, []);
|
||||||
|
const pushToLibrary = React.useCallback(() => {
|
||||||
|
core.dispatch({
|
||||||
|
action: 'Player',
|
||||||
|
args: {
|
||||||
|
action: 'PushToLibrary'
|
||||||
|
}
|
||||||
|
}, 'player');
|
||||||
|
}, []);
|
||||||
|
const player = useModelState({
|
||||||
model: 'player',
|
model: 'player',
|
||||||
action: loadPlayerAction,
|
action: loadPlayerAction,
|
||||||
init: initPlayerState,
|
init: initPlayerState,
|
||||||
mapWithCtx: mapPlayerStateWithCtx
|
mapWithCtx: mapPlayerStateWithCtx
|
||||||
});
|
});
|
||||||
|
return [player, updateLibraryItemState, pushToLibrary];
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = usePlayer;
|
module.exports = usePlayer;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const { useProfile } = require('stremio/common');
|
|
||||||
const { useServices } = require('stremio/services');
|
const { useServices } = require('stremio/services');
|
||||||
|
|
||||||
const useSettings = () => {
|
const useSettings = (profile) => {
|
||||||
const { core } = useServices();
|
const { core } = useServices();
|
||||||
const profile = useProfile();
|
|
||||||
const updateSettings = React.useCallback((settings) => {
|
const updateSettings = React.useCallback((settings) => {
|
||||||
core.dispatch({
|
core.dispatch({
|
||||||
action: 'Ctx',
|
action: 'Ctx',
|
||||||
|
|
@ -16,7 +14,7 @@ const useSettings = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [profile.settings]);
|
}, [profile]);
|
||||||
return [profile.settings, updateSettings];
|
return [profile.settings, updateSettings];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ function HTMLSubtitles(options) {
|
||||||
track.url.length > 0 &&
|
track.url.length > 0 &&
|
||||||
typeof track.origin === 'string' &&
|
typeof track.origin === 'string' &&
|
||||||
track.origin.length > 0 &&
|
track.origin.length > 0 &&
|
||||||
track.origin !== 'EMBEDDED';
|
track.origin !== 'EMBEDDED IN VIDEO';
|
||||||
})
|
})
|
||||||
.map(function(track) {
|
.map(function(track) {
|
||||||
return Object.freeze(Object.assign({}, track, {
|
return Object.freeze(Object.assign({}, track, {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ function YouTubeVideo(options) {
|
||||||
.map(function(track) {
|
.map(function(track) {
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
id: track.languageCode,
|
id: track.languageCode,
|
||||||
origin: 'EMBEDDED',
|
origin: 'EMBEDDED IN VIDEO',
|
||||||
label: track.languageName
|
label: track.languageName
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -482,7 +482,7 @@ function YouTubeVideo(options) {
|
||||||
embeddedSubtitlesSelectedTrackId = null;
|
embeddedSubtitlesSelectedTrackId = null;
|
||||||
var tracks = getSubtitlesTracks();
|
var tracks = getSubtitlesTracks();
|
||||||
for (var i = 0; i < tracks.length; i++) {
|
for (var i = 0; i < tracks.length; i++) {
|
||||||
if (tracks[i].id === arguments[2] && tracks[i].origin === 'EMBEDDED') {
|
if (tracks[i].id === arguments[2] && tracks[i].origin === 'EMBEDDED IN VIDEO') {
|
||||||
embeddedSubtitlesSelectedTrackId = tracks[i].id;
|
embeddedSubtitlesSelectedTrackId = tracks[i].id;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue