Respect motion only when playing back (#10632)

* Respect motion only when playing back motion

* Increase efficiency

* Fix import
This commit is contained in:
Nicolas Mowen 2024-03-23 13:49:31 -06:00 committed by GitHub
parent c2a32bd6c1
commit bb50b2b6f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 82 additions and 16 deletions

View File

@ -1,3 +1,4 @@
import { Timeline } from "@/types/timeline";
import { useState } from "react"; import { useState } from "react";
type TimelineEventOverlayProps = { type TimelineEventOverlayProps = {

View File

@ -6,7 +6,7 @@ import { useEffect, useMemo, useState } from "react";
import MSEPlayer from "./MsePlayer"; import MSEPlayer from "./MsePlayer";
import JSMpegPlayer from "./JSMpegPlayer"; import JSMpegPlayer from "./JSMpegPlayer";
import { MdCircle } from "react-icons/md"; 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 { useRecordingsState } from "@/api/ws";
import { LivePlayerMode } from "@/types/live"; import { LivePlayerMode } from "@/types/live";
import useCameraLiveMode from "@/hooks/use-camera-live-mode"; import useCameraLiveMode from "@/hooks/use-camera-live-mode";

View File

@ -1,6 +1,7 @@
import { Recording } from "@/types/record"; import { Recording } from "@/types/record";
import { DynamicPlayback } from "@/types/playback"; import { DynamicPlayback } from "@/types/playback";
import { PreviewController } from "../PreviewPlayer"; import { PreviewController } from "../PreviewPlayer";
import { Timeline } from "@/types/timeline";
type PlayerMode = "playback" | "scrubbing"; type PlayerMode = "playback" | "scrubbing";

View File

@ -8,6 +8,7 @@ import { Preview } from "@/types/preview";
import PreviewPlayer, { PreviewController } from "../PreviewPlayer"; import PreviewPlayer, { PreviewController } from "../PreviewPlayer";
import { DynamicVideoController } from "./DynamicVideoController"; import { DynamicVideoController } from "./DynamicVideoController";
import HlsVideoPlayer from "../HlsVideoPlayer"; import HlsVideoPlayer from "../HlsVideoPlayer";
import { Timeline } from "@/types/timeline";
/** /**
* Dynamically switches between video playback and scrubbing preview player. * Dynamically switches between video playback and scrubbing preview player.

View File

@ -4,6 +4,8 @@ import {
useMotionActivity, useMotionActivity,
} from "@/api/ws"; } from "@/api/ws";
import { CameraConfig } from "@/types/frigateConfig"; import { CameraConfig } from "@/types/frigateConfig";
import { MotionData, ReviewSegment } from "@/types/review";
import { TimeRange } from "@/types/timeline";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
type useCameraActivityReturn = { type useCameraActivityReturn = {
@ -12,7 +14,7 @@ type useCameraActivityReturn = {
activeAudio: boolean; activeAudio: boolean;
}; };
export default function useCameraActivity( export function useCameraActivity(
camera: CameraConfig, camera: CameraConfig,
): useCameraActivityReturn { ): useCameraActivityReturn {
const [activeObjects, setActiveObjects] = useState<string[]>([]); const [activeObjects, setActiveObjects] = useState<string[]>([]);
@ -66,3 +68,57 @@ export default function useCameraActivity(
: false, : 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;
}

View File

@ -1,4 +1,4 @@
type Timeline = { export type Timeline = {
camera: string; camera: string;
timestamp: number; timestamp: number;
data: { data: {
@ -23,11 +23,4 @@ type Timeline = {
source: string; source: string;
}; };
// may be used in the future, keep for now for reference export type TimeRange = { before: number; after: number };
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type HourlyTimeline = {
start: number;
end: number;
count: number;
hours: { [key: string]: Timeline[] };
};

View File

@ -21,6 +21,7 @@ import {
} from "react-icons/md"; } from "react-icons/md";
import { FaBicycle } from "react-icons/fa"; import { FaBicycle } from "react-icons/fa";
import { endOfHourOrCurrentTime } from "./dateUtil"; import { endOfHourOrCurrentTime } from "./dateUtil";
import { Timeline } from "@/types/timeline";
export function getTimelineIcon(timelineItem: Timeline) { export function getTimelineIcon(timelineItem: Timeline) {
switch (timelineItem.class_type) { switch (timelineItem.class_type) {

View File

@ -39,6 +39,8 @@ import PreviewPlayer, {
import SummaryTimeline from "@/components/timeline/SummaryTimeline"; import SummaryTimeline from "@/components/timeline/SummaryTimeline";
import { RecordingStartingPoint } from "@/types/record"; import { RecordingStartingPoint } from "@/types/record";
import VideoControls from "@/components/player/VideoControls"; import VideoControls from "@/components/player/VideoControls";
import { TimeRange } from "@/types/timeline";
import { useCameraMotionTimestamps } from "@/hooks/use-camera-activity";
type EventViewProps = { type EventViewProps = {
reviews?: ReviewSegment[]; reviews?: ReviewSegment[];
@ -606,7 +608,7 @@ type MotionReviewProps = {
significant_motion: ReviewSegment[]; significant_motion: ReviewSegment[];
}; };
relevantPreviews?: Preview[]; relevantPreviews?: Preview[];
timeRange: { before: number; after: number }; timeRange: TimeRange;
startTime?: number; startTime?: number;
filter?: ReviewFilter; filter?: ReviewFilter;
motionOnly?: boolean; motionOnly?: boolean;
@ -718,6 +720,12 @@ function MotionReview({
const [playbackRate, setPlaybackRate] = useState(8); const [playbackRate, setPlaybackRate] = useState(8);
const [controlsOpen, setControlsOpen] = useState(false); const [controlsOpen, setControlsOpen] = useState(false);
const seekTimestamps = useCameraMotionTimestamps(
timeRange,
motionOnly,
reviewItems?.all ?? [],
motionData ?? [],
);
useEffect(() => { useEffect(() => {
if (!playing) { if (!playing) {
@ -725,17 +733,22 @@ function MotionReview({
} }
const interval = 500 / playbackRate; const interval = 500 / playbackRate;
const startTime = currentTime; const startIdx = seekTimestamps.findIndex((time) => time > currentTime);
if (!startIdx) {
return;
}
let counter = 0; let counter = 0;
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
counter += 0.5; counter += 1;
if (startTime + counter >= timeRange.before) { if (startIdx + counter >= seekTimestamps.length) {
setPlaying(false); setPlaying(false);
return; return;
} }
setCurrentTime(startTime + counter); setCurrentTime(seekTimestamps[startIdx + counter]);
}, interval); }, interval);
return () => { return () => {