From ca52ea9ff9fea536bcaadbd73c916ae53ee6e622 Mon Sep 17 00:00:00 2001 From: IrohDW Date: Thu, 24 Oct 2024 15:55:27 -0600 Subject: [PATCH 1/2] Videoplayer data is stored in a context. Mobile and PC Video Controls are split into different components. --- .../VideoPlayer/Components/LoadingVideo.tsx | 25 +-- .../VideoPlayer/Components/MobileControls.tsx | 107 +++++++++++++ .../VideoPlayer/Components/VideoContext.ts | 28 ++++ .../Components/VideoControls-State.ts | 79 ++++----- .../VideoPlayer/Components/VideoControls.tsx | 148 ++++------------- .../common/VideoPlayer/VideoPlayer-State.ts | 54 ++++--- .../common/VideoPlayer/VideoPlayer.tsx | 151 ++++++++---------- .../VideoContent/VideoActionsBar.tsx | 2 +- .../VideoContent/VideoContent-State.ts | 4 +- .../VideoContent/VideoContent.tsx | 2 +- 10 files changed, 317 insertions(+), 283 deletions(-) create mode 100644 src/components/common/VideoPlayer/Components/MobileControls.tsx create mode 100644 src/components/common/VideoPlayer/Components/VideoContext.ts diff --git a/src/components/common/VideoPlayer/Components/LoadingVideo.tsx b/src/components/common/VideoPlayer/Components/LoadingVideo.tsx index 2eea3df..9d2f209 100644 --- a/src/components/common/VideoPlayer/Components/LoadingVideo.tsx +++ b/src/components/common/VideoPlayer/Components/LoadingVideo.tsx @@ -2,23 +2,12 @@ import { Box, CircularProgress, Typography } from "@mui/material"; import { setVideoPlaying } from "../../../../state/features/globalSlice.ts"; import { useDispatch } from "react-redux"; import { PlayArrow } from "@mui/icons-material"; +import { useVideoContext } from "./VideoContext.ts"; + +export const LoadingVideo = () => { + const { isLoading, resourceStatus, src, startPlay, from, togglePlay } = + useVideoContext(); -export interface LoadingVideoProps { - isLoading: boolean; - resourceStatus: any; - src: any; - startPlay: boolean; - from: any; - togglePlay: () => void; -} -export const LoadingVideo = ({ - isLoading, - resourceStatus, - src, - startPlay, - from, - togglePlay, -}: LoadingVideoProps) => { const getDownloadProgress = (current: number, total: number) => { const progress = (current / total) * 100; return Number.isNaN(progress) ? "" : progress.toFixed(0) + "%"; @@ -27,7 +16,7 @@ export const LoadingVideo = ({ const dispatch = useDispatch(); return ( <> - {isLoading && ( + {isLoading.value && ( )} - {((!src && !isLoading) || !startPlay) && ( + {((!src && !isLoading.value) || !startPlay.value) && ( { + const { + togglePlay, + reloadVideo, + onProgressChange, + videoRef, + handleMenuOpen, + handleMenuClose, + onVolumeChange, + increaseSpeed, + togglePictureInPicture, + toggleFullscreen, + playing, + progress, + anchorEl, + volume, + playbackRate, + } = useVideoContext(); + + return ( + <> + togglePlay()} + > + {playing.value ? : } + + + + + + + + + + + + + + increaseSpeed()}> + + Speed: {playbackRate}x + + + + + + + + + + + ); +}; diff --git a/src/components/common/VideoPlayer/Components/VideoContext.ts b/src/components/common/VideoPlayer/Components/VideoContext.ts new file mode 100644 index 0000000..3f27fd3 --- /dev/null +++ b/src/components/common/VideoPlayer/Components/VideoContext.ts @@ -0,0 +1,28 @@ +import { createContext, useContext } from "react"; +import { useVideoPlayerState } from "../VideoPlayer-State.ts"; +import { VideoPlayerProps } from "../VideoPlayer.tsx"; +import { useVideoControlsState } from "./VideoControls-State.ts"; + +export type VideoContextType = VideoPlayerProps & + ReturnType & + ReturnType; + +export const VideoContext = createContext( + undefined +); +export const useContextData = (props, ref) => { + const videoState = useVideoPlayerState(props, ref); + const controlState = useVideoControlsState(props, videoState); + + return { + ...props, + ...videoState, + ...controlState, + }; +}; + +export const useVideoContext = () => { + const context = useContext(VideoContext); + if (!context) throw new Error("VideoContext is NULL"); + return context; +}; diff --git a/src/components/common/VideoPlayer/Components/VideoControls-State.ts b/src/components/common/VideoPlayer/Components/VideoControls-State.ts index b9d15fa..c610566 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls-State.ts +++ b/src/components/common/VideoPlayer/Components/VideoControls-State.ts @@ -1,3 +1,9 @@ +import { useSignal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; + +import { useEffect } from "react"; +import ReactDOM from "react-dom"; +import { useSelector } from "react-redux"; import { Key } from "ts-key-enum"; import { setVideoPlaying } from "../../../../state/features/globalSlice.ts"; import { @@ -6,36 +12,33 @@ import { setVolumeSetting, } from "../../../../state/features/persistSlice.ts"; import { RootState, store } from "../../../../state/store.ts"; -import { - canPlay, - isLoading, - isMuted, - mutedVolume, - playbackRate, - playing, - progress, - startPlay, - useVideoPlayerState, - videoObjectFit, - volume, -} from "../VideoPlayer-State.ts"; -import { useEffect } from "react"; -import { signal, useSignal } from "@preact/signals-react"; -import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; +import { useVideoPlayerState } from "../VideoPlayer-State.ts"; import { VideoPlayerProps } from "../VideoPlayer.tsx"; -import ReactDOM from "react-dom"; -import { useSelector } from "react-redux"; - -export const showControlsFullScreen = signal(true); export const useVideoControlsState = ( props: VideoPlayerProps, - videoRef: React.MutableRefObject, videoPlayerState: ReturnType ) => { useSignals(); - const { src, getSrc, resourceStatus } = videoPlayerState; + const { + src, + getSrc, + resourceStatus, + videoRef, + playbackRate, + playing, + isLoading, + startPlay, + volume, + isMuted, + mutedVolume, + progress, + videoObjectFit, + canPlay, + } = videoPlayerState; const { identifier, autoPlay } = props; + + const showControlsFullScreen = useSignal(true); const persistSelector = store.getState().persist; const videoPlaying = useSelector( @@ -146,8 +149,19 @@ export const useVideoControlsState = ( togglePlay(); }; + const handleCanPlay = () => { + isLoading.value = false; + canPlay.value = true; + updatePlaybackRate(playbackRate.value); + setPlaying(true); // makes the video play when fully loaded + }; + + const setPlaying = async (setPlay: boolean) => { + setPlay ? await videoRef.current.play() : videoRef.current.pause(); + playing.value = setPlay; + }; + const togglePlay = async () => { - // console.log("in toggleplay playing is: ", playing.value); if (!videoRef.current) return; if (!src || resourceStatus?.status !== "READY") { const el = document.getElementById("videoWrapper"); @@ -159,13 +173,9 @@ export const useVideoControlsState = ( }); getSrc(); } + startPlay.value = true; - - const pause = playing.value; - playing.value = !playing.value; - - if (pause) videoRef.current.pause(); - else await videoRef.current.play(); + setPlaying(!playing.value); }; const onVolumeChange = (_: any, value: number | number[]) => { @@ -178,9 +188,7 @@ export const useVideoControlsState = ( }; useEffect(() => { - if (autoPlay && identifier) { - togglePlay(); - } + if (autoPlay && identifier) togglePlay(); }, [autoPlay, identifier]); const mute = () => { @@ -353,12 +361,6 @@ export const useVideoControlsState = ( } }; - const handleCanPlay = () => { - isLoading.value = false; - canPlay.value = true; - updatePlaybackRate(playbackRate.value); - }; - useEffect(() => { videoRef.current.volume = volume.value; if ( @@ -390,5 +392,6 @@ export const useVideoControlsState = ( keyboardShortcutsDown, handleCanPlay, toggleMute, + showControlsFullScreen, }; }; diff --git a/src/components/common/VideoPlayer/Components/VideoControls.tsx b/src/components/common/VideoPlayer/Components/VideoControls.tsx index 2c85010..dc5d752 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls.tsx +++ b/src/components/common/VideoPlayer/Components/VideoControls.tsx @@ -1,6 +1,5 @@ import { Fullscreen, - MoreVert as MoreIcon, Pause, PictureInPicture, PlayArrow, @@ -8,39 +7,15 @@ import { VolumeOff, VolumeUp, } from "@mui/icons-material"; -import { IconButton, Menu, MenuItem, Slider, Typography } from "@mui/material"; -import { useSignals } from "@preact/signals-react/runtime"; +import { IconButton, Slider, Typography } from "@mui/material"; +import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; import { useEffect } from "react"; -import { - anchorEl, - canPlay, - isMobileView, - isMuted, - playbackRate, - playing, - progress, - useVideoPlayerState, - volume, -} from "../VideoPlayer-State.ts"; -import { ControlsContainer } from "../VideoPlayer-styles.ts"; -import { VideoPlayerProps } from "../VideoPlayer.tsx"; -import { - showControlsFullScreen, - useVideoControlsState, -} from "./VideoControls-State.ts"; -export interface VideoControlProps { - controlState: ReturnType; - videoState: ReturnType; - props: VideoPlayerProps; - videoRef: any; -} -export const VideoControls = ({ - controlState, - videoState, - props, - videoRef, -}: VideoControlProps) => { +import { ControlsContainer } from "../VideoPlayer-styles.ts"; +import { MobileControls } from "./MobileControls.tsx"; +import { useVideoContext } from "./VideoContext.ts"; + +export const VideoControls = () => { useSignals(); const { reloadVideo, @@ -51,97 +26,38 @@ export const VideoControls = ({ toggleFullscreen, formatTime, toggleMute, - } = controlState; - const { onProgressChange, handleMenuOpen, handleMenuClose, toggleRef } = - videoState; - const { from = null } = props; + onProgressChange, + toggleRef, + from, + videoRef, + canPlay, + isMobileView, + isMuted, + playbackRate, + playing, + progress, + volume, + showControlsFullScreen, + } = useVideoContext(); - useEffect(() => { + useSignalEffect(() => { + console.log("canPlay is: ", canPlay.value); const videoWidth = videoRef?.current?.offsetWidth; if (videoWidth && videoWidth <= 600) { isMobileView.value = true; } - }, [canPlay.value]); + }); + + const showMobileControls = + isMobileView.value && canPlay.value && showControlsFullScreen.value; return ( - {isMobileView.value && canPlay.value && showControlsFullScreen.value ? ( - <> - togglePlay()} - > - {playing.value ? : } - - - - - - - - - - - - - - increaseSpeed()}> - - Speed: {playbackRate.value}x - - - - - - - - - - + {showMobileControls ? ( + ) : canPlay.value ? ( <> - {progress && + {progress.value && videoRef.current?.duration && formatTime(progress.value)} / - {progress && + {progress.value && videoRef.current?.duration && formatTime(videoRef.current?.duration)} diff --git a/src/components/common/VideoPlayer/VideoPlayer-State.ts b/src/components/common/VideoPlayer/VideoPlayer-State.ts index 763a77c..8311044 100644 --- a/src/components/common/VideoPlayer/VideoPlayer-State.ts +++ b/src/components/common/VideoPlayer/VideoPlayer-State.ts @@ -1,4 +1,4 @@ -import { signal } from "@preact/signals-react"; +import { useSignal, useSignals } from "@preact/signals-react/runtime"; import React, { useContext, useEffect, @@ -10,33 +10,28 @@ import React, { import { useDispatch, useSelector } from "react-redux"; import { setVideoPlaying } from "../../../state/features/globalSlice.ts"; import { StretchVideoType } from "../../../state/features/persistSlice.ts"; -import { RootState, store } from "../../../state/store.ts"; +import { RootState } from "../../../state/store.ts"; import { MyContext } from "../../../wrappers/DownloadWrapper.tsx"; import { VideoPlayerProps } from "./VideoPlayer.tsx"; -import { useSignals } from "@preact/signals-react/runtime"; - -export const playing = signal(false); - -export const isMuted = signal(false); -export const progress = signal(0); -export const isLoading = signal(false); -export const canPlay = signal(false); -export const startPlay = signal(false); -export const isMobileView = signal(false); - -export const volume = signal(0.5); -export const mutedVolume = signal(0.5); -export const playbackRate = signal(1); -export const anchorEl = signal(null); -export const videoObjectFit = signal("contain"); export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => { useSignals(); const persistSelector = useSelector((state: RootState) => state.persist); - volume.value = persistSelector.volume; - mutedVolume.value = persistSelector.volume; - playbackRate.value = persistSelector.playbackRate; - videoObjectFit.value = persistSelector.stretchVideoSetting; + + const playing = useSignal(false); + const isMuted = useSignal(false); + const progress = useSignal(0); + const isLoading = useSignal(false); + const canPlay = useSignal(false); + const startPlay = useSignal(false); + const isMobileView = useSignal(false); + const volume = useSignal(persistSelector.volume); + const mutedVolume = useSignal(persistSelector.volume); + const playbackRate = useSignal(persistSelector.playbackRate); + const anchorEl = useSignal(null); + const videoObjectFit = useSignal( + persistSelector.stretchVideoSetting + ); const { name, @@ -119,7 +114,8 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => { if (!videoRef.current) return; videoRef.current.currentTime = value as number; progress.value = value as number; - if (!playing) { + + if (!playing.value) { await videoRef.current.play(); playing.value = true; } @@ -269,5 +265,17 @@ export const useVideoPlayerState = (props: VideoPlayerProps, ref: any) => { handleMenuOpen, handleMenuClose, toggleRef, + playing, + isMuted, + progress, + isLoading, + canPlay, + startPlay, + isMobileView, + volume, + mutedVolume, + playbackRate, + anchorEl, + videoObjectFit, }; }; diff --git a/src/components/common/VideoPlayer/VideoPlayer.tsx b/src/components/common/VideoPlayer/VideoPlayer.tsx index 1f187d0..6d79312 100644 --- a/src/components/common/VideoPlayer/VideoPlayer.tsx +++ b/src/components/common/VideoPlayer/VideoPlayer.tsx @@ -2,17 +2,8 @@ import { useSignals } from "@preact/signals-react/runtime"; import CSS from "csstype"; import { forwardRef } from "react"; import { LoadingVideo } from "./Components/LoadingVideo.tsx"; -import { - showControlsFullScreen, - useVideoControlsState, -} from "./Components/VideoControls-State.ts"; +import { useContextData, VideoContext } from "./Components/VideoContext.ts"; import { VideoControls } from "./Components/VideoControls.tsx"; -import { - isLoading, - startPlay, - useVideoPlayerState, - videoObjectFit, -} from "./VideoPlayer-State.ts"; import { VideoContainer, VideoElement } from "./VideoPlayer-styles.ts"; export interface VideoStyles { @@ -37,95 +28,85 @@ export interface VideoPlayerProps { style?: CSS.Properties; } -export type refType = { +export type videoRefType = { getContainerRef: () => React.MutableRefObject; getVideoRef: () => React.MutableRefObject; }; -export const VideoPlayer = forwardRef( +export const VideoPlayer = forwardRef( (props: VideoPlayerProps, ref) => { useSignals(); + const contextData = useContextData(props, ref); + const { - poster, - identifier, - autoplay = true, - from = null, - videoStyles = {}, - } = props; - const videoState = useVideoPlayerState(props, ref); - const { + keyboardShortcutsUp, + keyboardShortcutsDown, + from, + videoStyles, containerRef, resourceStatus, - videoRef, src, + togglePlay, + identifier, + videoRef, + poster, updateProgress, + autoplay, handleEnded, - getSrc, - toggleRef, - } = videoState; - - const controlState = useVideoControlsState(props, videoRef, videoState); - const { keyboardShortcutsUp, keyboardShortcutsDown, togglePlay } = - controlState; + handleCanPlay, + startPlay, + videoObjectFit, + showControlsFullScreen, + } = contextData; return ( - - - togglePlay()} - onEnded={handleEnded} - // onLoadedMetadata={handleLoadedMetadata} - onCanPlay={controlState.handleCanPlay} - onMouseEnter={e => { - showControlsFullScreen.value = true; + + { - showControlsFullScreen.value = false; - }} - preload="metadata" - style={ - startPlay.value - ? { - ...videoStyles?.video, - objectFit: videoObjectFit.value, - } - : { height: "100%", ...videoStyles } - } - /> - - + ref={containerRef} + > + + togglePlay()} + onEnded={handleEnded} + // onLoadedMetadata={handleLoadedMetadata} + onCanPlay={handleCanPlay} + onMouseEnter={e => { + showControlsFullScreen.value = true; + }} + onMouseLeave={e => { + showControlsFullScreen.value = false; + }} + preload="metadata" + style={ + startPlay.value + ? { + ...videoStyles?.video, + objectFit: videoObjectFit.value, + } + : { height: "100%", ...videoStyles } + } + /> + + + ); } ); diff --git a/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx b/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx index 23eafda..5d40410 100644 --- a/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx +++ b/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx @@ -4,7 +4,7 @@ import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAn import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx"; import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx"; import FileElement from "../../../components/common/FileElement.tsx"; -import { refType } from "../../../components/common/VideoPlayer/VideoPlayer.tsx"; +import { videoRefType } from "../../../components/common/VideoPlayer/VideoPlayer.tsx"; import { titleFormatterOnSave } from "../../../constants/Misc.ts"; import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx"; import DownloadIcon from "@mui/icons-material/Download"; diff --git a/src/pages/ContentPages/VideoContent/VideoContent-State.ts b/src/pages/ContentPages/VideoContent/VideoContent-State.ts index 3c41fbf..c29495a 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent-State.ts +++ b/src/pages/ContentPages/VideoContent/VideoContent-State.ts @@ -1,4 +1,4 @@ -import { refType } from "../../../components/common/VideoPlayer/VideoPlayer.tsx"; +import { videoRefType } from "../../../components/common/VideoPlayer/VideoPlayer.tsx"; import { QTUBE_VIDEO_BASE, SUPER_LIKE_BASE, @@ -27,7 +27,7 @@ export const useVideoContentState = () => { const [isExpandedDescription, setIsExpandedDescription] = useState(false); - const containerRef = useRef(null); + const containerRef = useRef(null); const [nameAddress, setNameAddress] = useState(""); const [descriptionHeight, setDescriptionHeight] = useState( diff --git a/src/pages/ContentPages/VideoContent/VideoContent.tsx b/src/pages/ContentPages/VideoContent/VideoContent.tsx index 331fbe9..222ec82 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent.tsx @@ -6,7 +6,7 @@ import { CommentSection } from "../../../components/common/Comments/CommentSecti import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx"; import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx"; import { - refType, + videoRefType, VideoPlayer, } from "../../../components/common/VideoPlayer/VideoPlayer.tsx"; import { From f7a139a654f599d9736056e9ea844aca1fb93cac Mon Sep 17 00:00:00 2001 From: IrohDW Date: Thu, 24 Oct 2024 16:00:27 -0600 Subject: [PATCH 2/2] Videoplayer data is stored in a context. Mobile and PC Video Controls are split into different components. --- src/components/common/VideoPlayer/Components/VideoControls.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/VideoPlayer/Components/VideoControls.tsx b/src/components/common/VideoPlayer/Components/VideoControls.tsx index dc5d752..15cd00a 100644 --- a/src/components/common/VideoPlayer/Components/VideoControls.tsx +++ b/src/components/common/VideoPlayer/Components/VideoControls.tsx @@ -41,7 +41,7 @@ export const VideoControls = () => { } = useVideoContext(); useSignalEffect(() => { - console.log("canPlay is: ", canPlay.value); + console.log("canPlay is: ", canPlay.value); // makes the function execute when canPlay changes const videoWidth = videoRef?.current?.offsetWidth; if (videoWidth && videoWidth <= 600) { isMobileView.value = true;