diff --git a/web/src/api/ws.tsx b/web/src/api/ws.tsx index 5e347e1d4..831b3dff3 100644 --- a/web/src/api/ws.tsx +++ b/web/src/api/ws.tsx @@ -42,13 +42,17 @@ function useValue(): useValueReturn { const cameraStates: WsState = {}; Object.keys(config.cameras).forEach((camera) => { - const { name, record, detect, snapshots, audio } = config.cameras[camera]; + const { name, record, detect, snapshots, audio, onvif } = + config.cameras[camera]; cameraStates[`${name}/recordings/state`] = record.enabled ? "ON" : "OFF"; cameraStates[`${name}/detect/state`] = detect.enabled ? "ON" : "OFF"; cameraStates[`${name}/snapshots/state`] = snapshots.enabled ? "ON" : "OFF"; cameraStates[`${name}/audio/state`] = audio.enabled ? "ON" : "OFF"; + cameraStates[`${name}/ptz_autotracker/state`] = onvif.autotracking.enabled + ? "ON" + : "OFF"; }); setWsState({ ...wsState, ...cameraStates }); @@ -161,6 +165,17 @@ export function useAudioState(camera: string): { return { payload: payload as ToggleableSetting, send }; } +export function useAutotrackingState(camera: string): { + payload: ToggleableSetting; + send: (payload: ToggleableSetting, retain?: boolean) => void; +} { + const { + value: { payload }, + send, + } = useWs(`${camera}/ptz_autotracker/state`, `${camera}/ptz_autotracker/set`); + return { payload: payload as ToggleableSetting, send }; +} + export function usePtzCommand(camera: string): { payload: string; send: (payload: string, retain?: boolean) => void; diff --git a/web/src/components/player/MsePlayer.tsx b/web/src/components/player/MsePlayer.tsx index 63f78006f..b8ab2da66 100644 --- a/web/src/components/player/MsePlayer.tsx +++ b/web/src/components/player/MsePlayer.tsx @@ -305,6 +305,7 @@ function MSEPlayer({ onLoadedData={onPlaying} onLoadedMetadata={handleLoadedMetadata} muted={!audioEnabled} + onError={onClose} /> ); } diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index e3c1801e6..2f6bb497f 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -1,5 +1,6 @@ import { useAudioState, + useAutotrackingState, useDetectState, usePtzCommand, useRecordingsState, @@ -50,7 +51,7 @@ import { FaMicrophoneSlash, } from "react-icons/fa"; import { GiSpeaker, GiSpeakerOff } from "react-icons/gi"; -import { HiViewfinderCircle } from "react-icons/hi2"; +import { TbViewfinder, TbViewfinderOff } from "react-icons/tb"; import { IoMdArrowRoundBack } from "react-icons/io"; import { LuEar, @@ -326,6 +327,9 @@ export default function LiveCameraView({ camera }: LiveCameraViewProps) { @@ -534,7 +538,7 @@ function PtzControlPanel({ className={`${clickOverlay ? "text-selected" : "text-primary"}`} onClick={() => setClickOverlay(!clickOverlay)} > - + )} @@ -566,11 +570,13 @@ function PtzControlPanel({ type FrigateCameraFeaturesProps = { camera: string; audioDetectEnabled: boolean; + autotrackingEnabled: boolean; fullscreen: boolean; }; function FrigateCameraFeatures({ camera, audioDetectEnabled, + autotrackingEnabled, fullscreen, }: FrigateCameraFeaturesProps) { const { payload: detectState, send: sendDetect } = useDetectState(camera); @@ -578,6 +584,8 @@ function FrigateCameraFeatures({ const { payload: snapshotState, send: sendSnapshot } = useSnapshotsState(camera); const { payload: audioState, send: sendAudio } = useAudioState(camera); + const { payload: autotrackingState, send: sendAutotracking } = + useAutotrackingState(camera); // desktop shows icons part of row if (isDesktop) { @@ -617,6 +625,18 @@ function FrigateCameraFeatures({ onClick={() => sendAudio(audioState == "ON" ? "OFF" : "ON")} /> )} + {autotrackingEnabled && ( + + sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON") + } + /> + )} ); } @@ -662,6 +682,15 @@ function FrigateCameraFeatures({ onCheckedChange={() => sendAudio(audioState == "ON" ? "OFF" : "ON")} /> )} + {autotrackingEnabled && ( + + sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON") + } + /> + )} );