Files
frigate/web/src/components/camera/DynamicCameraImage.tsx
Nicolas Mowen f4310862aa WebUI Improvements and fixes (#9613)
* Show toast instead of text for success and errors

* Show correct times

* Start playing next hour when current hour ends

* Fix refreshing camera image

* Fix timeline
2024-02-05 17:54:08 -06:00

121 lines
3.2 KiB
TypeScript

import { useCallback, useEffect, useMemo, useState } from "react";
import { AspectRatio } from "../ui/aspect-ratio";
import CameraImage from "./CameraImage";
import { LuEar } from "react-icons/lu";
import { CameraConfig } from "@/types/frigateConfig";
import { TbUserScan } from "react-icons/tb";
import { MdLeakAdd } from "react-icons/md";
import {
useAudioActivity,
useFrigateEvents,
useMotionActivity,
} from "@/api/ws";
type DynamicCameraImageProps = {
camera: CameraConfig;
aspect: number;
};
const INTERVAL_INACTIVE_MS = 60000; // refresh once a minute
const INTERVAL_ACTIVE_MS = 1000; // refresh once a second
export default function DynamicCameraImage({
camera,
aspect,
}: DynamicCameraImageProps) {
const [key, setKey] = useState(Date.now());
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | undefined>(
undefined
);
const [activeObjects, setActiveObjects] = useState<string[]>([]);
const hasActiveObjects = useMemo(
() => activeObjects.length > 0,
[activeObjects]
);
const { payload: detectingMotion } = useMotionActivity(camera.name);
const { payload: event } = useFrigateEvents();
const { payload: audioRms } = useAudioActivity(camera.name);
useEffect(() => {
if (!event) {
return;
}
if (event.after.camera != camera.name) {
return;
}
if (event.type == "end") {
const eventIndex = activeObjects.indexOf(event.after.id);
if (eventIndex != -1) {
const newActiveObjects = [...activeObjects];
newActiveObjects.splice(eventIndex, 1);
setActiveObjects(newActiveObjects);
}
} else {
if (!event.after.stationary) {
const eventIndex = activeObjects.indexOf(event.after.id);
if (eventIndex == -1) {
const newActiveObjects = [...activeObjects, event.after.id];
setActiveObjects(newActiveObjects);
clearTimeout(timeoutId);
setKey(Date.now());
}
}
}
}, [event, activeObjects]);
const handleLoad = useCallback(() => {
const loadTime = Date.now() - key;
const loadInterval = hasActiveObjects
? INTERVAL_ACTIVE_MS
: INTERVAL_INACTIVE_MS;
const tId = setTimeout(
() => {
setKey(Date.now());
},
loadTime > loadInterval ? 1 : loadInterval
);
setTimeoutId(tId);
}, [key]);
return (
<AspectRatio
ratio={aspect}
className="bg-black flex justify-center items-center relative"
>
<CameraImage
camera={camera.name}
fitAspect={aspect}
searchParams={`cache=${key}`}
onload={handleLoad}
/>
<div className="flex absolute right-0 bottom-0 bg-black bg-opacity-20 rounded p-1">
<MdLeakAdd
className={`${
detectingMotion == "ON" ? "text-motion" : "text-gray-600"
}`}
/>
<TbUserScan
className={`${
activeObjects.length > 0 ? "text-object" : "text-gray-600"
}`}
/>
{camera.audio.enabled_in_config && (
<LuEar
className={`${
parseInt(audioRms) >= camera.audio.min_volume
? "text-audio"
: "text-gray-600"
}`}
/>
)}
</div>
</AspectRatio>
);
}