diff --git a/web/src/components/card/AnimatedEventCard.tsx b/web/src/components/card/AnimatedEventCard.tsx index 126dd4511..125b6dada 100644 --- a/web/src/components/card/AnimatedEventCard.tsx +++ b/web/src/components/card/AnimatedEventCard.tsx @@ -7,12 +7,12 @@ import { REVIEW_PADDING, ReviewSegment } from "@/types/review"; import { useNavigate } from "react-router-dom"; import { RecordingStartingPoint } from "@/types/record"; import axios from "axios"; -import { Preview } from "@/types/preview"; import { InProgressPreview, VideoPreview, } from "../player/PreviewThumbnailPlayer"; import { isCurrentHour } from "@/utils/dateUtil"; +import { useCameraPreviews } from "@/hooks/use-camera-previews"; type AnimatedEventCardProps = { event: ReviewSegment; @@ -24,10 +24,15 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) { // preview - const { data: previews } = useSWR( - currentHour - ? null - : `/preview/${event.camera}/start/${Math.round(event.start_time)}/end/${Math.round(event.end_time || event.start_time + 20)}`, + const previews = useCameraPreviews( + { + after: Math.round(event.start_time), + before: Math.round(event.end_time || event.start_time + 20), + }, + { + camera: event.camera, + fetchPreviews: !currentHour, + }, ); // interaction diff --git a/web/src/hooks/use-camera-previews.ts b/web/src/hooks/use-camera-previews.ts new file mode 100644 index 000000000..c01fd263a --- /dev/null +++ b/web/src/hooks/use-camera-previews.ts @@ -0,0 +1,57 @@ +import { Preview } from "@/types/preview"; +import { TimeRange } from "@/types/timeline"; +import { useEffect, useState } from "react"; +import useSWR from "swr"; + +type OptionalCameraPreviewProps = { + camera?: string; + autoRefresh?: boolean; + fetchPreviews?: boolean; +}; + +export function useCameraPreviews( + initialTimeRange: TimeRange, + { + camera = "all", + autoRefresh = true, + fetchPreviews = true, + }: OptionalCameraPreviewProps, +) { + const [timeRange, setTimeRange] = useState(initialTimeRange); + + useEffect(() => { + setTimeRange(initialTimeRange); + }, [initialTimeRange]); + + const { data: allPreviews } = useSWR( + fetchPreviews + ? `preview/${camera}/start/${timeRange.after}/end/${timeRange.before}` + : null, + { revalidateOnFocus: false, revalidateOnReconnect: false }, + ); + + // Set a timeout to update previews on the hour + useEffect(() => { + if (!autoRefresh || !fetchPreviews || !allPreviews) { + return; + } + + const callback = () => { + const nextPreviewStart = new Date( + allPreviews[allPreviews.length - 1].end * 1000, + ); + nextPreviewStart.setHours(nextPreviewStart.getHours() + 1); + + if (Date.now() > nextPreviewStart.getTime()) { + setTimeRange({ after: timeRange.after, before: Date.now() / 1000 }); + } + }; + document.addEventListener("focusin", callback); + + return () => { + document.removeEventListener("focusin", callback); + }; + }, [allPreviews, autoRefresh, fetchPreviews, timeRange]); + + return allPreviews; +} diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index 0cc3cd6b5..bf325f064 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -1,9 +1,9 @@ import ActivityIndicator from "@/components/indicators/activity-indicator"; import useApiFilter from "@/hooks/use-api-filter"; +import { useCameraPreviews } from "@/hooks/use-camera-previews"; import { useTimezone } from "@/hooks/use-date-utils"; import { useOverlayState, useSearchEffect } from "@/hooks/use-overlay-state"; import { FrigateConfig } from "@/types/frigateConfig"; -import { Preview } from "@/types/preview"; import { RecordingStartingPoint } from "@/types/record"; import { ReviewFilter, @@ -161,7 +161,6 @@ export default function Events() { }, [updateSummary]); // preview videos - const [previewKey, setPreviewKey] = useState(0); const previewTimes = useMemo(() => { if (!reviews || reviews.length == 0) { return undefined; @@ -170,50 +169,22 @@ export default function Events() { const startDate = new Date(); startDate.setMinutes(0, 0, 0); - let endDate; - if (previewKey == 0) { - endDate = new Date(reviews.at(-1)?.end_time || 0); - endDate.setHours(0, 0, 0, 0); - } else { - endDate = new Date(); - endDate.setMilliseconds(0); - } + const endDate = new Date(reviews.at(-1)?.end_time || 0); + endDate.setHours(0, 0, 0, 0); return { - start: startDate.getTime() / 1000, - end: endDate.getTime() / 1000, + after: startDate.getTime() / 1000, + before: endDate.getTime() / 1000, }; - }, [reviews, previewKey]); - const { data: allPreviews } = useSWR( - previewTimes - ? `preview/all/start/${previewTimes.start}/end/${previewTimes.end}` - : null, - { revalidateOnFocus: false, revalidateOnReconnect: false }, + }, [reviews]); + + const allPreviews = useCameraPreviews( + previewTimes ?? { after: 0, before: 0 }, + { + fetchPreviews: previewTimes != undefined, + }, ); - // Set a timeout to update previews on the hour - useEffect(() => { - if (!allPreviews || allPreviews.length == 0) { - return; - } - - const callback = () => { - const nextPreviewStart = new Date( - allPreviews[allPreviews.length - 1].end * 1000, - ); - nextPreviewStart.setHours(nextPreviewStart.getHours() + 1); - - if (Date.now() > nextPreviewStart.getTime()) { - setPreviewKey(10 * Math.random()); - } - }; - document.addEventListener("focusin", callback); - - return () => { - document.removeEventListener("focusin", callback); - }; - }, [allPreviews]); - // review status const markAllItemsAsReviewed = useCallback(