From ad2b56d95a1710c3c63123bab73908a2652097f9 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Mon, 16 Jun 2025 02:13:25 +0300 Subject: [PATCH] change to popover --- src/components/VideoPlayer/VideoControls.tsx | 346 +++++++++++++----- .../VideoPlayer/VideoControlsBar.tsx | 16 +- src/components/VideoPlayer/VideoPlayer.tsx | 43 ++- .../VideoPlayer/useVideoPlayerController.tsx | 3 +- .../VideoPlayer/useVideoPlayerHotKeys.tsx | 6 + 5 files changed, 308 insertions(+), 106 deletions(-) diff --git a/src/components/VideoPlayer/VideoControls.tsx b/src/components/VideoPlayer/VideoControls.tsx index a909e60..91d1ddf 100644 --- a/src/components/VideoPlayer/VideoControls.tsx +++ b/src/components/VideoPlayer/VideoControls.tsx @@ -1,4 +1,16 @@ -import { Box, IconButton, Popper, Slider, Typography } from "@mui/material"; +import { + alpha, + Box, + ButtonBase, + Divider, + Fade, + IconButton, + Popover, + Popper, + Slider, + Typography, + useTheme, +} from "@mui/material"; export const fontSizeExSmall = "60%"; export const fontSizeSmall = "80%"; import AspectRatioIcon from "@mui/icons-material/AspectRatio"; @@ -14,11 +26,14 @@ import { import { formatTime } from "../../utils/time.js"; import { CustomFontTooltip } from "./CustomFontTooltip.js"; import { useCallback, useEffect, useRef, useState } from "react"; - +import SlowMotionVideoIcon from "@mui/icons-material/SlowMotionVideo"; const buttonPaddingBig = "6px"; const buttonPaddingSmall = "4px"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; +import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos"; +import CheckIcon from "@mui/icons-material/Check"; -export const PlayButton = ({togglePlay, isPlaying , isScreenSmall}: any) => { +export const PlayButton = ({ togglePlay, isPlaying, isScreenSmall }: any) => { return ( { ); }; -export const ReloadButton = ({reloadVideo, isScreenSmall}: any) => { +export const ReloadButton = ({ reloadVideo, isScreenSmall }: any) => { return ( { ); }; -export const ProgressSlider = ({progress, duration, playerRef}: any) => { +export const ProgressSlider = ({ progress, duration, playerRef }: any) => { const sliderRef = useRef(null); const [hoverX, setHoverX] = useState(null); const [thumbnailUrl, setThumbnailUrl] = useState(null); - const [showDuration, setShowDuration] = useState(0) + const [showDuration, setShowDuration] = useState(0); const onProgressChange = (_: any, value: number | number[]) => { - if (!playerRef.current) return; + if (!playerRef.current) return; - playerRef.current?.currentTime(value as number); + playerRef.current?.currentTime(value as number); }; const THUMBNAIL_DEBOUNCE = 500; @@ -69,8 +84,6 @@ export const ProgressSlider = ({progress, duration, playerRef}: any) => { const debounceTimeoutRef = useRef(null); const previousBlobUrlRef = useRef(null); - - const handleMouseMove = (e: React.MouseEvent) => { const slider = sliderRef.current; if (!slider) return; @@ -79,10 +92,10 @@ export const ProgressSlider = ({progress, duration, playerRef}: any) => { const x = e.clientX - rect.left; const percent = x / rect.width; const time = Math.min(Math.max(0, percent * duration), duration); - console.log('hello100') + console.log("hello100"); setHoverX(e.clientX); - setShowDuration(time) + setShowDuration(time); if (debounceTimeoutRef.current) clearTimeout(debounceTimeoutRef.current); // debounceTimeoutRef.current = setTimeout(() => { @@ -112,29 +125,31 @@ export const ProgressSlider = ({progress, duration, playerRef}: any) => { }, []); const hoverAnchorRef = useRef(null); - if(hoverX){ -console.log('thumbnailUrl', thumbnailUrl, hoverX) - + if (hoverX) { + console.log("thumbnailUrl", thumbnailUrl, hoverX); } - console.log('duration', duration) + console.log("duration", duration); return ( - + + ref={hoverAnchorRef} + sx={{ + position: "absolute", + left: hoverX ?? -9999, + top: 0, + width: "1px", + height: "1px", + pointerEvents: "none", + }} + /> - {hoverX !== null && ( + {hoverX !== null && ( - - {/* + {/* */} - {formatTime(showDuration)} + + {formatTime(showDuration)} + )} @@ -209,9 +245,7 @@ console.log('thumbnailUrl', thumbnailUrl, hoverX) ); }; -export const VideoTime = ({progress, isScreenSmall, duration}: any) => { - - +export const VideoTime = ({ progress, isScreenSmall, duration }: any) => { return ( { - {typeof duration === 'number' ? formatTime(progress) : ''} - {' / '} - {typeof duration === 'number' ? formatTime(duration) : ''} + {typeof duration === "number" ? formatTime(progress) : ""} + {" / "} + {typeof duration === "number" ? formatTime(duration) : ""} ); }; -const VolumeButton = ({isMuted, toggleMute}: any) => { +const VolumeButton = ({ isMuted, toggleMute }: any) => { return ( { sx={{ display: "flex", gap: "5px", alignItems: "center", width: "100%" }} > - + ); }; -export const PlaybackRate = ({playbackRate, increaseSpeed, isScreenSmall}: any) => { +const speeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 2.5, 3]; + +export const PlaybackRate = ({ + playbackRate, + increaseSpeed, + isScreenSmall, + onSelect, +}: any) => { + const [isOpen, setIsOpen] = useState(false); + const btnRef = useRef(null); + const theme = useTheme(); + const onBack = () => { + setIsOpen(false); + }; + return ( - - increaseSpeed()} + <> + - {playbackRate}x - - + setIsOpen(true)} + > + + + + + setIsOpen(false)} + slots={{ + transition: Fade, + }} + slotProps={{ + transition: { + timeout: 200, + }, + paper: { + sx: { + bgcolor: alpha("#181818", 0.98), + color: "white", + opacity: 0.9, + borderRadius: 2, + boxShadow: 5, + p: 1, + minWidth: 225, + height: 300, + overflow: "hidden", + display: "flex", + flexDirection: "column", + }, + }, + }} + anchorOrigin={{ + vertical: "top", + horizontal: "center", + }} + transformOrigin={{ + vertical: "bottom", + horizontal: "center", + }} + > + + + + + + + Playback speed + + + + + + {speeds?.map((speed) => { + const isSelected = speed === playbackRate; + return ( + { + onSelect(speed) + setIsOpen(false) + }} + sx={{ + px: 2, + py: 1, + "&:hover": { + backgroundColor: "rgba(255, 255, 255, 0.1)", + }, + width: "100%", + justifyContent: "space-between", + }} + > + {speed} + {isSelected ? : } + + ); + })} + + + ); }; -export const ObjectFitButton = ({toggleObjectFit, isScreenSmall}: any) => { +export const ObjectFitButton = ({ toggleObjectFit, isScreenSmall }: any) => { return ( { ); }; -export const PictureInPictureButton = ({isFullscreen, toggleRef, togglePictureInPicture, isScreenSmall}: any) => { - +export const PictureInPictureButton = ({ + isFullscreen, + toggleRef, + togglePictureInPicture, + isScreenSmall, +}: any) => { return ( <> {!isFullscreen && ( @@ -357,8 +536,7 @@ export const PictureInPictureButton = ({isFullscreen, toggleRef, togglePictureIn ); }; -export const FullscreenButton = ({toggleFullscreen, isScreenSmall}: any) => { - +export const FullscreenButton = ({ toggleFullscreen, isScreenSmall }: any) => { return ( void subtitleBtnRef: any + onSelectPlaybackRate: (rate: number)=> void; } -export const VideoControlsBar = ({subtitleBtnRef, showControls, playbackRate, increaseSpeed,decreaseSpeed, isFullScreen, showControlsFullScreen, reloadVideo, onVolumeChange, volume, isPlaying, canPlay, isScreenSmall, controlsHeight, playerRef, duration, progress, togglePlay, toggleFullscreen, extractFrames, openSubtitleManager}: VideoControlsBarProps) => { +export const VideoControlsBar = ({subtitleBtnRef, showControls, playbackRate, increaseSpeed,decreaseSpeed, isFullScreen, showControlsFullScreen, reloadVideo, onVolumeChange, volume, isPlaying, canPlay, isScreenSmall, controlsHeight, playerRef, duration, progress, togglePlay, toggleFullscreen, extractFrames, openSubtitleManager, onSelectPlaybackRate}: VideoControlsBarProps) => { const showMobileControls = isScreenSmall && canPlay; @@ -96,12 +98,18 @@ export const VideoControlsBar = ({subtitleBtnRef, showControls, playbackRate, in - - + + {/* */} + - + + {/* */} diff --git a/src/components/VideoPlayer/VideoPlayer.tsx b/src/components/VideoPlayer/VideoPlayer.tsx index 0106433..40b659d 100644 --- a/src/components/VideoPlayer/VideoPlayer.tsx +++ b/src/components/VideoPlayer/VideoPlayer.tsx @@ -206,6 +206,7 @@ export const VideoPlayer = ({ status, percentLoaded, showControlsFullScreen, + onSelectPlaybackRate } = useVideoPlayerController({ autoPlay, playerRef, @@ -214,6 +215,27 @@ export const VideoPlayer = ({ isPlayerInitialized, }); + console.log('isFullscreen', isFullscreen) + + const enterFullscreen = useCallback(() => { + const ref = containerRef?.current as any; + if (!ref) return; + + if (ref.requestFullscreen && !isFullscreen) { + ref.requestFullscreen(); + } + }, []); + + const exitFullscreen = useCallback(() => { + document?.exitFullscreen(); + }, [isFullscreen]); + + const toggleFullscreen = useCallback(() => { + console.log('togglefull', isFullscreen) + isFullscreen ? exitFullscreen() : enterFullscreen(); + }, [isFullscreen]); + + const hotkeyHandlers = useMemo( () => ({ reloadVideo, @@ -227,6 +249,7 @@ export const VideoPlayer = ({ toggleMute, setProgressAbsolute, setAlwaysShowControls, + toggleFullscreen }), [ reloadVideo, @@ -240,6 +263,7 @@ export const VideoPlayer = ({ toggleMute, setProgressAbsolute, setAlwaysShowControls, + toggleFullscreen ] ); @@ -346,23 +370,7 @@ export const VideoPlayer = ({ }; }, [isPlayerInitialized]); - const enterFullscreen = () => { - const ref = containerRef?.current as any; - if (!ref) return; - - if (ref.requestFullscreen && !isFullscreen) { - ref.requestFullscreen(); - } - }; - - const exitFullscreen = () => { - if (isFullscreen) document.exitFullscreen(); - }; - - const toggleFullscreen = () => { - isFullscreen ? exitFullscreen() : enterFullscreen(); - }; - + const canvasRef = useRef(null); const videoRefForCanvas = useRef(null); const extractFrames = useCallback((time: number): void => { @@ -744,6 +752,7 @@ export const VideoPlayer = ({ duration={duration} progress={localProgress} openSubtitleManager={openSubtitleManager} + onSelectPlaybackRate={onSelectPlaybackRate} /> )} diff --git a/src/components/VideoPlayer/useVideoPlayerController.tsx b/src/components/VideoPlayer/useVideoPlayerController.tsx index 0eca16e..1b471f1 100644 --- a/src/components/VideoPlayer/useVideoPlayerController.tsx +++ b/src/components/VideoPlayer/useVideoPlayerController.tsx @@ -280,6 +280,7 @@ const togglePlay = useCallback(async () => { } }, [togglePlay, isReady]); + return { reloadVideo, togglePlay, @@ -299,6 +300,6 @@ const togglePlay = useCallback(async () => { isReady, resourceUrl, startPlay, - status, percentLoaded, showControlsFullScreen + status, percentLoaded, showControlsFullScreen, onSelectPlaybackRate: updatePlaybackRate }; }; diff --git a/src/components/VideoPlayer/useVideoPlayerHotKeys.tsx b/src/components/VideoPlayer/useVideoPlayerHotKeys.tsx index 68e4e77..ff19e66 100644 --- a/src/components/VideoPlayer/useVideoPlayerHotKeys.tsx +++ b/src/components/VideoPlayer/useVideoPlayerHotKeys.tsx @@ -12,6 +12,7 @@ interface UseVideoControls { changeVolume: (delta: number) => void; toggleMute: () => void; setProgressAbsolute: (percent: number) => void; + toggleFullscreen: ()=> void; } export const useVideoPlayerHotKeys = (props: UseVideoControls) => { @@ -26,6 +27,7 @@ export const useVideoPlayerHotKeys = (props: UseVideoControls) => { changeVolume, toggleMute, setProgressAbsolute, + toggleFullscreen } = props; const handleKeyDown = useCallback((e: KeyboardEvent) => { @@ -46,6 +48,9 @@ export const useVideoPlayerHotKeys = (props: UseVideoControls) => { case "o": toggleObjectFit(); break; + case "f": + toggleFullscreen(); + break; case "c": toggleAlwaysShowControls(); break; @@ -127,6 +132,7 @@ export const useVideoPlayerHotKeys = (props: UseVideoControls) => { changeVolume, toggleMute, setProgressAbsolute, + toggleFullscreen ]); useEffect(() => {