mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-27 01:12:59 +00:00
feat: add statistics menu on player
This commit is contained in:
parent
b1f9abb0c9
commit
e542e5d550
6 changed files with 293 additions and 14 deletions
|
|
@ -24,6 +24,8 @@ const ControlBar = ({
|
||||||
audioTracks,
|
audioTracks,
|
||||||
metaItem,
|
metaItem,
|
||||||
nextVideo,
|
nextVideo,
|
||||||
|
stream,
|
||||||
|
statistics,
|
||||||
onPlayRequested,
|
onPlayRequested,
|
||||||
onPauseRequested,
|
onPauseRequested,
|
||||||
onMuteRequested,
|
onMuteRequested,
|
||||||
|
|
@ -35,6 +37,7 @@ const ControlBar = ({
|
||||||
onToggleSpeedMenu,
|
onToggleSpeedMenu,
|
||||||
onToggleVideosMenu,
|
onToggleVideosMenu,
|
||||||
onToggleOptionsMenu,
|
onToggleOptionsMenu,
|
||||||
|
onToggleStatisticsMenu,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const { chromecast } = useServices();
|
const { chromecast } = useServices();
|
||||||
|
|
@ -55,6 +58,9 @@ const ControlBar = ({
|
||||||
const onOptionsButtonMouseDown = React.useCallback((event) => {
|
const onOptionsButtonMouseDown = React.useCallback((event) => {
|
||||||
event.nativeEvent.optionsMenuClosePrevented = true;
|
event.nativeEvent.optionsMenuClosePrevented = true;
|
||||||
}, []);
|
}, []);
|
||||||
|
const onStatisticsButtonMouseDown = React.useCallback((event) => {
|
||||||
|
event.nativeEvent.statisticsMenuClosePrevented = true;
|
||||||
|
}, []);
|
||||||
const onPlayPauseButtonClick = React.useCallback(() => {
|
const onPlayPauseButtonClick = React.useCallback(() => {
|
||||||
if (paused) {
|
if (paused) {
|
||||||
if (typeof onPlayRequested === 'function') {
|
if (typeof onPlayRequested === 'function') {
|
||||||
|
|
@ -111,6 +117,11 @@ const ControlBar = ({
|
||||||
onToggleOptionsMenu();
|
onToggleOptionsMenu();
|
||||||
}
|
}
|
||||||
}, [onToggleOptionsMenu]);
|
}, [onToggleOptionsMenu]);
|
||||||
|
const onStatisticsButtonClick = React.useCallback(() => {
|
||||||
|
if (typeof onToggleStatisticsMenu === 'function') {
|
||||||
|
onToggleStatisticsMenu();
|
||||||
|
}
|
||||||
|
}, [onToggleStatisticsMenu]);
|
||||||
const onChromecastButtonClick = React.useCallback(() => {
|
const onChromecastButtonClick = React.useCallback(() => {
|
||||||
chromecast.transport.requestSession();
|
chromecast.transport.requestSession();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -165,12 +176,12 @@ const ControlBar = ({
|
||||||
<Icon className={styles['icon']} icon={'ic_more'} />
|
<Icon className={styles['icon']} icon={'ic_more'} />
|
||||||
</Button>
|
</Button>
|
||||||
<div className={classnames(styles['control-bar-buttons-menu-container'], { 'open': buttonsMenuOpen })}>
|
<div className={classnames(styles['control-bar-buttons-menu-container'], { 'open': buttonsMenuOpen })}>
|
||||||
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': statistics === null || statistics.type === 'Err' || stream === null || typeof stream.infoHash !== 'string' || typeof stream.fileIdx !== 'number' })} tabIndex={-1} onMouseDown={onStatisticsButtonMouseDown} onClick={onStatisticsButtonClick}>
|
||||||
|
<Icon className={styles['icon']} icon={'ic_network'} />
|
||||||
|
</Button>
|
||||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': playbackSpeed === null })} tabIndex={-1} onMouseDown={onSpeedButtonMouseDown} onClick={onSpeedButtonClick}>
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': playbackSpeed === null })} tabIndex={-1} onMouseDown={onSpeedButtonMouseDown} onClick={onSpeedButtonClick}>
|
||||||
<Icon className={styles['icon']} icon={'ic_speedometer'} />
|
<Icon className={styles['icon']} icon={'ic_speedometer'} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={classnames(styles['control-bar-button'], 'disabled')} tabIndex={-1}>
|
|
||||||
<Icon className={styles['icon']} icon={'ic_network'} />
|
|
||||||
</Button>
|
|
||||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': metaItem === null || metaItem.type !== 'Ready' })} tabIndex={-1} onMouseDown={onInfoButtonMouseDown} onClick={onInfoButtonClick}>
|
<Button className={classnames(styles['control-bar-button'], { 'disabled': metaItem === null || metaItem.type !== 'Ready' })} tabIndex={-1} onMouseDown={onInfoButtonMouseDown} onClick={onInfoButtonClick}>
|
||||||
<Icon className={styles['icon']} icon={'ic_info'} />
|
<Icon className={styles['icon']} icon={'ic_info'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -209,6 +220,8 @@ ControlBar.propTypes = {
|
||||||
audioTracks: PropTypes.array,
|
audioTracks: PropTypes.array,
|
||||||
metaItem: PropTypes.object,
|
metaItem: PropTypes.object,
|
||||||
nextVideo: PropTypes.object,
|
nextVideo: PropTypes.object,
|
||||||
|
stream: PropTypes.object,
|
||||||
|
statistics: PropTypes.object,
|
||||||
onPlayRequested: PropTypes.func,
|
onPlayRequested: PropTypes.func,
|
||||||
onPauseRequested: PropTypes.func,
|
onPauseRequested: PropTypes.func,
|
||||||
onMuteRequested: PropTypes.func,
|
onMuteRequested: PropTypes.func,
|
||||||
|
|
@ -220,6 +233,7 @@ ControlBar.propTypes = {
|
||||||
onToggleSpeedMenu: PropTypes.func,
|
onToggleSpeedMenu: PropTypes.func,
|
||||||
onToggleVideosMenu: PropTypes.func,
|
onToggleVideosMenu: PropTypes.func,
|
||||||
onToggleOptionsMenu: PropTypes.func,
|
onToggleOptionsMenu: PropTypes.func,
|
||||||
|
onToggleStatisticsMenu: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = ControlBar;
|
module.exports = ControlBar;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,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 NextVideoPopup = require('./NextVideoPopup');
|
const NextVideoPopup = require('./NextVideoPopup');
|
||||||
|
const StatisticsMenu = require('./StatisticsMenu');
|
||||||
const InfoMenu = require('./InfoMenu');
|
const InfoMenu = require('./InfoMenu');
|
||||||
const OptionsMenu = require('./OptionsMenu');
|
const OptionsMenu = require('./OptionsMenu');
|
||||||
const VideosMenu = require('./VideosMenu');
|
const VideosMenu = require('./VideosMenu');
|
||||||
|
|
@ -81,6 +82,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false);
|
||||||
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false);
|
const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false);
|
||||||
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false);
|
||||||
|
const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false);
|
||||||
const nextVideoPopupDismissed = React.useRef(false);
|
const nextVideoPopupDismissed = React.useRef(false);
|
||||||
const defaultSubtitlesSelected = React.useRef(false);
|
const defaultSubtitlesSelected = React.useRef(false);
|
||||||
const defaultAudioTrackSelected = React.useRef(false);
|
const defaultAudioTrackSelected = React.useRef(false);
|
||||||
|
|
@ -234,6 +236,9 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
if (!event.nativeEvent.videosMenuClosePrevented) {
|
if (!event.nativeEvent.videosMenuClosePrevented) {
|
||||||
closeVideosMenu();
|
closeVideosMenu();
|
||||||
}
|
}
|
||||||
|
if (!event.nativeEvent.statisticsMenuClosePrevented) {
|
||||||
|
closeStatisticsMenu();
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const onContainerMouseMove = React.useCallback((event) => {
|
const onContainerMouseMove = React.useCallback((event) => {
|
||||||
setImmersed(false);
|
setImmersed(false);
|
||||||
|
|
@ -356,6 +361,26 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [player.nextVideo, videoState.time, videoState.duration]);
|
}, [player.nextVideo, videoState.time, videoState.duration]);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (player.selected && player.selected.stream && typeof player.selected.stream.infoHash === 'string' && typeof player.selected.stream.fileIdx === 'number') {
|
||||||
|
const { infoHash, fileIdx } = player.selected.stream;
|
||||||
|
const getStatistics = () => {
|
||||||
|
core.transport.dispatch({
|
||||||
|
action: 'StreamingServer',
|
||||||
|
args: {
|
||||||
|
action: 'GetStatistics',
|
||||||
|
args: {
|
||||||
|
infoHash,
|
||||||
|
fileIdx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
getStatistics();
|
||||||
|
const statisticsInterval = setInterval(getStatistics, 5000);
|
||||||
|
return () => clearInterval(statisticsInterval);
|
||||||
|
}
|
||||||
|
}, [player.selected]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!defaultSubtitlesSelected.current) {
|
if (!defaultSubtitlesSelected.current) {
|
||||||
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang);
|
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang);
|
||||||
|
|
@ -445,7 +470,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event) => {
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
case 'Space': {
|
case 'Space': {
|
||||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.paused !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && !statisticsMenuOpen && videoState.paused !== null) {
|
||||||
if (videoState.paused) {
|
if (videoState.paused) {
|
||||||
onPlayRequested();
|
onPlayRequested();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -456,7 +481,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowRight': {
|
case 'ArrowRight': {
|
||||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.time !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && !statisticsMenuOpen && videoState.time !== null) {
|
||||||
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
|
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
|
||||||
onSeekRequested(videoState.time + (settings.seekTimeDuration * seekTimeMultiplier));
|
onSeekRequested(videoState.time + (settings.seekTimeDuration * seekTimeMultiplier));
|
||||||
}
|
}
|
||||||
|
|
@ -464,7 +489,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowLeft': {
|
case 'ArrowLeft': {
|
||||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.time !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && !statisticsMenuOpen && videoState.time !== null) {
|
||||||
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
|
const seekTimeMultiplier = event.shiftKey ? 3 : 1;
|
||||||
onSeekRequested(videoState.time - (settings.seekTimeDuration * seekTimeMultiplier));
|
onSeekRequested(videoState.time - (settings.seekTimeDuration * seekTimeMultiplier));
|
||||||
}
|
}
|
||||||
|
|
@ -472,14 +497,14 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowUp': {
|
case 'ArrowUp': {
|
||||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.volume !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && !statisticsMenuOpen && videoState.volume !== null) {
|
||||||
onVolumeChangeRequested(videoState.volume + 5);
|
onVolumeChangeRequested(videoState.volume + 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ArrowDown': {
|
case 'ArrowDown': {
|
||||||
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && videoState.volume !== null) {
|
if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && !optionsMenuOpen && !statisticsMenuOpen && videoState.volume !== null) {
|
||||||
onVolumeChangeRequested(videoState.volume - 5);
|
onVolumeChangeRequested(videoState.volume - 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -490,6 +515,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
closeInfoMenu();
|
closeInfoMenu();
|
||||||
closeSpeedMenu();
|
closeSpeedMenu();
|
||||||
closeVideosMenu();
|
closeVideosMenu();
|
||||||
|
closeStatisticsMenu();
|
||||||
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)) {
|
||||||
|
|
@ -503,6 +529,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
closeSpeedMenu();
|
closeSpeedMenu();
|
||||||
closeVideosMenu();
|
closeVideosMenu();
|
||||||
|
closeStatisticsMenu();
|
||||||
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
||||||
toggleInfoMenu();
|
toggleInfoMenu();
|
||||||
}
|
}
|
||||||
|
|
@ -514,6 +541,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
closeInfoMenu();
|
closeInfoMenu();
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
closeVideosMenu();
|
closeVideosMenu();
|
||||||
|
closeStatisticsMenu();
|
||||||
if (videoState.playbackSpeed !== null) {
|
if (videoState.playbackSpeed !== null) {
|
||||||
toggleSpeedMenu();
|
toggleSpeedMenu();
|
||||||
}
|
}
|
||||||
|
|
@ -525,18 +553,32 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
closeInfoMenu();
|
closeInfoMenu();
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
closeSpeedMenu();
|
closeSpeedMenu();
|
||||||
|
closeStatisticsMenu();
|
||||||
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
if (player.metaItem !== null && player.metaItem.type === 'Ready') {
|
||||||
toggleVideosMenu();
|
toggleVideosMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'KeyD': {
|
||||||
|
closeOptionsMenu();
|
||||||
|
closeInfoMenu();
|
||||||
|
closeSubtitlesMenu();
|
||||||
|
closeSpeedMenu();
|
||||||
|
closeVideosMenu();
|
||||||
|
if (streamingServer.statistics !== null && streamingServer.statistics.type !== 'Err' && player.selected && typeof player.selected.stream.infoHash === 'string' && typeof player.selected.stream.fileIdx === 'number') {
|
||||||
|
toggleStatisticsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'Escape': {
|
case 'Escape': {
|
||||||
closeOptionsMenu();
|
closeOptionsMenu();
|
||||||
closeSubtitlesMenu();
|
closeSubtitlesMenu();
|
||||||
closeInfoMenu();
|
closeInfoMenu();
|
||||||
closeSpeedMenu();
|
closeSpeedMenu();
|
||||||
closeVideosMenu();
|
closeVideosMenu();
|
||||||
|
closeStatisticsMenu();
|
||||||
onDismissNextVideoPopup();
|
onDismissNextVideoPopup();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -548,7 +590,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', onKeyDown);
|
window.removeEventListener('keydown', onKeyDown);
|
||||||
};
|
};
|
||||||
}, [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]);
|
}, [player.metaItem, player.selected, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videosMenuOpen, speedMenuOpen, optionsMenuOpen, statisticsMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, videoState.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu, toggleStatisticsMenu]);
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
setImmersedDebounced.cancel();
|
setImmersedDebounced.cancel();
|
||||||
|
|
@ -557,7 +599,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && !casting && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && !videosMenuOpen && !nextVideoPopupOpen && !optionsMenuOpen })}
|
<div className={classnames(styles['player-container'], { [styles['immersed']]: immersed && !casting && videoState.paused !== null && !videoState.paused && !subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && !videosMenuOpen && !nextVideoPopupOpen && !optionsMenuOpen && !statisticsMenuOpen })}
|
||||||
onMouseDown={onContainerMouseDown}
|
onMouseDown={onContainerMouseDown}
|
||||||
onMouseMove={onContainerMouseMove}
|
onMouseMove={onContainerMouseMove}
|
||||||
onMouseOver={onContainerMouseMove}
|
onMouseOver={onContainerMouseMove}
|
||||||
|
|
@ -608,7 +650,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen || optionsMenuOpen ?
|
subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen || optionsMenuOpen || statisticsMenuOpen ?
|
||||||
<div className={styles['layer']} />
|
<div className={styles['layer']} />
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
|
|
@ -633,6 +675,8 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
audioTracks={videoState.audioTracks}
|
audioTracks={videoState.audioTracks}
|
||||||
metaItem={player.metaItem}
|
metaItem={player.metaItem}
|
||||||
nextVideo={player.nextVideo}
|
nextVideo={player.nextVideo}
|
||||||
|
stream={player.selected !== null ? player.selected.stream : null}
|
||||||
|
statistics={streamingServer.statistics}
|
||||||
onPlayRequested={onPlayRequested}
|
onPlayRequested={onPlayRequested}
|
||||||
onPauseRequested={onPauseRequested}
|
onPauseRequested={onPauseRequested}
|
||||||
onMuteRequested={onMuteRequested}
|
onMuteRequested={onMuteRequested}
|
||||||
|
|
@ -644,6 +688,7 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
onToggleInfoMenu={toggleInfoMenu}
|
onToggleInfoMenu={toggleInfoMenu}
|
||||||
onToggleSpeedMenu={toggleSpeedMenu}
|
onToggleSpeedMenu={toggleSpeedMenu}
|
||||||
onToggleVideosMenu={toggleVideosMenu}
|
onToggleVideosMenu={toggleVideosMenu}
|
||||||
|
onToggleStatisticsMenu={toggleStatisticsMenu}
|
||||||
onMouseMove={onBarMouseMove}
|
onMouseMove={onBarMouseMove}
|
||||||
onMouseOver={onBarMouseMove}
|
onMouseOver={onBarMouseMove}
|
||||||
/>
|
/>
|
||||||
|
|
@ -659,6 +704,16 @@ const Player = ({ urlParams, queryParams }) => {
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
statisticsMenuOpen ?
|
||||||
|
<StatisticsMenu
|
||||||
|
className={classnames(styles['layer'], styles['menu-layer'])}
|
||||||
|
stream={player.selected !== null ? player.selected.stream : null}
|
||||||
|
statistics={streamingServer.statistics}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
{
|
{
|
||||||
subtitlesMenuOpen ?
|
subtitlesMenuOpen ?
|
||||||
<SubtitlesMenu
|
<SubtitlesMenu
|
||||||
|
|
|
||||||
79
src/routes/Player/StatisticsMenu/StatisticsMenu.js
Normal file
79
src/routes/Player/StatisticsMenu/StatisticsMenu.js
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright (C) 2017-2023 Smart code 203358507
|
||||||
|
|
||||||
|
const React = require('react');
|
||||||
|
const classNames = require('classnames');
|
||||||
|
const PropTypes = require('prop-types');
|
||||||
|
const styles = require('./styles.less');
|
||||||
|
|
||||||
|
const StatisticsMenu = ({ className, stream, statistics }) => {
|
||||||
|
const peers = React.useMemo(() => {
|
||||||
|
return statistics.type === 'Ready' && statistics.content?.peers ?
|
||||||
|
statistics.content.peers
|
||||||
|
:
|
||||||
|
0;
|
||||||
|
}, [statistics]);
|
||||||
|
|
||||||
|
const speed = React.useMemo(() => {
|
||||||
|
return statistics.type === 'Ready' && statistics.content?.downloadSpeed ?
|
||||||
|
(statistics.content.downloadSpeed / 1000 / 1000).toFixed(2)
|
||||||
|
:
|
||||||
|
0;
|
||||||
|
}, [statistics]);
|
||||||
|
|
||||||
|
const completed = React.useMemo(() => {
|
||||||
|
return statistics.type === 'Ready' && statistics.content?.streamProgress ?
|
||||||
|
(statistics.content.streamProgress * 100).toFixed(2)
|
||||||
|
:
|
||||||
|
0;
|
||||||
|
}, [statistics]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(className, styles['statistics-menu-container'])}>
|
||||||
|
<div className={styles['title']}>
|
||||||
|
Statistics
|
||||||
|
</div>
|
||||||
|
<div className={styles['stats']}>
|
||||||
|
<div className={styles['stat']}>
|
||||||
|
<div className={styles['label']}>
|
||||||
|
Peers
|
||||||
|
</div>
|
||||||
|
<div className={styles['value']}>
|
||||||
|
{ peers }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles['stat']}>
|
||||||
|
<div className={styles['label']}>
|
||||||
|
Speed
|
||||||
|
</div>
|
||||||
|
<div className={styles['value']}>
|
||||||
|
{ speed } MB/s
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles['stat']}>
|
||||||
|
<div className={styles['label']}>
|
||||||
|
Completed
|
||||||
|
</div>
|
||||||
|
<div className={styles['value']}>
|
||||||
|
{ completed } %
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles['info-hash']}>
|
||||||
|
<div className={styles['label']}>
|
||||||
|
Info Hash
|
||||||
|
</div>
|
||||||
|
<div className={styles['value']}>
|
||||||
|
{ stream.infoHash }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StatisticsMenu.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
stream: PropTypes.object,
|
||||||
|
statistics: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = StatisticsMenu;
|
||||||
4
src/routes/Player/StatisticsMenu/index.js
Normal file
4
src/routes/Player/StatisticsMenu/index.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// Copyright (C) 2017-2023 Smart code 203358507
|
||||||
|
|
||||||
|
const StatisticsMenu = require('./StatisticsMenu');
|
||||||
|
module.exports = StatisticsMenu;
|
||||||
52
src/routes/Player/StatisticsMenu/styles.less
Normal file
52
src/routes/Player/StatisticsMenu/styles.less
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (C) 2017-2023 Smart code 203358507
|
||||||
|
|
||||||
|
@import (reference) '~@stremio/stremio-colors/less/stremio-colors.less';
|
||||||
|
|
||||||
|
.statistics-menu-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
width: 30rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
flex: none;
|
||||||
|
font-weight: 600;
|
||||||
|
color: @color-surface-light5-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
flex: none;
|
||||||
|
font-weight: 500;
|
||||||
|
color: @color-surface-light5-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
flex: none;
|
||||||
|
font-weight: 500;
|
||||||
|
color: @color-surface-light5-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-hash {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/types/models/StremingServer.d.ts
vendored
81
src/types/models/StremingServer.d.ts
vendored
|
|
@ -25,11 +25,86 @@ type StreamingServerSettings = {
|
||||||
serverVersion: string,
|
serverVersion: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SFile = {
|
||||||
|
name: string,
|
||||||
|
path: string,
|
||||||
|
length: number,
|
||||||
|
offset: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Source = {
|
||||||
|
last_started: string,
|
||||||
|
numFound: number,
|
||||||
|
numFoundUniq: number,
|
||||||
|
numRequests: number,
|
||||||
|
url: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Growler = {
|
||||||
|
flood: number,
|
||||||
|
pulse: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeerSearch = {
|
||||||
|
max: number,
|
||||||
|
min: number,
|
||||||
|
sources: string[],
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwarmCap = {
|
||||||
|
maxSpeed: number,
|
||||||
|
minPeers: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
connections: number,
|
||||||
|
dht: boolean,
|
||||||
|
growler: Growler,
|
||||||
|
handshakeTimeout: number,
|
||||||
|
path: string,
|
||||||
|
peerSearch: PeerSearch,
|
||||||
|
swarmCap: SwarmCap,
|
||||||
|
timeout: number,
|
||||||
|
tracker: boolean,
|
||||||
|
virtual: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Statistics = {
|
||||||
|
name: string,
|
||||||
|
infoHash: string,
|
||||||
|
files: SFile[],
|
||||||
|
sources: Source[],
|
||||||
|
opts: Options,
|
||||||
|
downloadSpeed: number,
|
||||||
|
uploadSpeed: number,
|
||||||
|
downloaded: number,
|
||||||
|
uploaded: number,
|
||||||
|
unchoked: number,
|
||||||
|
peers: number,
|
||||||
|
queued: number,
|
||||||
|
unique: number,
|
||||||
|
connectionTries: number,
|
||||||
|
peerSearchRunning: boolean,
|
||||||
|
streamLen: number,
|
||||||
|
streamName: string,
|
||||||
|
streamProgress: number,
|
||||||
|
swarmConnections: number,
|
||||||
|
swarmPaused: boolean,
|
||||||
|
swarmSize: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Selected = {
|
||||||
|
transportUrl: string,
|
||||||
|
statistics: {
|
||||||
|
infoHash: string,
|
||||||
|
fileIdx: number,
|
||||||
|
} | null
|
||||||
|
};
|
||||||
|
|
||||||
type StreamingServer = {
|
type StreamingServer = {
|
||||||
baseUrl: Loadable<string> | null,
|
baseUrl: Loadable<string> | null,
|
||||||
selected: {
|
selected: Selected | null,
|
||||||
transportUrl: string,
|
|
||||||
} | null,
|
|
||||||
settings: Loadable<StreamingServerSettings> | null,
|
settings: Loadable<StreamingServerSettings> | null,
|
||||||
torrent: [string, Loadable<Torrent>] | null,
|
torrent: [string, Loadable<Torrent>] | null,
|
||||||
|
statistics: Loadable<Statistics> | null,
|
||||||
};
|
};
|
||||||
Loading…
Reference in a new issue