feat(Player): add options menu

This commit is contained in:
Tim 2022-10-31 18:22:55 +01:00
parent 92fa3d53a5
commit 3ff7a67fe5
5 changed files with 161 additions and 2 deletions

View file

@ -29,6 +29,7 @@ const ControlBar = ({
onSeekRequested,
onToggleSubtitlesMenu,
onToggleInfoMenu,
onToggleOptionsMenu,
...props
}) => {
const { chromecast } = useServices();
@ -40,6 +41,9 @@ const ControlBar = ({
const onInfoButtonMouseDown = React.useCallback((event) => {
event.nativeEvent.infoMenuClosePrevented = true;
}, []);
const onOptionsButtonMouseDown = React.useCallback((event) => {
event.nativeEvent.optionsMenuClosePrevented = true;
}, []);
const onPlayPauseButtonClick = React.useCallback(() => {
if (paused) {
if (typeof onPlayRequested === 'function') {
@ -72,6 +76,11 @@ const ControlBar = ({
onToggleInfoMenu();
}
}, [onToggleInfoMenu]);
const onOptionsButtonClick = React.useCallback(() => {
if (typeof onToggleOptionsMenu === 'function') {
onToggleOptionsMenu();
}
}, [onToggleOptionsMenu]);
const onChromecastButtonClick = React.useCallback(() => {
chromecast.transport.requestSession();
}, []);
@ -133,6 +142,9 @@ const ControlBar = ({
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
<Icon className={styles['icon']} icon={'ic_videos'} />
</Button>
<Button className={styles['control-bar-button']} tabIndex={-1} onMouseDown={onOptionsButtonMouseDown} onClick={onOptionsButtonClick}>
<Icon className={styles['icon']} icon={'ic_more'} />
</Button>
</div>
</div>
</div>
@ -156,7 +168,8 @@ ControlBar.propTypes = {
onVolumeChangeRequested: PropTypes.func,
onSeekRequested: PropTypes.func,
onToggleSubtitlesMenu: PropTypes.func,
onToggleInfoMenu: PropTypes.func
onToggleInfoMenu: PropTypes.func,
onToggleOptionsMenu: PropTypes.func
};
module.exports = ControlBar;

View file

@ -0,0 +1,86 @@
// 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, useStreamingServer, useToast } = require('stremio/common');
const styles = require('./styles');
const OptionsMenu = ({ className, stream }) => {
const streamingServer = useStreamingServer();
const toast = useToast();
const streamUrl = React.useMemo(() => {
return stream !== null ?
typeof stream.url === 'string' ?
stream.url
:
typeof stream.infoHash === 'string' &&
typeof stream.fileIdx === 'number' &&
typeof streamingServer.selected === 'object' &&
typeof streamingServer.selected.transportUrl === 'string' ?
`${streamingServer.selected.transportUrl}${stream.infoHash}/${stream.fileIdx}`
:
null
:
null;
}, [stream, streamingServer]);
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. Try to enable your browser permission.',
timeout: 3000
});
});
}
}, [streamUrl]);
const onDownloadVideoButtonClick = React.useCallback(() => {
if (streamUrl !== null) {
window.open(streamUrl);
}
}, [streamUrl]);
const onExternalPlayerButtonClick = React.useCallback(() => {
if (streamUrl !== null) {
window.open(`vlc://${encodeURIComponent(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;

View file

@ -0,0 +1,5 @@
// Copyright (C) 2017-2022 Smart code 203358507
const OptionsMenu = require('./OptionsMenu');
module.exports = OptionsMenu;

View 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;
}
}
}

View file

@ -11,6 +11,7 @@ const Icon = require('@stremio/stremio-icons/dom');
const BufferingLoader = require('./BufferingLoader');
const ControlBar = require('./ControlBar');
const InfoMenu = require('./InfoMenu');
const OptionsMenu = require('./OptionsMenu');
const SubtitlesMenu = require('./SubtitlesMenu');
const Video = require('./Video');
const usePlayer = require('./usePlayer');
@ -38,6 +39,7 @@ const Player = ({ urlParams, queryParams }) => {
const setImmersedDebounced = React.useCallback(debounce(setImmersed, 3000), []);
const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false);
const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false);
const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false);
const [error, setError] = React.useState(null);
const [videoState, setVideoState] = React.useReducer(
(videoState, nextVideoState) => ({ ...videoState, ...nextVideoState }),
@ -198,6 +200,9 @@ const Player = ({ urlParams, queryParams }) => {
if (!event.nativeEvent.infoMenuClosePrevented) {
closeInfoMenu();
}
if (!event.nativeEvent.optionsMenuClosePrevented) {
closeOptionsMenu();
}
}, []);
const onContainerMouseMove = React.useCallback((event) => {
setImmersed(false);
@ -399,6 +404,7 @@ const Player = ({ urlParams, queryParams }) => {
}
case 'KeyS': {
closeInfoMenu();
closeOptionsMenu();
if ((Array.isArray(videoState.subtitlesTracks) && videoState.subtitlesTracks.length > 0) ||
(Array.isArray(videoState.extraSubtitlesTracks) && videoState.extraSubtitlesTracks.length > 0) ||
(Array.isArray(videoState.audioTracks) && videoState.audioTracks.length > 0)) {
@ -409,6 +415,7 @@ const Player = ({ urlParams, queryParams }) => {
}
case 'KeyI': {
closeSubtitlesMenu();
closeOptionsMenu();
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
toggleInfoMenu();
}
@ -418,6 +425,7 @@ const Player = ({ urlParams, queryParams }) => {
case 'Escape': {
closeSubtitlesMenu();
closeInfoMenu();
closeOptionsMenu();
break;
}
}
@ -437,7 +445,7 @@ const Player = ({ urlParams, queryParams }) => {
};
}, []);
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 && !optionsMenuOpen })}
onMouseDown={onContainerMouseDown}
onMouseMove={onContainerMouseMove}
onMouseOver={onContainerMouseMove}
@ -513,6 +521,7 @@ const Player = ({ urlParams, queryParams }) => {
onSeekRequested={onSeekRequested}
onToggleSubtitlesMenu={toggleSubtitlesMenu}
onToggleInfoMenu={toggleInfoMenu}
onToggleOptionsMenu={toggleOptionsMenu}
onMouseMove={onBarMouseMove}
onMouseOver={onBarMouseMove}
/>
@ -554,6 +563,15 @@ const Player = ({ urlParams, queryParams }) => {
:
null
}
{
optionsMenuOpen ?
<OptionsMenu
className={classnames(styles['layer'], styles['menu-layer'])}
stream={videoState.stream}
/>
:
null
}
</div>
);
};