From 8f5698f261009ae40f82487a60f3a0c07c674798 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 13 Mar 2026 12:12:13 -0500 Subject: [PATCH] fix motion previews on safari and ios match the logic used in ScrubbablePreview for manually stepping currentTime at the correct rate --- web/src/views/events/MotionPreviewsPane.tsx | 89 +++++++++++---------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/web/src/views/events/MotionPreviewsPane.tsx b/web/src/views/events/MotionPreviewsPane.tsx index 7f126f107..6b5b262ce 100644 --- a/web/src/views/events/MotionPreviewsPane.tsx +++ b/web/src/views/events/MotionPreviewsPane.tsx @@ -9,6 +9,7 @@ import { useState, } from "react"; import { isCurrentHour } from "@/utils/dateUtil"; +import { isFirefox, isMobile, isSafari } from "react-device-detect"; import { useTranslation } from "react-i18next"; import { CameraConfig } from "@/types/frigateConfig"; import useSWR from "swr"; @@ -305,31 +306,46 @@ function MotionPreviewClip({ ); }, [clipStart, preview, range.end_time]); + const compatIntervalRef = useRef | null>(null); + + useEffect(() => { + return () => { + if (compatIntervalRef.current) { + clearInterval(compatIntervalRef.current); + } + }; + }, []); + const resetPlayback = useCallback(() => { if (!videoRef.current || !preview) { return; } + if (compatIntervalRef.current) { + clearInterval(compatIntervalRef.current); + compatIntervalRef.current = null; + } + videoRef.current.currentTime = clipStart; - videoRef.current.playbackRate = playbackRate; - }, [clipStart, playbackRate, preview]); - useEffect(() => { - if (!videoRef.current || !preview) { - return; - } - - if (!isVisible) { + if (isSafari || (isFirefox && isMobile)) { + // Safari / iOS can't play at speeds > 2x, so manually step through frames videoRef.current.pause(); - videoRef.current.currentTime = clipStart; - return; - } + compatIntervalRef.current = setInterval(() => { + if (!videoRef.current) { + return; + } - if (videoRef.current.readyState >= 2) { - resetPlayback(); - void videoRef.current.play().catch(() => undefined); + videoRef.current.currentTime += 1; + + if (videoRef.current.currentTime >= clipEnd) { + videoRef.current.currentTime = clipStart; + } + }, 1000 / playbackRate); + } else { + videoRef.current.playbackRate = playbackRate; } - }, [clipStart, isVisible, preview, resetPlayback]); + }, [clipStart, clipEnd, playbackRate, preview]); const drawDimOverlay = useCallback(() => { if (!dimOverlayCanvasRef.current) { @@ -463,15 +479,17 @@ function MotionPreviewClip({ {showLoadingIndicator && ( )} - {preview ? ( + {preview && isVisible ? ( <> {motionHeatmap && (