diff --git a/src/routes/Player/ControlBar/ControlBar.js b/src/routes/Player/ControlBar/ControlBar.js index a68a542bf..505ce982f 100644 --- a/src/routes/Player/ControlBar/ControlBar.js +++ b/src/routes/Player/ControlBar/ControlBar.js @@ -35,9 +35,9 @@ const ControlBar = ({ onVolumeChangeRequested, onSeekRequested, onToggleSubtitlesMenu, - onToggleInfoMenu, + // onToggleInfoMenu, onToggleSpeedMenu, - onToggleVideosMenu, + onToggleSideDrawer, onToggleOptionsMenu, onToggleStatisticsMenu, ...props @@ -48,9 +48,9 @@ const ControlBar = ({ const onSubtitlesButtonMouseDown = React.useCallback((event) => { event.nativeEvent.subtitlesMenuClosePrevented = true; }, []); - const onInfoButtonMouseDown = React.useCallback((event) => { - event.nativeEvent.infoMenuClosePrevented = true; - }, []); + // const onInfoButtonMouseDown = React.useCallback((event) => { + // event.nativeEvent.infoMenuClosePrevented = true; + // }, []); const onSpeedButtonMouseDown = React.useCallback((event) => { event.nativeEvent.speedMenuClosePrevented = true; }, []); @@ -151,9 +151,9 @@ const ControlBar = ({ - + */} @@ -162,7 +162,7 @@ const ControlBar = ({ { metaItem?.content?.videos?.length > 0 ? - : @@ -202,7 +202,7 @@ ControlBar.propTypes = { onToggleSubtitlesMenu: PropTypes.func, onToggleInfoMenu: PropTypes.func, onToggleSpeedMenu: PropTypes.func, - onToggleVideosMenu: PropTypes.func, + onToggleSideDrawer: PropTypes.func, onToggleOptionsMenu: PropTypes.func, onToggleStatisticsMenu: PropTypes.func, }; diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 5ace96f77..1a6b238c0 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -15,9 +15,7 @@ const Error = require('./Error'); const ControlBar = require('./ControlBar'); const NextVideoPopup = require('./NextVideoPopup'); const StatisticsMenu = require('./StatisticsMenu'); -const InfoMenu = require('./InfoMenu'); const OptionsMenu = require('./OptionsMenu'); -const VideosMenu = require('./VideosMenu'); const SubtitlesMenu = require('./SubtitlesMenu'); const SpeedMenu = require('./SpeedMenu'); const usePlayer = require('./usePlayer'); @@ -26,6 +24,8 @@ const useStatistics = require('./useStatistics'); const useVideo = require('./useVideo'); const styles = require('./styles'); const Video = require('./Video'); +const { default: SideDrawer } = require('./SideDrawer/SideDrawer'); +const { default: Icon } = require('@stremio/stremio-icons/react'); const Player = ({ urlParams, queryParams }) => { const { t } = useTranslation(); @@ -54,27 +54,24 @@ const Player = ({ urlParams, queryParams }) => { const [optionsMenuOpen, , closeOptionsMenu, toggleOptionsMenu] = useBinaryState(false); const [subtitlesMenuOpen, , closeSubtitlesMenu, toggleSubtitlesMenu] = useBinaryState(false); - const [infoMenuOpen, , closeInfoMenu, toggleInfoMenu] = useBinaryState(false); const [speedMenuOpen, , closeSpeedMenu, toggleSpeedMenu] = useBinaryState(false); - const [videosMenuOpen, , closeVideosMenu, toggleVideosMenu] = useBinaryState(false); const [statisticsMenuOpen, , closeStatisticsMenu, toggleStatisticsMenu] = useBinaryState(false); const [nextVideoPopupOpen, openNextVideoPopup, closeNextVideoPopup] = useBinaryState(false); + const [sideDrawerOpen, openSideDrawer, closeSideDrawer, toggleSideDrawer] = useBinaryState(false); const menusOpen = React.useMemo(() => { - return optionsMenuOpen || subtitlesMenuOpen || infoMenuOpen || speedMenuOpen || videosMenuOpen || statisticsMenuOpen; - }, [optionsMenuOpen, subtitlesMenuOpen, infoMenuOpen, speedMenuOpen, videosMenuOpen, statisticsMenuOpen]); + return optionsMenuOpen || subtitlesMenuOpen || speedMenuOpen || statisticsMenuOpen; + }, [optionsMenuOpen, subtitlesMenuOpen, speedMenuOpen, statisticsMenuOpen]); const closeMenus = React.useCallback(() => { closeOptionsMenu(); closeSubtitlesMenu(); - closeInfoMenu(); closeSpeedMenu(); - closeVideosMenu(); closeStatisticsMenu(); }, []); const overlayHidden = React.useMemo(() => { - return immersed && !casting && video.state.paused !== null && !video.state.paused && !menusOpen && !nextVideoPopupOpen; + return immersed && !casting && video.state.paused !== null && !video.state.paused && !menusOpen && !nextVideoPopupOpen && !sideDrawerOpen; }, [immersed, casting, video.state.paused, menusOpen, nextVideoPopupOpen]); const nextVideoPopupDismissed = React.useRef(false); @@ -237,15 +234,9 @@ const Player = ({ urlParams, queryParams }) => { if (!event.nativeEvent.subtitlesMenuClosePrevented) { closeSubtitlesMenu(); } - if (!event.nativeEvent.infoMenuClosePrevented) { - closeInfoMenu(); - } if (!event.nativeEvent.speedMenuClosePrevented) { closeSpeedMenu(); } - if (!event.nativeEvent.videosMenuClosePrevented) { - closeVideosMenu(); - } if (!event.nativeEvent.statisticsMenuClosePrevented) { closeStatisticsMenu(); } @@ -412,19 +403,19 @@ const Player = ({ urlParams, queryParams }) => { } }, [video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks]); - React.useEffect(() => { - if (player.metaItem === null || player.metaItem.type !== 'Ready') { - closeInfoMenu(); - closeVideosMenu(); - } - }, [player.metaItem]); - React.useEffect(() => { if (video.state.playbackSpeed === null) { closeSpeedMenu(); } }, [video.state.playbackSpeed]); + React.useEffect(() => { + if (sideDrawerOpen) { + closeMenus(); + setImmersed(true); + } + }, [sideDrawerOpen]); + React.useEffect(() => { const toastFilter = (item) => item?.dataset?.type === 'CoreEvent'; toast.addFilter(toastFilter); @@ -521,7 +512,7 @@ const Player = ({ urlParams, queryParams }) => { case 'KeyI': { closeMenus(); if (player.metaItem !== null && player.metaItem.type === 'Ready') { - toggleInfoMenu(); + toggleSideDrawer(); } break; @@ -534,14 +525,6 @@ const Player = ({ urlParams, queryParams }) => { break; } - case 'KeyV': { - closeMenus(); - if (player.metaItem !== null && player.metaItem.type === 'Ready' && player.metaItem?.content?.videos?.length > 0) { - toggleVideosMenu(); - } - - break; - } case 'KeyD': { closeMenus(); if (streamingServer.statistics !== null && streamingServer.statistics.type !== 'Err' && player.selected && typeof player.selected.stream.infoHash === 'string' && typeof player.selected.stream.fileIdx === 'number') { @@ -563,11 +546,11 @@ const Player = ({ urlParams, queryParams }) => { }; const onWheel = ({ deltaY }) => { if (deltaY > 0) { - if (!menusOpen && video.state.volume !== null) { + if (!menusOpen && !sideDrawerOpen && video.state.volume !== null) { onVolumeChangeRequested(video.state.volume - 5); } } else { - if (!menusOpen && video.state.volume !== null) { + if (!menusOpen && !sideDrawerOpen && video.state.volume !== null) { onVolumeChangeRequested(video.state.volume + 5); } } @@ -582,7 +565,7 @@ const Player = ({ urlParams, queryParams }) => { window.removeEventListener('keyup', onKeyUp); window.removeEventListener('wheel', onWheel); }; - }, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu, toggleStatisticsMenu]); + }, [player.metaItem, player.selected, streamingServer.statistics, settings.seekTimeDuration, settings.seekShortTimeDuration, routeFocused, menusOpen, nextVideoPopupOpen, video.state.paused, video.state.time, video.state.volume, video.state.audioTracks, video.state.subtitlesTracks, video.state.extraSubtitlesTracks, video.state.playbackSpeed, toggleSubtitlesMenu, toggleStatisticsMenu, toggleSideDrawer]); React.useEffect(() => { video.events.on('error', onError); @@ -691,10 +674,9 @@ const Player = ({ urlParams, queryParams }) => { onSeekRequested={onSeekRequested} onToggleOptionsMenu={toggleOptionsMenu} onToggleSubtitlesMenu={toggleSubtitlesMenu} - onToggleInfoMenu={toggleInfoMenu} onToggleSpeedMenu={toggleSpeedMenu} - onToggleVideosMenu={toggleVideosMenu} onToggleStatisticsMenu={toggleStatisticsMenu} + onToggleSideDrawer={toggleSideDrawer} onMouseMove={onBarMouseMove} onMouseOver={onBarMouseMove} /> @@ -719,6 +701,22 @@ const Player = ({ urlParams, queryParams }) => { : null } + { + player.metaItem !== null && player.metaItem.type === 'Ready' ? + <> +
+ +
+ + + : null + } { subtitlesMenuOpen ? { : null } - { - infoMenuOpen ? - - : - null - } { speedMenuOpen ? { : null } - { - videosMenuOpen ? - - : - null - } { optionsMenuOpen ? void; + sideDrawerOpen: boolean; +}; + +const SideDrawer = ({ seriesInfo, className, closeSideBar, sideDrawerOpen, ...props }: Props) => { + const [season, setSeason] = React.useState(seriesInfo?.season); + const metaItem = React.useMemo(() => { + return props.metaItem !== null && Array.isArray(props.metaItem.videos) && seriesInfo ? + { + ...props.metaItem, + links: props.metaItem.links.filter(({ category }) => category === CONSTANTS.SHARE_LINK_CATEGORY) + } + : + props.metaItem; + }, [props.metaItem]); + const videos = React.useMemo(() => { + return props.metaItem && Array.isArray(props.metaItem.videos) ? + props.metaItem.videos.filter((video) => video.season === season) + : + props.metaItem.videos; + }, [props.metaItem, season]); + const seasons = React.useMemo(() => { + return props.metaItem && props.metaItem.videos + .map(({ season }) => season) + .filter((season, index, seasons) => { + return season !== null && season !== undefined && + !isNaN(season) && + typeof season === 'number' && + seasons.indexOf(season) === index; + }) + .sort((a, b) => (a || Number.MAX_SAFE_INTEGER) - (b || Number.MAX_SAFE_INTEGER)); + }, [props.metaItem.videos]); + + const seasonOnSelect = React.useCallback((event: { value: string }) => { + setSeason(parseInt(event.value)); + }, []); + + return ( + <> +
+
+
+ { + metaItem !== null ? + + : + null + } +
+ { + videos !== null && seriesInfo ? + <> + +
+ {videos.map((video, index) => ( +
+ + : null + } + +
+ + ); +}; + +export default SideDrawer; diff --git a/src/routes/Player/styles.less b/src/routes/Player/styles.less index 8bff42fe1..9f48bbc91 100644 --- a/src/routes/Player/styles.less +++ b/src/routes/Player/styles.less @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 @import (reference) '~@stremio/stremio-colors/less/stremio-colors.less'; +@import (reference) '~stremio/common/screen-sizes.less'; :import('~stremio/common/Slider/styles.less') { active-slider-within: active-slider-within; @@ -112,5 +113,85 @@ html:not(.active-slider-within) { backdrop-filter: blur(15px); overflow: auto; } + + &.side-drawer-button { + right: -4rem; + top: 50%; + left: initial; + bottom: initial; + height: 12.5rem; + width: 7.5rem; + transform: translateY(-50%); + display: flex; + justify-content: center; + align-items: center; + background-color: var(--modal-background-color); + cursor: pointer; + border-top-left-radius: 50%; + border-bottom-left-radius: 50%; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + + .icon { + width: 2.5rem; + height: 2.5rem; + color: var(--primary-foreground-color); + opacity: 0.6; + margin-right: 4rem; + transition: all 0.3s ease-in-out; + + } + + &.open { + display: none; + } + + &:hover { + .icon { + opacity: 1; + } + } + } + + &.side-drawer-layer { + top: 0; + right: 0; + left: initial; + bottom: initial; + max-width: 30%; + height: 100vh; + border-top-left-radius: var(--border-radius); + border-bottom-left-radius: var(--border-radius); + background-color: var(--modal-background-color); + box-shadow: 0 1.35rem 2.7rem var(--color-background-dark5-40), + 0 1.1rem 0.85rem var(--color-background-dark5-20); + backdrop-filter: blur(15px); + overflow: visible; + padding: 1rem; + z-index: 1; + transition: transform 0.3s ease-in-out; + transform: translateX(100%); + + &.open { + transform: translateX(0); + } + + @media screen and (max-width: @small) { + max-width: 40%; + } + + @media screen and (max-width: @xsmall) { + max-width: 50%; + } + + @media screen and (max-width: @xxsmall) { + max-width: 60%; + } + + @media screen and (max-width: @minimum) { + max-width: 70%; + } + } } } \ No newline at end of file diff --git a/src/types/MetaItem.d.ts b/src/types/MetaItem.d.ts index 0d692a580..56e7db3c7 100644 --- a/src/types/MetaItem.d.ts +++ b/src/types/MetaItem.d.ts @@ -15,7 +15,7 @@ type MetaItemPreview = { posterShape: PosterShape, releaseInfo: string | null, runtime: string | null, - released: string | null, + released: Date | null | undefined, trailerStreams: TrailerStream[], links: Link[], behaviorHints: BehaviorHints, diff --git a/src/types/Video.d.ts b/src/types/Video.d.ts index 92bc704f5..7ed3d0fac 100644 --- a/src/types/Video.d.ts +++ b/src/types/Video.d.ts @@ -14,4 +14,9 @@ type Video = { episode?: number, streams: Stream[], trailerStreams: TrailerStream[], + watched: boolean, + progress: number, + upcoming: boolean, + deepLinks: VideoDeepLinks, + scheduled: boolean, };