mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Respect motion only when playing back (#10632)
* Respect motion only when playing back motion * Increase efficiency * Fix import
This commit is contained in:
		
							parent
							
								
									c2a32bd6c1
								
							
						
					
					
						commit
						bb50b2b6f4
					
				| @ -1,3 +1,4 @@ | ||||
| import { Timeline } from "@/types/timeline"; | ||||
| import { useState } from "react"; | ||||
| 
 | ||||
| type TimelineEventOverlayProps = { | ||||
|  | ||||
| @ -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"; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { Recording } from "@/types/record"; | ||||
| import { DynamicPlayback } from "@/types/playback"; | ||||
| import { PreviewController } from "../PreviewPlayer"; | ||||
| import { Timeline } from "@/types/timeline"; | ||||
| 
 | ||||
| type PlayerMode = "playback" | "scrubbing"; | ||||
| 
 | ||||
|  | ||||
| @ -8,6 +8,7 @@ import { Preview } from "@/types/preview"; | ||||
| import PreviewPlayer, { PreviewController } from "../PreviewPlayer"; | ||||
| import { DynamicVideoController } from "./DynamicVideoController"; | ||||
| import HlsVideoPlayer from "../HlsVideoPlayer"; | ||||
| import { Timeline } from "@/types/timeline"; | ||||
| 
 | ||||
| /** | ||||
|  * Dynamically switches between video playback and scrubbing preview player. | ||||
|  | ||||
| @ -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<string[]>([]); | ||||
| @ -66,3 +68,57 @@ export default function useCameraActivity( | ||||
|       : false, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function useCameraMotionTimestamps( | ||||
|   timeRange: TimeRange, | ||||
|   motionOnly: boolean, | ||||
|   events: ReviewSegment[], | ||||
|   motion: MotionData[], | ||||
| ) { | ||||
|   const timestamps = useMemo(() => { | ||||
|     const seekableTimestamps = []; | ||||
|     let lastEventIdx = 0; | ||||
|     let lastMotionIdx = 0; | ||||
| 
 | ||||
|     for (let i = timeRange.after; i <= timeRange.before; i += 0.5) { | ||||
|       if (!motionOnly) { | ||||
|         seekableTimestamps.push(i); | ||||
|       } else { | ||||
|         const relevantEventIdx = events.findIndex((seg, segIdx) => { | ||||
|           if (segIdx < lastEventIdx) { | ||||
|             return false; | ||||
|           } | ||||
| 
 | ||||
|           return seg.start_time <= i && seg.end_time >= i; | ||||
|         }); | ||||
| 
 | ||||
|         if (relevantEventIdx != -1) { | ||||
|           lastEventIdx = relevantEventIdx; | ||||
|           continue; | ||||
|         } | ||||
| 
 | ||||
|         const relevantMotionIdx = motion.findIndex((mot, motIdx) => { | ||||
|           if (motIdx < lastMotionIdx) { | ||||
|             return false; | ||||
|           } | ||||
| 
 | ||||
|           return mot.start_time <= i && mot.start_time + 15 >= i; | ||||
|         }); | ||||
| 
 | ||||
|         if (relevantMotionIdx == -1 || motion[relevantMotionIdx].motion == 0) { | ||||
|           if (relevantMotionIdx != -1) { | ||||
|             lastMotionIdx = relevantMotionIdx; | ||||
|           } | ||||
| 
 | ||||
|           continue; | ||||
|         } | ||||
| 
 | ||||
|         seekableTimestamps.push(i); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return seekableTimestamps; | ||||
|   }, [timeRange, motionOnly, events, motion]); | ||||
| 
 | ||||
|   return timestamps; | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| type Timeline = { | ||||
| export type Timeline = { | ||||
|   camera: string; | ||||
|   timestamp: number; | ||||
|   data: { | ||||
| @ -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 }; | ||||
|  | ||||
| @ -21,6 +21,7 @@ import { | ||||
| } from "react-icons/md"; | ||||
| import { FaBicycle } from "react-icons/fa"; | ||||
| import { endOfHourOrCurrentTime } from "./dateUtil"; | ||||
| import { Timeline } from "@/types/timeline"; | ||||
| 
 | ||||
| export function getTimelineIcon(timelineItem: Timeline) { | ||||
|   switch (timelineItem.class_type) { | ||||
|  | ||||
| @ -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 () => { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user