mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-19 13:52:12 +00:00
Merge pull request #311 from Stremio/feat/player-options-menu
feat(Player): add options menu
This commit is contained in:
commit
9a52f3cbc7
7 changed files with 184 additions and 17 deletions
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -12,7 +12,7 @@
|
|||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "5.0.1",
|
||||
"@stremio/stremio-core-web": "0.44.7",
|
||||
"@stremio/stremio-core-web": "0.44.8",
|
||||
"@stremio/stremio-icons": "4.0.0",
|
||||
"@stremio/stremio-video": "0.0.24",
|
||||
"a-color-picker": "1.2.1",
|
||||
|
|
@ -2699,9 +2699,9 @@
|
|||
"integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA=="
|
||||
},
|
||||
"node_modules/@stremio/stremio-core-web": {
|
||||
"version": "0.44.7",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.7.tgz",
|
||||
"integrity": "sha512-hkeYLfL1On4TMBHn87Onrp93aeRuTh4YXMKdDR1Vz5YikPOiPEq/JRoLLmmSSsFEdifs6Egu+A0qiggTttepOA==",
|
||||
"version": "0.44.8",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.8.tgz",
|
||||
"integrity": "sha512-c7ARfOxf0yy73b6sZauCLPUjpNYacF9tZXW7bxxg18IBGv7kXDBvLnk1rc54XKhJfHRgAPJuY5I1ijA0bM4nBQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.16.0"
|
||||
}
|
||||
|
|
@ -16716,9 +16716,9 @@
|
|||
"integrity": "sha512-Dt3PYmy1DZ473QNs99KYXVWQPHtpIl37VUY0+gCEvvuCqE1fRrZIJtZ9KbysUKonvO7WwdQDztgcW0iGoc1dEA=="
|
||||
},
|
||||
"@stremio/stremio-core-web": {
|
||||
"version": "0.44.7",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.7.tgz",
|
||||
"integrity": "sha512-hkeYLfL1On4TMBHn87Onrp93aeRuTh4YXMKdDR1Vz5YikPOiPEq/JRoLLmmSSsFEdifs6Egu+A0qiggTttepOA==",
|
||||
"version": "0.44.8",
|
||||
"resolved": "https://registry.npmjs.org/@stremio/stremio-core-web/-/stremio-core-web-0.44.8.tgz",
|
||||
"integrity": "sha512-c7ARfOxf0yy73b6sZauCLPUjpNYacF9tZXW7bxxg18IBGv7kXDBvLnk1rc54XKhJfHRgAPJuY5I1ijA0bM4nBQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.16.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"@babel/runtime": "7.16.0",
|
||||
"@sentry/browser": "6.13.3",
|
||||
"@stremio/stremio-colors": "5.0.1",
|
||||
"@stremio/stremio-core-web": "0.44.7",
|
||||
"@stremio/stremio-core-web": "0.44.8",
|
||||
"@stremio/stremio-icons": "4.0.0",
|
||||
"@stremio/stremio-video": "0.0.24",
|
||||
"a-color-picker": "1.2.1",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const ControlBar = ({
|
|||
onToggleInfoMenu,
|
||||
onToggleSpeedMenu,
|
||||
onToggleVideosMenu,
|
||||
onToggleOptionsMenu,
|
||||
...props
|
||||
}) => {
|
||||
const { chromecast } = useServices();
|
||||
|
|
@ -50,6 +51,9 @@ const ControlBar = ({
|
|||
const onVideosButtonMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.videosMenuClosePrevented = true;
|
||||
}, []);
|
||||
const onOptionsButtonMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.optionsMenuClosePrevented = true;
|
||||
}, []);
|
||||
const onPlayPauseButtonClick = React.useCallback(() => {
|
||||
if (paused) {
|
||||
if (typeof onPlayRequested === 'function') {
|
||||
|
|
@ -101,6 +105,11 @@ const ControlBar = ({
|
|||
onToggleVideosMenu();
|
||||
}
|
||||
}, [onToggleVideosMenu]);
|
||||
const onOptionsButtonClick = React.useCallback(() => {
|
||||
if (typeof onToggleOptionsMenu === 'function') {
|
||||
onToggleOptionsMenu();
|
||||
}
|
||||
}, [onToggleOptionsMenu]);
|
||||
const onChromecastButtonClick = React.useCallback(() => {
|
||||
chromecast.transport.requestSession();
|
||||
}, []);
|
||||
|
|
@ -178,6 +187,9 @@ const ControlBar = ({
|
|||
:
|
||||
null
|
||||
}
|
||||
<Button className={styles['control-bar-button']} tabIndex={-1} onMouseDown={onOptionsButtonMouseDown} onClick={onOptionsButtonClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_more'} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -205,7 +217,8 @@ ControlBar.propTypes = {
|
|||
onToggleSubtitlesMenu: PropTypes.func,
|
||||
onToggleInfoMenu: PropTypes.func,
|
||||
onToggleSpeedMenu: PropTypes.func,
|
||||
onToggleVideosMenu: PropTypes.func
|
||||
onToggleVideosMenu: PropTypes.func,
|
||||
onToggleOptionsMenu: PropTypes.func,
|
||||
};
|
||||
|
||||
module.exports = ControlBar;
|
||||
|
|
|
|||
92
src/routes/Player/OptionsMenu/OptionsMenu.js
Normal file
92
src/routes/Player/OptionsMenu/OptionsMenu.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { Button, useToast } = require('stremio/common');
|
||||
// const { useServices } = require('stremio/services');
|
||||
const styles = require('./styles');
|
||||
|
||||
const OptionsMenu = ({ className, stream }) => {
|
||||
// const { core } = useServices();
|
||||
const toast = useToast();
|
||||
const streamUrl = React.useMemo(() => {
|
||||
return stream !== null ?
|
||||
stream.deepLinks &&
|
||||
stream.deepLinks.externalPlayer &&
|
||||
typeof stream.deepLinks.externalPlayer.download === 'string' ?
|
||||
stream.deepLinks.externalPlayer.download
|
||||
:
|
||||
null
|
||||
:
|
||||
null;
|
||||
}, [stream]);
|
||||
const onCopyStreamButtonClick = React.useCallback(() => {
|
||||
if (streamUrl !== null) {
|
||||
navigator.clipboard.writeText(streamUrl)
|
||||
.then(() => {
|
||||
toast.show({
|
||||
type: 'success',
|
||||
title: 'Copied',
|
||||
message: 'Stream link was copied to your clipboard',
|
||||
timeout: 3000
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
toast.show({
|
||||
type: 'error',
|
||||
title: 'Error',
|
||||
message: `Failed to copy stream link: ${streamUrl}`,
|
||||
timeout: 3000
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [streamUrl]);
|
||||
const onDownloadVideoButtonClick = React.useCallback(() => {
|
||||
if (streamUrl !== null) {
|
||||
window.open(streamUrl);
|
||||
}
|
||||
}, [streamUrl]);
|
||||
// const onExternalPlayerButtonClick = React.useCallback(() => {
|
||||
// if (streamUrl !== null) {
|
||||
// core.transport.dispatch({
|
||||
// action: 'StreamingServer',
|
||||
// args: {
|
||||
// action: 'PlayOnDevice',
|
||||
// args: {
|
||||
// device: 'vlc',
|
||||
// source: streamUrl,
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }, [streamUrl]);
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.optionsMenuClosePrevented = true;
|
||||
}, []);
|
||||
return (
|
||||
<div className={classnames(className, styles['options-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<Button className={classnames(styles['option-container'], { 'disabled': stream === null })} disabled={stream === null} onClick={onCopyStreamButtonClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_link'} />
|
||||
<div className={styles['label']}>Copy Stream Link</div>
|
||||
</Button>
|
||||
<Button className={classnames(styles['option-container'], { 'disabled': stream === null })} disabled={stream === null}onClick={onDownloadVideoButtonClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_downloads'} />
|
||||
<div className={styles['label']}>Download Video</div>
|
||||
</Button>
|
||||
{/* <Button className={classnames(styles['option-container'], { 'disabled': stream === null })} disabled={stream === null} onClick={onExternalPlayerButtonClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_vlc'} />
|
||||
<div className={styles['label']}>Play in External Player</div>
|
||||
</Button> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
OptionsMenu.propTypes = {
|
||||
className: PropTypes.string,
|
||||
stream: PropTypes.object
|
||||
};
|
||||
|
||||
module.exports = OptionsMenu;
|
||||
5
src/routes/Player/OptionsMenu/index.js
Normal file
5
src/routes/Player/OptionsMenu/index.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const OptionsMenu = require('./OptionsMenu');
|
||||
|
||||
module.exports = OptionsMenu;
|
||||
37
src/routes/Player/OptionsMenu/styles.less
Normal file
37
src/routes/Player/OptionsMenu/styles.less
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||
|
||||
.options-menu-container {
|
||||
width: 15rem;
|
||||
|
||||
.option-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 4rem;
|
||||
|
||||
.icon {
|
||||
flex: none;
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
margin: 1.3rem;
|
||||
fill: @color-surface-light5-90;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: 1;
|
||||
max-height: 2.4em;
|
||||
font-weight: 400;
|
||||
color: @color-surface-light5-90;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-background-light2;
|
||||
}
|
||||
|
||||
&:global(.disabled) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ const BufferingLoader = require('./BufferingLoader');
|
|||
const ControlBar = require('./ControlBar');
|
||||
const NextVideoPopup = require('./NextVideoPopup');
|
||||
const InfoMenu = require('./InfoMenu');
|
||||
const OptionsMenu = require('./OptionsMenu');
|
||||
const VideosMenu = require('./VideosMenu');
|
||||
const SubtitlesMenu = require('./SubtitlesMenu');
|
||||
const SpeedMenu = require('./SpeedMenu');
|
||||
|
|
@ -40,6 +41,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
});
|
||||
const [immersed, setImmersed] = React.useState(true);
|
||||
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
|
||||
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
|
||||
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
|
||||
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
|
||||
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
||||
|
|
@ -215,6 +217,9 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
toggleFullscreen();
|
||||
}, [toggleFullscreen]);
|
||||
const onContainerMouseDown = React.useCallback((event) => {
|
||||
if (!event.nativeEvent.optionsMenuClosePrevented) {
|
||||
closeOptionsMenu();
|
||||
}
|
||||
if (!event.nativeEvent.subtitlesMenuClosePrevented) {
|
||||
closeSubtitlesMenu();
|
||||
}
|
||||
|
|
@ -439,7 +444,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
const onKeyDown = (event) => {
|
||||
switch (event.code) {
|
||||
case 'Space': {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen&& videoState.paused !== null) {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.paused !== null) {
|
||||
if (videoState.paused) {
|
||||
onPlayRequested();
|
||||
} else {
|
||||
|
|
@ -450,7 +455,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
break;
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.time !== null) {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.time !== null) {
|
||||
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
|
||||
onSeekRequested(videoState.time + (settings.seekTimeDuration * seekTimeMultiplier));
|
||||
}
|
||||
|
|
@ -458,7 +463,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
break;
|
||||
}
|
||||
case 'ArrowLeft': {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.time !== null) {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.time !== null) {
|
||||
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
|
||||
onSeekRequested(videoState.time - (settings.seekTimeDuration * seekTimeMultiplier));
|
||||
}
|
||||
|
|
@ -466,20 +471,21 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
break;
|
||||
}
|
||||
case 'ArrowUp': {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.volume !== null) {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.volume !== null) {
|
||||
onVolumeChangeRequested(videoState.volume + 5);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'ArrowDown': {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.volume !== null) {
|
||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.volume !== null) {
|
||||
onVolumeChangeRequested(videoState.volume - 5);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'KeyS': {
|
||||
closeOptionsMenu();
|
||||
closeInfoMenu();
|
||||
closeSpeedMenu();
|
||||
closeVideosMenu();
|
||||
|
|
@ -492,6 +498,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
break;
|
||||
}
|
||||
case 'KeyI': {
|
||||
closeOptionsMenu();
|
||||
closeSubtitlesMenu();
|
||||
closeSpeedMenu();
|
||||
closeVideosMenu();
|
||||
|
|
@ -502,6 +509,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
break;
|
||||
}
|
||||
case 'KeyR': {
|
||||
closeOptionsMenu();
|
||||
closeInfoMenu();
|
||||
closeSubtitlesMenu();
|
||||
closeVideosMenu();
|
||||
|
|
@ -512,6 +520,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
break;
|
||||
}
|
||||
case 'KeyV': {
|
||||
closeOptionsMenu();
|
||||
closeInfoMenu();
|
||||
closeSubtitlesMenu();
|
||||
closeSpeedMenu();
|
||||
|
|
@ -522,6 +531,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
break;
|
||||
}
|
||||
case 'Escape': {
|
||||
closeOptionsMenu();
|
||||
closeSubtitlesMenu();
|
||||
closeInfoMenu();
|
||||
closeSpeedMenu();
|
||||
|
|
@ -537,7 +547,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}, [player.metaItem, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videosMenuOpen, speedMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, videoState.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu]);
|
||||
}, [player.metaItem, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videosMenuOpen, speedMenuOpen, optionsMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, videoState.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu]);
|
||||
React.useLayoutEffect(() => {
|
||||
return () => {
|
||||
setImmersedDebounced.cancel();
|
||||
|
|
@ -546,7 +556,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
};
|
||||
}, []);
|
||||
return (
|
||||
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && !casting && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && !videosMenuOpen && !nextVideoPopupOpen })}
|
||||
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && !casting && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && !videosMenuOpen && !nextVideoPopupOpen && !optionsMenuOpen })}
|
||||
onMouseDown={onContainerMouseDown}
|
||||
onMouseMove={onContainerMouseMove}
|
||||
onMouseOver={onContainerMouseMove}
|
||||
|
|
@ -591,7 +601,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
null
|
||||
}
|
||||
{
|
||||
subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen ?
|
||||
subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen || optionsMenuOpen ?
|
||||
<div className={styles['layer']} />
|
||||
:
|
||||
null
|
||||
|
|
@ -622,6 +632,7 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
onUnmuteRequested={onUnmuteRequested}
|
||||
onVolumeChangeRequested={onVolumeChangeRequested}
|
||||
onSeekRequested={onSeekRequested}
|
||||
onToggleOptionsMenu={toggleOptionsMenu}
|
||||
onToggleSubtitlesMenu={toggleSubtitlesMenu}
|
||||
onToggleInfoMenu={toggleInfoMenu}
|
||||
onToggleSpeedMenu={toggleSpeedMenu}
|
||||
|
|
@ -699,6 +710,15 @@ const Player = ({ urlParams, queryParams }) => {
|
|||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
optionsMenuOpen ?
|
||||
<OptionsMenu
|
||||
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||
stream={player.selected.stream}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue