diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 38c66f630..ed00f0ad6 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -411,6 +411,13 @@ "debugging": "Debugging", "objectList": "Object List", "noObjects": "No objects", + "audio": { + "title": "Audio", + "noAudioDetections": "No audio detections", + "score": "score", + "currentRMS": "Current RMS", + "currentdbFS": "Current dbFS" + }, "boundingBoxes": { "title": "Bounding boxes", "desc": "Show bounding boxes around tracked objects", diff --git a/web/src/views/settings/ObjectSettingsView.tsx b/web/src/views/settings/ObjectSettingsView.tsx index bd47b01bc..cb2a63121 100644 --- a/web/src/views/settings/ObjectSettingsView.tsx +++ b/web/src/views/settings/ObjectSettingsView.tsx @@ -16,7 +16,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { ObjectType } from "@/types/ws"; +import { AudioDetection, ObjectType } from "@/types/ws"; import useDeepMemo from "@/hooks/use-deep-memo"; import { Card } from "@/components/ui/card"; import { getIconForLabel } from "@/utils/iconUtil"; @@ -30,6 +30,8 @@ import { isDesktop } from "react-device-detect"; import { Trans, useTranslation } from "react-i18next"; import { useDocDomain } from "@/hooks/use-doc-domain"; import { getTranslatedLabel } from "@/utils/i18n"; +import { AudioLevelGraph } from "@/components/audio/AudioLevelGraph"; +import { useWs } from "@/api/ws"; type ObjectSettingsViewProps = { selectedCamera?: string; @@ -126,9 +128,12 @@ export default function ObjectSettingsView({ } }, [config, selectedCamera]); - const { objects } = useCameraActivity(cameraConfig ?? ({} as CameraConfig)); + const { objects, audio_detections } = useCameraActivity( + cameraConfig ?? ({} as CameraConfig), + ); const memoizedObjects = useDeepMemo(objects); + const memoizedAudio = useDeepMemo(audio_detections); const searchParams = useMemo(() => { if (!optionsLoaded) { @@ -189,11 +194,18 @@ export default function ObjectSettingsView({ )} - + input.roles.includes("audio")) ? "grid-cols-3" : "grid-cols-2"}`} + > {t("debug.debugging")} {t("debug.objectList")} + {cameraConfig.ffmpeg.inputs.some((input) => + input.roles.includes("audio"), + ) && ( + {t("debug.audio.title")} + )}
@@ -304,6 +316,16 @@ export default function ObjectSettingsView({ + {cameraConfig.ffmpeg.inputs.some((input) => + input.roles.includes("audio"), + ) && ( + + + + )}
@@ -362,7 +384,7 @@ function ObjectList({ cameraConfig, objects }: ObjectListProps) { return (
{objects && objects.length > 0 ? ( - objects.map((obj) => { + objects.map((obj: ObjectType) => { return (
@@ -438,3 +460,61 @@ function ObjectList({ cameraConfig, objects }: ObjectListProps) {
); } + +type AudioListProps = { + cameraConfig: CameraConfig; + audioDetections?: AudioDetection[]; +}; + +function AudioList({ cameraConfig, audioDetections }: AudioListProps) { + const { t } = useTranslation(["views/settings"]); + + // Get audio levels directly from ws hooks + const { + value: { payload: audioRms }, + } = useWs(`${cameraConfig.name}/audio/rms`, ""); + const { + value: { payload: audioDBFS }, + } = useWs(`${cameraConfig.name}/audio/dBFS`, ""); + + return ( +
+ {audioDetections && Object.keys(audioDetections).length > 0 ? ( + Object.entries(audioDetections).map(([key, obj]) => ( + +
+
+
+ {getIconForLabel(key, "size-5 text-white")} +
+
{getTranslatedLabel(key)}
+
+
+
+
+

+ {t("debug.objectShapeFilterDrawing.score")} +

+ {obj.score ? (obj.score * 100).toFixed(1).toString() : "-"}% +
+
+
+
+
+ )) + ) : ( +
+

{t("debug.audio.noAudioDetections")}

+

+ {t("debug.audio.currentRMS")}{" "} + {(typeof audioRms === "number" ? audioRms : 0).toFixed(1)} |{" "} + {t("debug.audio.currentdbFS")}{" "} + {(typeof audioDBFS === "number" ? audioDBFS : 0).toFixed(1)} +

+
+ )} + + +
+ ); +}