diff --git a/web/src/components/menu/LiveContextMenu.tsx b/web/src/components/menu/LiveContextMenu.tsx index f5222592d..81be53f58 100644 --- a/web/src/components/menu/LiveContextMenu.tsx +++ b/web/src/components/menu/LiveContextMenu.tsx @@ -82,7 +82,7 @@ export default function LiveContextMenu({ ); useEffect(() => { - if (cameraGroup) { + if (cameraGroup && cameraGroup != "default") { setGroupStreamingSettings(allGroupsStreamingSettings[cameraGroup]); } // set individual group when all groups changes @@ -91,7 +91,12 @@ export default function LiveContextMenu({ const onSave = useCallback( (settings: GroupStreamingSettings) => { - if (!cameraGroup || !allGroupsStreamingSettings) { + if ( + !cameraGroup || + !allGroupsStreamingSettings || + cameraGroup == "default" || + !settings + ) { return; } diff --git a/web/src/views/live/LiveDashboardView.tsx b/web/src/views/live/LiveDashboardView.tsx index 363405023..89a2aeef2 100644 --- a/web/src/views/live/LiveDashboardView.tsx +++ b/web/src/views/live/LiveDashboardView.tsx @@ -14,7 +14,11 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { usePersistence } from "@/hooks/use-persistence"; -import { CameraConfig, FrigateConfig } from "@/types/frigateConfig"; +import { + AllGroupsStreamingSettings, + CameraConfig, + FrigateConfig, +} from "@/types/frigateConfig"; import { ReviewSegment } from "@/types/review"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { @@ -38,6 +42,7 @@ import { FaCompress, FaExpand } from "react-icons/fa"; import useCameraLiveMode from "@/hooks/use-camera-live-mode"; import { useResizeObserver } from "@/hooks/resize-observer"; import LiveContextMenu from "@/components/menu/LiveContextMenu"; +import { useStreamingSettings } from "@/context/streaming-settings-provider"; type LiveDashboardViewProps = { cameras: CameraConfig[]; @@ -135,8 +140,6 @@ export default function LiveDashboardView({ // camera live views - const [autoLiveView] = usePersistence("autoLiveView", true); - const [{ height: containerHeight }] = useResizeObserver(containerRef); const hasScrollbar = useMemo(() => { @@ -198,6 +201,17 @@ export default function LiveDashboardView({ supportsAudioOutputStates, } = useCameraLiveMode(cameras, windowVisible); + const [globalAutoLive] = usePersistence("autoLiveView", true); + + const { allGroupsStreamingSettings, setAllGroupsStreamingSettings } = + useStreamingSettings(); + + const currentGroupStreamingSettings = useMemo(() => { + if (cameraGroup && cameraGroup != "default" && allGroupsStreamingSettings) { + return allGroupsStreamingSettings[cameraGroup]; + } + }, [allGroupsStreamingSettings, cameraGroup]); + const cameraRef = useCallback( (node: HTMLElement | null) => { if (!visibleCameraObserver.current) { @@ -245,6 +259,25 @@ export default function LiveDashboardView({ })); }; + useEffect(() => { + if (!allGroupsStreamingSettings) { + return; + } + + const initialAudioStates: AudioState = {}; + const initialVolumeStates: VolumeState = {}; + + Object.entries(allGroupsStreamingSettings).forEach(([_, groupSettings]) => { + Object.entries(groupSettings).forEach(([camera, cameraSettings]) => { + initialAudioStates[camera] = cameraSettings.playAudio ?? false; + initialVolumeStates[camera] = cameraSettings.volume ?? 1; + }); + }); + + setAudioStates(initialAudioStates); + setVolumeStates(initialVolumeStates); + }, [allGroupsStreamingSettings]); + const toggleAudio = (cameraName: string): void => { setAudioStates((prev) => ({ ...prev, @@ -252,12 +285,53 @@ export default function LiveDashboardView({ })); }; + const onSaveMuting = useCallback( + (playAudio: boolean) => { + if ( + !cameraGroup || + !allGroupsStreamingSettings || + cameraGroup == "default" + ) { + return; + } + + const existingGroupSettings = + allGroupsStreamingSettings[cameraGroup] || {}; + + const updatedSettings: AllGroupsStreamingSettings = { + ...Object.fromEntries( + Object.entries(allGroupsStreamingSettings || {}).filter( + ([key]) => key !== cameraGroup, + ), + ), + [cameraGroup]: { + ...existingGroupSettings, + ...Object.fromEntries( + Object.entries(existingGroupSettings).map( + ([cameraName, settings]) => [ + cameraName, + { + ...settings, + playAudio: playAudio, + }, + ], + ), + ), + }, + }; + + setAllGroupsStreamingSettings?.(updatedSettings); + }, + [cameraGroup, allGroupsStreamingSettings, setAllGroupsStreamingSettings], + ); + const muteAll = (): void => { const updatedStates: Record = {}; visibleCameras.forEach((cameraName) => { updatedStates[cameraName] = false; }); setAudioStates(updatedStates); + onSaveMuting(false); }; const unmuteAll = (): void => { @@ -266,6 +340,7 @@ export default function LiveDashboardView({ updatedStates[cameraName] = true; }); setAudioStates(updatedStates); + onSaveMuting(true); }; return ( @@ -392,19 +467,30 @@ export default function LiveDashboardView({ } else { grow = "aspect-video"; } + const streamName = + currentGroupStreamingSettings?.[camera.name]?.streamName || + Object.values(camera.live.streams)?.[0]; + const autoLive = + currentGroupStreamingSettings?.[camera.name]?.streamType !== + "no-streaming"; + const showStillWithoutActivity = + currentGroupStreamingSettings?.[camera.name]?.streamType !== + "continuous"; + const useWebGL = + currentGroupStreamingSettings?.[camera.name] + ?.compatibilityMode || false; return ( toggleAudio(camera.name)} @@ -431,11 +517,12 @@ export default function LiveDashboardView({ } cameraConfig={camera} preferredLiveMode={preferredLiveModes[camera.name] ?? "mse"} - autoLive={autoLiveView} - useWebGL={false} + autoLive={autoLive ?? globalAutoLive} + showStillWithoutActivity={showStillWithoutActivity ?? true} + useWebGL={useWebGL} playInBackground={false} showStats={statsStates[camera.name]} - streamName={Object.values(camera.live.streams)[0]} + streamName={streamName} onClick={() => onSelectCamera(camera.name)} onError={(e) => handleError(camera.name, e)} onResetLiveMode={() => resetPreferredLiveMode(camera.name)}