From 2f33e6e18f9384903f7fdbcea4dbb2780054fed9 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:38:52 -0500 Subject: [PATCH] retry camera snapshots after a failed load AutoUpdatingCameraImage only scheduled the next frame from onLoad, so a single failed latest.webp request froze the image until the component remounted. It now retries on error, dropping the periodic cache key so the retry requests a fresh URL. --- .../camera/AutoUpdatingCameraImage.tsx | 20 +++++++++++++++++++ web/src/components/camera/CameraImage.tsx | 3 +++ 2 files changed, 23 insertions(+) diff --git a/web/src/components/camera/AutoUpdatingCameraImage.tsx b/web/src/components/camera/AutoUpdatingCameraImage.tsx index 43a59daa93..8e968c37d7 100644 --- a/web/src/components/camera/AutoUpdatingCameraImage.tsx +++ b/web/src/components/camera/AutoUpdatingCameraImage.tsx @@ -12,6 +12,7 @@ type AutoUpdatingCameraImageProps = { }; const MIN_LOAD_TIMEOUT_MS = 200; +const ERROR_RETRY_MS = 1000; export default function AutoUpdatingCameraImage({ camera, @@ -68,6 +69,24 @@ export default function AutoUpdatingCameraImage({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [key, setFps]); + const handleError = useCallback(() => { + if (reloadInterval == -1) { + return; + } + + // the periodic cache url doesn't change between retries, so drop it + setIsCached(true); + + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + timeoutRef.current = setTimeout( + () => setKey(Date.now()), + Math.max(reloadInterval, ERROR_RETRY_MS), + ); + }, [reloadInterval]); + // periodic cache to reduce loading indicator const [isCached, setIsCached] = useState(false); @@ -92,6 +111,7 @@ export default function AutoUpdatingCameraImage({ diff --git a/web/src/components/camera/CameraImage.tsx b/web/src/components/camera/CameraImage.tsx index 0ba7ea19e7..f328d77387 100644 --- a/web/src/components/camera/CameraImage.tsx +++ b/web/src/components/camera/CameraImage.tsx @@ -11,6 +11,7 @@ type CameraImageProps = { className?: string; camera: string; onload?: () => void; + onerror?: () => void; searchParams?: string; }; @@ -18,6 +19,7 @@ export default function CameraImage({ className, camera, onload, + onerror, searchParams = "", }: CameraImageProps) { const { data: config } = useSWR("config"); @@ -96,6 +98,7 @@ export default function CameraImage({ "rounded-lg md:rounded-2xl", )} onLoad={handleImageLoad} + onError={onerror} loading="lazy" /> ) : (