basic info menu implemented

This commit is contained in:
nklhrstv 2020-04-02 18:17:14 +03:00
parent 19c747f019
commit 1ec803b06d
6 changed files with 83 additions and 50 deletions

View file

@ -15,7 +15,7 @@ const ControlBar = ({
volume, volume,
muted, muted,
subtitlesTracks, subtitlesTracks,
stream, info,
onPlayRequested, onPlayRequested,
onPauseRequested, onPauseRequested,
onMuteRequested, onMuteRequested,
@ -97,7 +97,7 @@ const ControlBar = ({
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}> <Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
<Icon className={styles['icon']} icon={'ic_network'} /> <Icon className={styles['icon']} icon={'ic_network'} />
</Button> </Button>
<Button className={classnames(styles['control-bar-button'], { 'disabled': typeof stream !== 'object' || stream === null })} tabIndex={-1} onMouseDown={onInfoButtonMouseDown} onClick={onInfoButtonClick}> <Button className={classnames(styles['control-bar-button'], { 'disabled': typeof info !== 'object' || info === null })} tabIndex={-1} onMouseDown={onInfoButtonMouseDown} onClick={onInfoButtonClick}>
<Icon className={styles['icon']} icon={'ic_info'} /> <Icon className={styles['icon']} icon={'ic_info'} />
</Button> </Button>
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}> <Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
@ -122,7 +122,7 @@ ControlBar.propTypes = {
volume: PropTypes.number, volume: PropTypes.number,
muted: PropTypes.bool, muted: PropTypes.bool,
subtitlesTracks: PropTypes.array, subtitlesTracks: PropTypes.array,
stream: PropTypes.object, info: PropTypes.object,
onPlayRequested: PropTypes.func, onPlayRequested: PropTypes.func,
onPauseRequested: PropTypes.func, onPauseRequested: PropTypes.func,
onMuteRequested: PropTypes.func, onMuteRequested: PropTypes.func,

View file

@ -1,22 +1,42 @@
const React = require('react'); const React = require('react');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const classnames = require('classnames'); const classnames = require('classnames');
const MetaPreview = require('stremio/common/MetaPreview'); const { MetaPreview, CONSTANTS } = require('stremio/common');
const styles = require('./styles'); const styles = require('./styles');
const InfoMenu = ({ className }) => { const InfoMenu = ({ className, ...props }) => {
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) => { const onMouseDown = React.useCallback((event) => {
event.nativeEvent.infoMenuClosePrevented = true; event.nativeEvent.infoMenuClosePrevented = true;
}, []); }, []);
return ( return (
<div className={classnames(className, styles['info-menu-container'])} onMouseDown={onMouseDown}> <div className={classnames(className, styles['info-menu-container'])} onMouseDown={onMouseDown}>
<MetaPreview name={'Info menu name'} /> <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> </div>
); );
}; };
InfoMenu.propTypes = { InfoMenu.propTypes = {
className: PropTypes.string, className: PropTypes.string,
metaItem: PropTypes.object
}; };
module.exports = InfoMenu; module.exports = InfoMenu;

View file

@ -1,3 +1,3 @@
.info-menu-container { .info-menu-container {
width: 30rem;
} }

View file

@ -4,52 +4,23 @@ 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, 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 InfoMenu = require('./InfoMenu'); const InfoMenu = require('./InfoMenu');
const SubtitlesMenu = require('./SubtitlesMenu'); 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 profile = useProfile();
const [player, updateLibraryItemState, pushToLibrary] = usePlayer(urlParams); const [player, updateLibraryItemState, pushToLibrary] = usePlayer(urlParams);
const [settings, updateSettings] = useSettings(); const [settings, updateSettings] = useSettings(profile);
const stream = React.useMemo(() => { const info = useInfo(player, profile);
return player.selected !== null ? player.selected.stream : null;
}, [player]);
const metaItem = React.useMemo(() => {
return player.meta_resource !== null && player.meta_resource.content.type === 'Ready' ? player.meta_resource.content.content : null;
}, [player]);
const video = React.useMemo(() => {
return metaItem !== null && player.selected !== null && typeof player.selected.video_id === 'string' ?
metaItem.videos.reduce((result, video) => {
if (video.id === player.selected.video_id) {
return video;
}
return result;
}, null)
:
null;
}, [player, metaItem]);
const title = React.useMemo(() => {
const streamTitle = stream !== null && 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}` : '';
if (metaItemTitle.length > 0) {
return metaItemTitle
.concat(videoTitle.length > 0 || seriesInfo.length > 0 ? ' -' : '')
.concat(videoTitle.length > 0 ? ` ${videoTitle}` : '')
.concat(seriesInfo.length > 0 ? ` (${seriesInfo})` : '');
} else {
return streamTitle;
}
}, [stream, metaItem, video]);
const routeFocused = useRouteFocused(); const routeFocused = useRouteFocused();
const toast = useToast(); const toast = useToast();
const [, , , toggleFullscreen] = useFullscreen(); const [, , , toggleFullscreen] = useFullscreen();
@ -275,10 +246,10 @@ const Player = ({ urlParams }) => {
} }
}, [videoState.subtitlesTracks]); }, [videoState.subtitlesTracks]);
React.useEffect(() => { React.useEffect(() => {
if (stream === null) { if (info === null) {
closeInfoMenu(); closeInfoMenu();
} }
}, [stream]); }, [info]);
React.useEffect(() => { React.useEffect(() => {
const intervalId = setInterval(pushToLibrary, 30000); const intervalId = setInterval(pushToLibrary, 30000);
return () => { return () => {
@ -337,7 +308,7 @@ const Player = ({ urlParams }) => {
} }
case 'KeyM': { case 'KeyM': {
closeSubtitlesMenu(); closeSubtitlesMenu();
if (stream !== null) { if (info !== null) {
toggleInfoMenu(); toggleInfoMenu();
} }
@ -356,7 +327,7 @@ const Player = ({ urlParams }) => {
return () => { return () => {
window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keydown', onKeyDown);
}; };
}, [routeFocused, subtitlesMenuOpen, infoMenuOpen, stream, videoState.paused, videoState.time, videoState.volume, videoState.subtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu]); }, [routeFocused, subtitlesMenuOpen, infoMenuOpen, info, videoState.paused, videoState.time, videoState.volume, videoState.subtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu]);
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
return () => { return () => {
setImmersedDebounced.cancel(); setImmersedDebounced.cancel();
@ -404,7 +375,7 @@ const Player = ({ urlParams }) => {
} }
<HorizontalNavBar <HorizontalNavBar
className={classnames(styles['layer'], styles['nav-bar-layer'])} className={classnames(styles['layer'], styles['nav-bar-layer'])}
title={title} title={info !== null ? info.title : ''}
backButton={true} backButton={true}
fullscreenButton={true} fullscreenButton={true}
onMouseMove={onBarMouseMove} onMouseMove={onBarMouseMove}
@ -418,7 +389,7 @@ const Player = ({ urlParams }) => {
volume={videoState.volume} volume={videoState.volume}
muted={videoState.muted} muted={videoState.muted}
subtitlesTracks={videoState.subtitlesTracks} subtitlesTracks={videoState.subtitlesTracks}
stream={stream} info={info}
onPlayRequested={onPlayRequested} onPlayRequested={onPlayRequested}
onPauseRequested={onPauseRequested} onPauseRequested={onPauseRequested}
onMuteRequested={onMuteRequested} onMuteRequested={onMuteRequested}
@ -454,6 +425,9 @@ const Player = ({ urlParams }) => {
infoMenuOpen ? infoMenuOpen ?
<InfoMenu <InfoMenu
className={classnames(styles['layer'], styles['menu-layer'])} className={classnames(styles['layer'], styles['menu-layer'])}
stream={info !== null ? info.stream : null}
addon={info !== null ? info.addon : null}
metaItem={info !== null ? info.metaItem : null}
/> />
: :
null null

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

View file

@ -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',