diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index e40754339..9a7814798 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -160,6 +160,7 @@ export default function HlsVideoPlayer({ show={controls} controlsOpen={controlsOpen} setControlsOpen={setControlsOpen} + playbackRate={videoRef.current?.playbackRate ?? 1} onPlayPause={(play) => { if (!videoRef.current) { return; @@ -180,6 +181,9 @@ export default function HlsVideoPlayer({ videoRef.current.currentTime = Math.max(0, currentTime + diff); }} + onSetPlaybackRate={(rate) => + videoRef.current ? (videoRef.current.playbackRate = rate) : null + } /> {children} diff --git a/web/src/components/player/VideoControls.tsx b/web/src/components/player/VideoControls.tsx index b8044c687..f6fa19d12 100644 --- a/web/src/components/player/VideoControls.tsx +++ b/web/src/components/player/VideoControls.tsx @@ -30,6 +30,7 @@ const CONTROLS_DEFAULT: VideoControls = { seek: true, playbackRate: true, }; +const PLAYBACK_RATE_DEFAULT = isSafari ? [0.5, 1, 2] : [0.5, 1, 2, 4, 8, 16]; type VideoControlsProps = { className?: string; @@ -38,9 +39,12 @@ type VideoControlsProps = { isPlaying: boolean; show: boolean; controlsOpen?: boolean; + playbackRates?: number[]; + playbackRate: number; setControlsOpen?: (open: boolean) => void; onPlayPause: (play: boolean) => void; onSeek: (diff: number) => void; + onSetPlaybackRate: (rate: number) => void; }; export default function VideoControls({ className, @@ -49,18 +53,13 @@ export default function VideoControls({ isPlaying, show, controlsOpen, + playbackRates = PLAYBACK_RATE_DEFAULT, + playbackRate, setControlsOpen, onPlayPause, onSeek, + onSetPlaybackRate, }: VideoControlsProps) { - const playbackRates = useMemo(() => { - if (isSafari) { - return [0.5, 1, 2]; - } else { - return [0.5, 1, 2, 4, 8, 16]; - } - }, []); - const onReplay = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); @@ -177,7 +176,7 @@ export default function VideoControls({ {features.seek && ( )} - {video && features.playbackRate && ( + {features.playbackRate && ( { @@ -186,10 +185,10 @@ export default function VideoControls({ } }} > - {`${video.playbackRate}x`} + {`${playbackRate}x`} (video.playbackRate = parseFloat(rate))} + onValueChange={(rate) => onSetPlaybackRate(parseFloat(rate))} > {playbackRates.map((rate) => ( diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index 156d16b2d..f9b8e2d43 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -716,11 +716,15 @@ function MotionReview({ // playback + const [playbackRate, setPlaybackRate] = useState(8); + const [controlsOpen, setControlsOpen] = useState(false); + useEffect(() => { if (!playing) { return; } + const interval = 500 / playbackRate; const startTime = currentTime; let counter = 0; const intervalId = setInterval(() => { @@ -732,14 +736,14 @@ function MotionReview({ } setCurrentTime(startTime + counter); - }, 60); + }, interval); return () => { clearInterval(intervalId); }; // do not render when current time changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [playing]); + }, [playing, playbackRate]); if (!relevantPreviews) { return ; @@ -815,9 +819,13 @@ function MotionReview({ features={{ volume: false, seek: true, - playbackRate: false, + playbackRate: true, }} isPlaying={playing} + playbackRates={[4, 8, 12, 16]} + playbackRate={playbackRate} + controlsOpen={controlsOpen} + setControlsOpen={setControlsOpen} onPlayPause={setPlaying} onSeek={(diff) => { const wasPlaying = playing; @@ -832,6 +840,7 @@ function MotionReview({ setTimeout(() => setPlaying(true), 100); } }} + onSetPlaybackRate={setPlaybackRate} show={currentTime < timeRange.before - 4} />