mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-12-23 19:11:14 +01:00
194 lines
5.2 KiB
TypeScript
194 lines
5.2 KiB
TypeScript
import { useFrigateEvents, useMotionActivity } from "@/api/ws";
|
|
import { CameraConfig } from "@/types/frigateConfig";
|
|
import { MotionData, ReviewSegment } from "@/types/review";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { useTimelineUtils } from "./use-timeline-utils";
|
|
|
|
type ActiveObjectType = {
|
|
id: string;
|
|
label: string;
|
|
stationary: boolean;
|
|
};
|
|
|
|
type useCameraActivityReturn = {
|
|
activeTracking: boolean;
|
|
activeMotion: boolean;
|
|
activeObjects: ActiveObjectType[];
|
|
};
|
|
|
|
export function useCameraActivity(
|
|
camera: CameraConfig,
|
|
): useCameraActivityReturn {
|
|
const [activeObjects, setActiveObjects] = useState<ActiveObjectType[]>([]);
|
|
const hasActiveObjects = useMemo(
|
|
() => activeObjects.filter((obj) => !obj.stationary).length > 0,
|
|
[activeObjects],
|
|
);
|
|
|
|
const { payload: detectingMotion } = useMotionActivity(camera.name);
|
|
const { payload: event } = useFrigateEvents();
|
|
|
|
useEffect(() => {
|
|
if (!event) {
|
|
return;
|
|
}
|
|
|
|
if (event.after.camera != camera.name) {
|
|
return;
|
|
}
|
|
|
|
const eventIndex = activeObjects.findIndex(
|
|
(obj) => obj.id === event.after.id,
|
|
);
|
|
|
|
if (event.type == "end") {
|
|
if (eventIndex != -1) {
|
|
const newActiveObjects = [...activeObjects];
|
|
newActiveObjects.splice(eventIndex, 1);
|
|
setActiveObjects(newActiveObjects);
|
|
}
|
|
} else {
|
|
if (eventIndex == -1) {
|
|
// add unknown event to list if not stationary
|
|
if (!event.after.stationary) {
|
|
const newActiveObject: ActiveObjectType = {
|
|
id: event.after.id,
|
|
label: event.after.label,
|
|
stationary: event.after.stationary,
|
|
};
|
|
const newActiveObjects = [...activeObjects, newActiveObject];
|
|
setActiveObjects(newActiveObjects);
|
|
}
|
|
}
|
|
}
|
|
}, [camera, event, activeObjects]);
|
|
|
|
return {
|
|
activeTracking: hasActiveObjects,
|
|
activeMotion: detectingMotion == "ON",
|
|
activeObjects,
|
|
};
|
|
}
|
|
|
|
export function useCameraMotionNextTimestamp(
|
|
timeRangeSegmentEnd: number,
|
|
segmentDuration: number,
|
|
motionOnly: boolean,
|
|
reviewItems: ReviewSegment[],
|
|
motionData: MotionData[],
|
|
currentTime: number,
|
|
) {
|
|
const { alignStartDateToTimeline } = useTimelineUtils({
|
|
segmentDuration,
|
|
});
|
|
|
|
const noMotionRanges = useMemo(() => {
|
|
if (!motionData?.length || !reviewItems) {
|
|
return;
|
|
}
|
|
|
|
if (!motionOnly) {
|
|
return [];
|
|
}
|
|
|
|
const ranges = [];
|
|
let currentSegmentStart = null;
|
|
let currentSegmentEnd = null;
|
|
|
|
// align motion start to timeline start
|
|
const offset =
|
|
(motionData[0].start_time -
|
|
alignStartDateToTimeline(timeRangeSegmentEnd)) %
|
|
segmentDuration;
|
|
|
|
const startIndex = Math.abs(Math.floor(offset / 15));
|
|
|
|
for (
|
|
let i = startIndex;
|
|
i < motionData.length;
|
|
i = i + segmentDuration / 15
|
|
) {
|
|
const motionStart = motionData[i].start_time;
|
|
const motionEnd = motionStart + segmentDuration;
|
|
|
|
const segmentMotion = motionData
|
|
.slice(i, i + segmentDuration / 15)
|
|
.some(({ motion }) => motion !== undefined && motion > 0);
|
|
const overlappingReviewItems = reviewItems.some(
|
|
(item) =>
|
|
(item.start_time >= motionStart && item.start_time < motionEnd) ||
|
|
((item.end_time ?? Date.now() / 1000) > motionStart &&
|
|
(item.end_time ?? Date.now() / 1000) <= motionEnd) ||
|
|
(item.start_time <= motionStart &&
|
|
(item.end_time ?? Date.now() / 1000) >= motionEnd),
|
|
);
|
|
|
|
if (!segmentMotion || overlappingReviewItems) {
|
|
if (currentSegmentStart === null) {
|
|
currentSegmentStart = motionStart;
|
|
}
|
|
currentSegmentEnd = motionEnd;
|
|
} else {
|
|
if (currentSegmentStart !== null) {
|
|
ranges.push([currentSegmentStart, currentSegmentEnd]);
|
|
currentSegmentStart = null;
|
|
currentSegmentEnd = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentSegmentStart !== null) {
|
|
ranges.push([currentSegmentStart, currentSegmentEnd]);
|
|
}
|
|
|
|
return ranges;
|
|
}, [
|
|
motionData,
|
|
reviewItems,
|
|
motionOnly,
|
|
alignStartDateToTimeline,
|
|
segmentDuration,
|
|
timeRangeSegmentEnd,
|
|
]);
|
|
|
|
const nextTimestamp = useMemo(() => {
|
|
if (!noMotionRanges) {
|
|
return;
|
|
}
|
|
|
|
if (!motionOnly) {
|
|
return currentTime + 0.5;
|
|
}
|
|
|
|
let currentRange = 0;
|
|
let nextTimestamp = currentTime + 0.5;
|
|
|
|
while (currentRange < noMotionRanges.length) {
|
|
const [start, end] = noMotionRanges[currentRange];
|
|
|
|
if (start && end) {
|
|
// If the current time is before the start of the current range
|
|
if (currentTime < start) {
|
|
// The next timestamp is either the start of the current range or currentTime + 0.5, whichever is smaller
|
|
nextTimestamp = Math.min(start, nextTimestamp);
|
|
break;
|
|
}
|
|
// If the current time is within the current range
|
|
else if (currentTime >= start && currentTime < end) {
|
|
// The next timestamp is the end of the current range
|
|
nextTimestamp = end;
|
|
currentRange++;
|
|
}
|
|
// If the current time is past the end of the current range
|
|
else {
|
|
currentRange++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nextTimestamp;
|
|
}, [currentTime, noMotionRanges, motionOnly]);
|
|
|
|
return nextTimestamp;
|
|
}
|