mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-21 11:42:05 +00:00
Merge pull request #313 from Stremio/feat/player-videos-menu
feat(Player): implement videos menu
This commit is contained in:
commit
8de2b494dc
5 changed files with 111 additions and 6 deletions
|
|
@ -29,6 +29,7 @@ const ControlBar = ({
|
||||||
onSeekRequested,
|
onSeekRequested,
|
||||||
onToggleSubtitlesMenu,
|
onToggleSubtitlesMenu,
|
||||||
onToggleInfoMenu,
|
onToggleInfoMenu,
|
||||||
|
onToggleVideosMenu,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const { chromecast } = useServices();
|
const { chromecast } = useServices();
|
||||||
|
|
@ -40,6 +41,9 @@ const ControlBar = ({
|
||||||
const onInfoButtonMouseDown = React.useCallback((event) => {
|
const onInfoButtonMouseDown = React.useCallback((event) => {
|
||||||
event.nativeEvent.infoMenuClosePrevented = true;
|
event.nativeEvent.infoMenuClosePrevented = true;
|
||||||
}, []);
|
}, []);
|
||||||
|
const onVideosButtonMouseDown = React.useCallback((event) => {
|
||||||
|
event.nativeEvent.videosMenuClosePrevented = true;
|
||||||
|
}, []);
|
||||||
const onPlayPauseButtonClick = React.useCallback(() => {
|
const onPlayPauseButtonClick = React.useCallback(() => {
|
||||||
if (paused) {
|
if (paused) {
|
||||||
if (typeof onPlayRequested === 'function') {
|
if (typeof onPlayRequested === 'function') {
|
||||||
|
|
@ -72,6 +76,11 @@ const ControlBar = ({
|
||||||
onToggleInfoMenu();
|
onToggleInfoMenu();
|
||||||
}
|
}
|
||||||
}, [onToggleInfoMenu]);
|
}, [onToggleInfoMenu]);
|
||||||
|
const onVideosButtonClick = React.useCallback(() => {
|
||||||
|
if (typeof onToggleVideosMenu === 'function') {
|
||||||
|
onToggleVideosMenu();
|
||||||
|
}
|
||||||
|
}, [onToggleVideosMenu]);
|
||||||
const onChromecastButtonClick = React.useCallback(() => {
|
const onChromecastButtonClick = React.useCallback(() => {
|
||||||
chromecast.transport.requestSession();
|
chromecast.transport.requestSession();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -130,9 +139,14 @@ const ControlBar = ({
|
||||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': (!Array.isArray(subtitlesTracks) || subtitlesTracks.length === 0) && (!Array.isArray(audioTracks) || audioTracks.length === 0) })} tabIndex={-1} onMouseDown={onSubtitlesButtonMouseDown} onClick={onSubtitlesButtonClick}>
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': (!Array.isArray(subtitlesTracks) || subtitlesTracks.length === 0) && (!Array.isArray(audioTracks) || audioTracks.length === 0) })} tabIndex={-1} onMouseDown={onSubtitlesButtonMouseDown} onClick={onSubtitlesButtonClick}>
|
||||||
<Icon className={styles['icon']} icon={'ic_sub'} />
|
<Icon className={styles['icon']} icon={'ic_sub'} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
{
|
||||||
<Icon className={styles['icon']} icon={'ic_videos'} />
|
metaItem?.content?.videos?.length > 0 ?
|
||||||
</Button>
|
<Button className={styles['control-bar-button']} tabIndex={-1} onMouseDown={onVideosButtonMouseDown} onClick={onVideosButtonClick}>
|
||||||
|
<Icon className={styles['icon']} icon={'ic_videos'} />
|
||||||
|
</Button>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -156,7 +170,8 @@ ControlBar.propTypes = {
|
||||||
onVolumeChangeRequested: PropTypes.func,
|
onVolumeChangeRequested: PropTypes.func,
|
||||||
onSeekRequested: PropTypes.func,
|
onSeekRequested: PropTypes.func,
|
||||||
onToggleSubtitlesMenu: PropTypes.func,
|
onToggleSubtitlesMenu: PropTypes.func,
|
||||||
onToggleInfoMenu: PropTypes.func
|
onToggleInfoMenu: PropTypes.func,
|
||||||
|
onToggleVideosMenu: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = ControlBar;
|
module.exports = ControlBar;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const Icon = require('@stremio/stremio-icons/dom');
|
||||||
const BufferingLoader = require('./BufferingLoader');
|
const BufferingLoader = require('./BufferingLoader');
|
||||||
const ControlBar = require('./ControlBar');
|
const ControlBar = require('./ControlBar');
|
||||||
const InfoMenu = require('./InfoMenu');
|
const InfoMenu = require('./InfoMenu');
|
||||||
|
const VideosMenu = require('./VideosMenu');
|
||||||
const SubtitlesMenu = require('./SubtitlesMenu');
|
const SubtitlesMenu = require('./SubtitlesMenu');
|
||||||
const Video = require('./Video');
|
const Video = require('./Video');
|
||||||
const usePlayer = require('./usePlayer');
|
const usePlayer = require('./usePlayer');
|
||||||
|
|
@ -38,6 +39,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||||
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||||
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
|
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
|
||||||
|
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = 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 }),
|
||||||
|
|
@ -198,6 +200,9 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
if (!event.nativeEvent.infoMenuClosePrevented) {
|
if (!event.nativeEvent.infoMenuClosePrevented) {
|
||||||
closeInfoMenu();
|
closeInfoMenu();
|
||||||
}
|
}
|
||||||
|
if (!event.nativeEvent.videosMenuClosePrevented) {
|
||||||
|
closeVideosMenu();
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const onContainerMouseMove = React.useCallback((event) => {
|
const onContainerMouseMove = React.useCallback((event) => {
|
||||||
setImmersed(false);
|
setImmersed(false);
|
||||||
|
|
@ -318,6 +323,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (player.metaItem === null || player.metaItem.type !== 'Ready') {
|
if (player.metaItem === null || player.metaItem.type !== 'Ready') {
|
||||||
closeInfoMenu();
|
closeInfoMenu();
|
||||||
|
closeVideosMenu();
|
||||||
}
|
}
|
||||||
}, [player.metaItem]);
|
}, [player.metaItem]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -402,6 +408,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
case 'KeyS': {
|
case 'KeyS': {
|
||||||
closeInfoMenu();
|
closeInfoMenu();
|
||||||
|
closeVideosMenu();
|
||||||
if ((Array.isArray(videoState.subtitlesTracks) && videoState.subtitlesTracks.length > 0) ||
|
if ((Array.isArray(videoState.subtitlesTracks) && videoState.subtitlesTracks.length > 0) ||
|
||||||
(Array.isArray(videoState.extraSubtitlesTracks) && videoState.extraSubtitlesTracks.length > 0) ||
|
(Array.isArray(videoState.extraSubtitlesTracks) && videoState.extraSubtitlesTracks.length > 0) ||
|
||||||
(Array.isArray(videoState.audioTracks) && videoState.audioTracks.length > 0)) {
|
(Array.isArray(videoState.audioTracks) && videoState.audioTracks.length > 0)) {
|
||||||
|
|
@ -412,15 +419,26 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
case 'KeyI': {
|
case 'KeyI': {
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
|
closeVideosMenu();
|
||||||
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
||||||
toggleInfoMenu();
|
toggleInfoMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'KeyV': {
|
||||||
|
closeInfoMenu();
|
||||||
|
closeSubtitlesMenu();
|
||||||
|
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
||||||
|
toggleVideosMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'Escape': {
|
case 'Escape': {
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
closeInfoMenu();
|
closeInfoMenu();
|
||||||
|
closeVideosMenu();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -431,7 +449,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', onKeyDown);
|
window.removeEventListener('keydown', onKeyDown);
|
||||||
};
|
};
|
||||||
}, [player.metaItem, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu]);
|
}, [player.metaItem, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu]);
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
setImmersedDebounced.cancel();
|
setImmersedDebounced.cancel();
|
||||||
|
|
@ -440,7 +458,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && !casting && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen })}
|
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && !casting && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen })}
|
||||||
onMouseDown={onContainerMouseDown}
|
onMouseDown={onContainerMouseDown}
|
||||||
onMouseMove={onContainerMouseMove}
|
onMouseMove={onContainerMouseMove}
|
||||||
onMouseOver={onContainerMouseMove}
|
onMouseOver={onContainerMouseMove}
|
||||||
|
|
@ -516,6 +534,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
onSeekRequested={onSeekRequested}
|
onSeekRequested={onSeekRequested}
|
||||||
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
||||||
onToggleInfoMenu={toggleInfoMenu}
|
onToggleInfoMenu={toggleInfoMenu}
|
||||||
|
onToggleVideosMenu={toggleVideosMenu}
|
||||||
onMouseMove={onBarMouseMove}
|
onMouseMove={onBarMouseMove}
|
||||||
onMouseOver={onBarMouseMove}
|
onMouseOver={onBarMouseMove}
|
||||||
/>
|
/>
|
||||||
|
|
@ -557,6 +576,16 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
videosMenuOpen ?
|
||||||
|
<VideosMenu
|
||||||
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
|
metaItem={player.metaItem !== null && player.metaItem.type === 'Ready' ? player.metaItem.content : null}
|
||||||
|
seriesInfo={player.seriesInfo}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
51
src/routes/Player/VideosMenu/VideosMenu.js
Normal file
51
src/routes/Player/VideosMenu/VideosMenu.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright (C) 2017-2022 Smart code 203358507
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
const PropTypes = require('prop-types');
|
||||||
|
const classnames = require('classnames');
|
||||||
|
const Video = require('../../MetaDetails/VideosList/Video');
|
||||||
|
const styles = require('./styles');
|
||||||
|
|
||||||
|
const VideosMenu = ({ className, metaItem, seriesInfo }) => {
|
||||||
|
const onMouseDown = React.useCallback((event) => {
|
||||||
|
event.nativeEvent.videosMenuClosePrevented = true;
|
||||||
|
}, []);
|
||||||
|
const videos = React.useMemo(() => {
|
||||||
|
return seriesInfo && typeof seriesInfo.season === 'number' && Array.isArray(metaItem.videos) ?
|
||||||
|
metaItem.videos.filter(({ season }) => season === seriesInfo.season)
|
||||||
|
:
|
||||||
|
metaItem.videos;
|
||||||
|
}, [metaItem, seriesInfo]);
|
||||||
|
return (
|
||||||
|
<div className={classnames(className, styles['videos-menu-container'])} onMouseDown={onMouseDown}>
|
||||||
|
{
|
||||||
|
videos.map((video, index) => (
|
||||||
|
<Video
|
||||||
|
key={index}
|
||||||
|
id={video.id}
|
||||||
|
title={video.title}
|
||||||
|
thumbnail={video.thumbnail}
|
||||||
|
episode={video.episode}
|
||||||
|
released={video.released}
|
||||||
|
upcoming={video.upcoming}
|
||||||
|
watched={video.watched}
|
||||||
|
progress={video.progress}
|
||||||
|
deepLinks={video.deepLinks}
|
||||||
|
scheduled={video.scheduled}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
VideosMenu.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
metaItem: PropTypes.object,
|
||||||
|
seriesInfo: PropTypes.shape({
|
||||||
|
season: PropTypes.number,
|
||||||
|
episode: PropTypes.number,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = VideosMenu;
|
||||||
5
src/routes/Player/VideosMenu/index.js
Normal file
5
src/routes/Player/VideosMenu/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright (C) 2017-2022 Smart code 203358507
|
||||||
|
|
||||||
|
const VideosMenu = require('./VideosMenu');
|
||||||
|
|
||||||
|
module.exports = VideosMenu;
|
||||||
5
src/routes/Player/VideosMenu/styles.less
Normal file
5
src/routes/Player/VideosMenu/styles.less
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright (C) 2017-2022 Smart code 203358507
|
||||||
|
|
||||||
|
.videos-menu-container {
|
||||||
|
width: 30rem;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue