import ActivityScrubber from "@/components/scrubber/ActivityScrubber"; import ActivityIndicator from "@/components/ui/activity-indicator"; import { FrigateConfig } from "@/types/frigateConfig"; import { useEffect, useMemo, useRef, useState } from "react"; import useSWR from "swr"; import TimelineItemCard from "@/components/card/TimelineItemCard"; import { getTimelineHoursForDay } from "@/utils/historyUtil"; import { GraphDataPoint } from "@/types/graph"; import TimelineGraph from "@/components/graph/TimelineGraph"; import TimelineBar from "@/components/bar/TimelineBar"; import DynamicVideoPlayer, { DynamicVideoController, } from "@/components/player/DynamicVideoPlayer"; type DesktopTimelineViewProps = { timelineData: CardsData; allPreviews: Preview[]; initialPlayback: TimelinePlayback; }; export default function DesktopTimelineView({ timelineData, allPreviews, initialPlayback, }: DesktopTimelineViewProps) { const { data: config } = useSWR("config"); const timezone = useMemo( () => config?.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone, [config] ); const controllerRef = useRef(undefined); const initialScrollRef = useRef(null); const [selectedPlayback, setSelectedPlayback] = useState(initialPlayback); const [timelineTime, setTimelineTime] = useState(0); // handle scrolling to initial timeline item useEffect(() => { if (initialScrollRef.current != null) { initialScrollRef.current.scrollIntoView(); } }, [initialScrollRef]); const cameraPreviews = useMemo(() => { return allPreviews.filter((preview) => { return preview.camera == initialPlayback.camera; }); }, []); const timelineStack = useMemo( () => getTimelineHoursForDay( selectedPlayback.camera, timelineData, cameraPreviews, selectedPlayback.range.start + 60 ), [] ); const { data: activity } = useSWR( [ `${initialPlayback.camera}/recording/hourly/activity`, { after: timelineStack.start, before: timelineStack.end, timezone, }, ], { revalidateOnFocus: false } ); const timelineGraphData = useMemo(() => { if (!activity) { return {}; } const graphData: { [hour: string]: { objects: number[]; motion: GraphDataPoint[] }; } = {}; Object.entries(activity).forEach(([hour, data]) => { const objects: number[] = []; const motion: GraphDataPoint[] = []; data.forEach((seg, idx) => { if (seg.hasObjects) { objects.push(idx); } motion.push({ x: new Date(seg.date * 1000), y: seg.count, }); }); graphData[hour] = { objects, motion }; }); return graphData; }, [activity]); if (!config) { return ; } return (
{ controllerRef.current = controller; controllerRef.current.onPlayerTimeUpdate((timestamp: number) => { setTimelineTime(timestamp); }); if (initialPlayback.timelineItems.length > 0) { controllerRef.current?.seekToTimestamp( selectedPlayback.timelineItems[0].timestamp, true ); } }} />
{selectedPlayback.timelineItems.map((timeline) => { return ( { controllerRef.current?.seekToTimelineItem(timeline); }} /> ); })}
{timelineStack.playbackItems.map((timeline) => { const isInitiallySelected = initialPlayback.range.start == timeline.range.start; const isSelected = timeline.range.start == selectedPlayback.range.start; const graphData = timelineGraphData[timeline.range.start]; return (
{isSelected ? (
{ controllerRef.current?.scrubToTimestamp( data.time.getTime() / 1000 ); setTimelineTime(data.time.getTime() / 1000); }} timechangedHandler={(data) => { controllerRef.current?.seekToTimestamp( data.time.getTime() / 1000, true ); }} /> {isSelected && graphData && (
)}
) : ( { setSelectedPlayback(timeline); let startTs; if (timeline.timelineItems.length > 0) { startTs = selectedPlayback.timelineItems[0].timestamp; } else { startTs = timeline.range.start; } controllerRef.current?.seekToTimestamp(startTs, true); }} /> )}
); })}
); }