diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 69ad60bd3..a511bcb5d 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -6,7 +6,7 @@ import { useEffect, useMemo, useState } from "react"; import MSEPlayer from "./MsePlayer"; import JSMpegPlayer from "./JSMpegPlayer"; import { MdCircle } from "react-icons/md"; -import useCameraActivity from "@/hooks/use-camera-activity"; +import { useCameraActivity } from "@/hooks/use-camera-activity"; import { useRecordingsState } from "@/api/ws"; import { LivePlayerMode } from "@/types/live"; import useCameraLiveMode from "@/hooks/use-camera-live-mode"; diff --git a/web/src/hooks/use-camera-activity.ts b/web/src/hooks/use-camera-activity.ts index b15697c78..957de43c3 100644 --- a/web/src/hooks/use-camera-activity.ts +++ b/web/src/hooks/use-camera-activity.ts @@ -4,6 +4,8 @@ import { useMotionActivity, } from "@/api/ws"; import { CameraConfig } from "@/types/frigateConfig"; +import { MotionData, ReviewSegment } from "@/types/review"; +import { TimeRange } from "@/types/timeline"; import { useEffect, useMemo, useState } from "react"; type useCameraActivityReturn = { @@ -12,7 +14,7 @@ type useCameraActivityReturn = { activeAudio: boolean; }; -export default function useCameraActivity( +export function useCameraActivity( camera: CameraConfig, ): useCameraActivityReturn { const [activeObjects, setActiveObjects] = useState([]); @@ -66,3 +68,40 @@ export default function useCameraActivity( : false, }; } + +export function useCameraMotionTimestamps( + timeRange: TimeRange, + motionOnly: boolean, + events: ReviewSegment[], + motion: MotionData[], +) { + const timestamps = useMemo(() => { + const seekableTimestamps = []; + for (let i = timeRange.after; i <= timeRange.before; i += 0.5) { + if (!motionOnly) { + seekableTimestamps.push(i); + } else { + if ( + events.find((seg) => seg.start_time <= i && seg.end_time >= i) != + undefined + ) { + continue; + } + + const relevantMotion = motion.find( + (mot) => mot.start_time <= i && mot.start_time + 15 >= i, + ); + + if (!relevantMotion || relevantMotion.motion == 0) { + continue; + } + + seekableTimestamps.push(i); + } + } + + return seekableTimestamps; + }, [timeRange, motionOnly, events, motion]); + + return timestamps; +} diff --git a/web/src/types/timeline.ts b/web/src/types/timeline.ts index 86b364686..401d91ec7 100644 --- a/web/src/types/timeline.ts +++ b/web/src/types/timeline.ts @@ -23,11 +23,4 @@ type Timeline = { source: string; }; -// may be used in the future, keep for now for reference -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type HourlyTimeline = { - start: number; - end: number; - count: number; - hours: { [key: string]: Timeline[] }; -}; +export type TimeRange = { before: number; after: number }; diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index f9b8e2d43..353760ff7 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -39,6 +39,8 @@ import PreviewPlayer, { import SummaryTimeline from "@/components/timeline/SummaryTimeline"; import { RecordingStartingPoint } from "@/types/record"; import VideoControls from "@/components/player/VideoControls"; +import { TimeRange } from "@/types/timeline"; +import { useCameraMotionTimestamps } from "@/hooks/use-camera-activity"; type EventViewProps = { reviews?: ReviewSegment[]; @@ -606,7 +608,7 @@ type MotionReviewProps = { significant_motion: ReviewSegment[]; }; relevantPreviews?: Preview[]; - timeRange: { before: number; after: number }; + timeRange: TimeRange; startTime?: number; filter?: ReviewFilter; motionOnly?: boolean; @@ -718,6 +720,12 @@ function MotionReview({ const [playbackRate, setPlaybackRate] = useState(8); const [controlsOpen, setControlsOpen] = useState(false); + const seekTimestamps = useCameraMotionTimestamps( + timeRange, + motionOnly, + reviewItems?.all ?? [], + motionData ?? [], + ); useEffect(() => { if (!playing) { @@ -725,17 +733,22 @@ function MotionReview({ } const interval = 500 / playbackRate; - const startTime = currentTime; + const startIdx = seekTimestamps.findIndex((time) => time > currentTime); + + if (!startIdx) { + return; + } + let counter = 0; const intervalId = setInterval(() => { - counter += 0.5; + counter += 1; - if (startTime + counter >= timeRange.before) { + if (startIdx + counter >= seekTimestamps.length) { setPlaying(false); return; } - setCurrentTime(startTime + counter); + setCurrentTime(seekTimestamps[startIdx + counter]); }, interval); return () => {