diff --git a/frigate/comms/ws.py b/frigate/comms/ws.py
index 57a378c84..65b2e8b1a 100644
--- a/frigate/comms/ws.py
+++ b/frigate/comms/ws.py
@@ -52,7 +52,7 @@ class WebSocketClient(Communicator): # type: ignore[misc]
def opened(self) -> None:
"""A new websocket is opened, we need to send an update message"""
- self.receiver("onConnect", "")
+ threading.Timer(1.0, self.receiver, ("onConnect", "")).start()
def received_message(self, message: WebSocket.received_message) -> None:
try:
diff --git a/frigate/object_processing.py b/frigate/object_processing.py
index 49700f651..0e83e1ec1 100644
--- a/frigate/object_processing.py
+++ b/frigate/object_processing.py
@@ -727,8 +727,9 @@ class CameraState:
# TODO: can i switch to looking this up and only changing when an event ends?
# maintain best objects
- camera_activity = {
+ camera_activity: dict[str, list[any]] = {
"motion": len(motion_boxes) > 0,
+ "objects": [],
}
for obj in tracked_objects.values():
@@ -747,13 +748,9 @@ class CameraState:
):
label = obj.obj_data["sub_label"]
- if label not in camera_activity:
- camera_activity[label] = {
- "active": 1 if active else 0,
- "stationary": 1 if not active else 0,
- }
- else:
- camera_activity[label]["active" if active else "stationary"] += 1
+ camera_activity["objects"].append(
+ {"id": obj.obj_data["id"], "label": label, "stationary": not active}
+ )
# if the object's thumbnail is not from the current frame
if obj.false_positive or obj.thumbnail_data["frame_time"] != frame_time:
diff --git a/web/src/api/ws.tsx b/web/src/api/ws.tsx
index 2439dbb33..abe5c14b2 100644
--- a/web/src/api/ws.tsx
+++ b/web/src/api/ws.tsx
@@ -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 },
diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx
index d42ecb3a0..bffac32cb 100644
--- a/web/src/components/player/LivePlayer.tsx
+++ b/web/src/components/player/LivePlayer.tsx
@@ -46,7 +46,7 @@ export default function LivePlayer({
// camera activity
- const { activeMotion, activeTracking, activeObjects } =
+ const { activeMotion, activeTracking, objects } =
useCameraActivity(cameraConfig);
const cameraActive = useMemo(
@@ -163,7 +163,7 @@ export default function LivePlayer({
{player}
- {activeObjects.length > 0 && (
+ {objects.length > 0 && (
@@ -174,7 +174,7 @@ export default function LivePlayer({
>
{[
...new Set([
- ...(activeObjects || []).map(({ label }) => label),
+ ...(objects || []).map(({ label }) => label),
]),
]
.map((label) => {
@@ -186,11 +186,7 @@ export default function LivePlayer({
- {[
- ...new Set([
- ...(activeObjects || []).map(({ label }) => label),
- ]),
- ]
+ {[...new Set([...(objects || []).map(({ label }) => label)])]
.filter(
(label) =>
label !== undefined && !label.includes("-verified"),
diff --git a/web/src/hooks/use-camera-activity.ts b/web/src/hooks/use-camera-activity.ts
index 61a4f6299..ef065dd81 100644
--- a/web/src/hooks/use-camera-activity.ts
+++ b/web/src/hooks/use-camera-activity.ts
@@ -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([]);
+ const [objects, setObjects] = useState([]);
+
+ // 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,
};
}
diff --git a/web/src/types/ws.ts b/web/src/types/ws.ts
index 177d3600a..fb7a963c4 100644
--- a/web/src/types/ws.ts
+++ b/web/src/types/ws.ts
@@ -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";