forked from Github/frigate
Save initial camera state to update when websocket connects (#11174)
* Send camera state to dispatcher * Fix logic * Cleanup * Send camera activitiy in on connect * Support reading initial camera state * Fix key * Formatting * Sorting
This commit is contained in:
@@ -2,7 +2,12 @@ import { baseUrl } from "./baseUrl";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import useWebSocket, { ReadyState } from "react-use-websocket";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { FrigateEvent, FrigateReview, ToggleableSetting } from "@/types/ws";
|
||||
import {
|
||||
FrigateCameraState,
|
||||
FrigateEvent,
|
||||
FrigateReview,
|
||||
ToggleableSetting,
|
||||
} from "@/types/ws";
|
||||
import { FrigateStats } from "@/types/stats";
|
||||
import useSWR from "swr";
|
||||
import { createContainer } from "react-tracked";
|
||||
@@ -193,6 +198,16 @@ export function useFrigateStats(): { payload: FrigateStats } {
|
||||
return { payload: JSON.parse(payload as string) };
|
||||
}
|
||||
|
||||
export function useInitialCameraState(camera: string): {
|
||||
payload: FrigateCameraState;
|
||||
} {
|
||||
const {
|
||||
value: { payload },
|
||||
} = useWs("camera_activity", "");
|
||||
const data = JSON.parse(payload as string);
|
||||
return { payload: data ? data[camera] : undefined };
|
||||
}
|
||||
|
||||
export function useMotionActivity(camera: string): { payload: string } {
|
||||
const {
|
||||
value: { payload },
|
||||
|
||||
@@ -131,7 +131,11 @@ export default function Statusbar() {
|
||||
);
|
||||
|
||||
if (link) {
|
||||
return <Link to={link}>{message}</Link>;
|
||||
return (
|
||||
<Link key={id} to={link}>
|
||||
{message}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function LivePlayer({
|
||||
|
||||
// camera activity
|
||||
|
||||
const { activeMotion, activeTracking, activeObjects } =
|
||||
const { activeMotion, activeTracking, objects } =
|
||||
useCameraActivity(cameraConfig);
|
||||
|
||||
const cameraActive = useMemo(
|
||||
@@ -166,7 +166,7 @@ export default function LivePlayer({
|
||||
<div className="absolute bottom-0 inset-x-0 rounded-lg md:rounded-2xl z-10 w-full h-[10%] bg-gradient-to-t from-black/20 to-transparent pointer-events-none"></div>
|
||||
{player}
|
||||
|
||||
{activeObjects.length > 0 && (
|
||||
{objects.length > 0 && (
|
||||
<div className="absolute left-0 top-2 z-40">
|
||||
<Tooltip>
|
||||
<div className="flex">
|
||||
@@ -177,7 +177,7 @@ export default function LivePlayer({
|
||||
>
|
||||
{[
|
||||
...new Set([
|
||||
...(activeObjects || []).map(({ label }) => label),
|
||||
...(objects || []).map(({ label }) => label),
|
||||
]),
|
||||
]
|
||||
.map((label) => {
|
||||
@@ -189,11 +189,7 @@ export default function LivePlayer({
|
||||
</TooltipTrigger>
|
||||
</div>
|
||||
<TooltipContent className="capitalize">
|
||||
{[
|
||||
...new Set([
|
||||
...(activeObjects || []).map(({ label }) => label),
|
||||
]),
|
||||
]
|
||||
{[...new Set([...(objects || []).map(({ label }) => label)])]
|
||||
.filter(
|
||||
(label) =>
|
||||
label !== undefined && !label.includes("-verified"),
|
||||
|
||||
@@ -1,72 +1,97 @@
|
||||
import { useFrigateEvents, useMotionActivity } from "@/api/ws";
|
||||
import {
|
||||
useFrigateEvents,
|
||||
useInitialCameraState,
|
||||
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;
|
||||
};
|
||||
import { ObjectType } from "@/types/ws";
|
||||
import useDeepMemo from "./use-deep-memo";
|
||||
|
||||
type useCameraActivityReturn = {
|
||||
activeTracking: boolean;
|
||||
activeMotion: boolean;
|
||||
activeObjects: ActiveObjectType[];
|
||||
objects: ObjectType[];
|
||||
};
|
||||
|
||||
export function useCameraActivity(
|
||||
camera: CameraConfig,
|
||||
): useCameraActivityReturn {
|
||||
const [activeObjects, setActiveObjects] = useState<ActiveObjectType[]>([]);
|
||||
const [objects, setObjects] = useState<ObjectType[]>([]);
|
||||
|
||||
// init camera activity
|
||||
|
||||
const { payload: initialCameraState } = useInitialCameraState(camera.name);
|
||||
|
||||
const updatedCameraState = useDeepMemo(initialCameraState);
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedCameraState) {
|
||||
setObjects(updatedCameraState.objects);
|
||||
}
|
||||
}, [updatedCameraState]);
|
||||
|
||||
// handle camera activity
|
||||
|
||||
const hasActiveObjects = useMemo(
|
||||
() => activeObjects.filter((obj) => !obj.stationary).length > 0,
|
||||
[activeObjects],
|
||||
() => objects.filter((obj) => !obj.stationary).length > 0,
|
||||
[objects],
|
||||
);
|
||||
|
||||
const { payload: detectingMotion } = useMotionActivity(camera.name);
|
||||
const { payload: event } = useFrigateEvents();
|
||||
const updatedEvent = useDeepMemo(event);
|
||||
|
||||
useEffect(() => {
|
||||
if (!event) {
|
||||
if (!updatedEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.after.camera != camera.name) {
|
||||
if (updatedEvent.after.camera != camera.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventIndex = activeObjects.findIndex(
|
||||
(obj) => obj.id === event.after.id,
|
||||
const updatedEventIndex = objects.findIndex(
|
||||
(obj) => obj.id === updatedEvent.after.id,
|
||||
);
|
||||
|
||||
if (event.type == "end") {
|
||||
if (eventIndex != -1) {
|
||||
const newActiveObjects = [...activeObjects];
|
||||
newActiveObjects.splice(eventIndex, 1);
|
||||
setActiveObjects(newActiveObjects);
|
||||
if (updatedEvent.type == "end") {
|
||||
if (updatedEventIndex != -1) {
|
||||
const newActiveObjects = [...objects];
|
||||
newActiveObjects.splice(updatedEventIndex, 1);
|
||||
setObjects(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,
|
||||
if (updatedEventIndex == -1) {
|
||||
// add unknown updatedEvent to list if not stationary
|
||||
if (!updatedEvent.after.stationary) {
|
||||
const newActiveObject: ObjectType = {
|
||||
id: updatedEvent.after.id,
|
||||
label: updatedEvent.after.label,
|
||||
stationary: updatedEvent.after.stationary,
|
||||
};
|
||||
const newActiveObjects = [...activeObjects, newActiveObject];
|
||||
setActiveObjects(newActiveObjects);
|
||||
const newActiveObjects = [...objects, newActiveObject];
|
||||
setObjects(newActiveObjects);
|
||||
}
|
||||
} else {
|
||||
const newObjects = [...objects];
|
||||
newObjects[updatedEventIndex].label =
|
||||
updatedEvent.after.sub_label ?? updatedEvent.after.label;
|
||||
newObjects[updatedEventIndex].stationary =
|
||||
updatedEvent.after.stationary;
|
||||
setObjects(newObjects);
|
||||
}
|
||||
}
|
||||
}, [camera, event, activeObjects]);
|
||||
}, [camera, updatedEvent, objects]);
|
||||
|
||||
return {
|
||||
activeTracking: hasActiveObjects,
|
||||
activeMotion: detectingMotion == "ON",
|
||||
activeObjects,
|
||||
activeMotion: detectingMotion
|
||||
? detectingMotion == "ON"
|
||||
: initialCameraState?.motion == true,
|
||||
objects,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -41,4 +41,15 @@ export interface FrigateEvent {
|
||||
after: FrigateObjectState;
|
||||
}
|
||||
|
||||
export type ObjectType = {
|
||||
id: string;
|
||||
label: string;
|
||||
stationary: boolean;
|
||||
};
|
||||
|
||||
export interface FrigateCameraState {
|
||||
motion: boolean;
|
||||
objects: ObjectType[];
|
||||
}
|
||||
|
||||
export type ToggleableSetting = "ON" | "OFF";
|
||||
|
||||
Reference in New Issue
Block a user