diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 8038812db..4be653667 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -26,6 +26,7 @@ type LivePlayerProps = { containerRef?: React.MutableRefObject; className?: string; cameraConfig: CameraConfig; + streamName: string; preferredLiveMode: LivePlayerMode; showStillWithoutActivity?: boolean; windowVisible?: boolean; @@ -45,6 +46,7 @@ export default function LivePlayer({ containerRef, className, cameraConfig, + streamName, preferredLiveMode, showStillWithoutActivity = true, windowVisible = true, @@ -144,6 +146,19 @@ export default function LivePlayer({ setLiveReady(false); }, [preferredLiveMode]); + const [key, setKey] = useState(0); + + const resetPlayer = () => { + setLiveReady(false); + setKey((prevKey) => prevKey + 1); + }; + + useEffect(() => { + if (streamName) { + resetPlayer(); + } + }, [streamName]); + const playerIsPlaying = useCallback(() => { setLiveReady(true); }, []); @@ -153,13 +168,14 @@ export default function LivePlayer({ } let player; - if (!autoLive) { + if (!autoLive || !streamName) { player = null; } else if (preferredLiveMode == "webrtc") { player = ( ; diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index a3bbeea06..2f8d282a7 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -57,7 +57,7 @@ import { } from "react-icons/fa"; import { GiSpeaker, GiSpeakerOff } from "react-icons/gi"; import { TbViewfinder, TbViewfinderOff } from "react-icons/tb"; -import { IoMdArrowRoundBack } from "react-icons/io"; +import { IoIosWarning, IoMdArrowRoundBack } from "react-icons/io"; import { LuEar, LuEarOff, @@ -79,6 +79,20 @@ import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import useSWR from "swr"; import { cn } from "@/lib/utils"; import { useSessionPersistence } from "@/hooks/use-session-persistence"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, +} from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { usePersistence } from "@/hooks/use-persistence"; +import { TooltipPortal } from "@radix-ui/react-tooltip"; type LiveCameraViewProps = { config?: FrigateConfig; @@ -103,17 +117,20 @@ export default function LiveCameraView({ // supported features + const [streamName, setStreamName] = usePersistence( + `${camera.name}-stream`, + Object.values(camera.live.streams)[0], + ); + const isRestreamed = useMemo( () => config && - Object.keys(config.go2rtc.streams || {}).includes( - camera.live.stream_name, - ), - [camera, config], + Object.keys(config.go2rtc.streams || {}).includes(streamName ?? ""), + [config, streamName], ); const { data: cameraMetadata } = useSWR( - isRestreamed ? `go2rtc/streams/${camera.live.stream_name}` : null, + isRestreamed ? `go2rtc/streams/${streamName}` : null, { revalidateOnFocus: false, }, @@ -454,13 +471,16 @@ export default function LiveCameraView({ /> )} @@ -496,6 +516,7 @@ export default function LiveCameraView({ micEnabled={mic} iOSCompatFullScreen={isIOS} preferredLiveMode={preferredLiveMode} + streamName={streamName ?? ""} pip={pip} containerRef={containerRef} setFullResolution={setFullResolution} @@ -749,11 +770,14 @@ function PtzControlPanel({ } type FrigateCameraFeaturesProps = { - camera: string; + camera: CameraConfig; recordingEnabled: boolean; audioDetectEnabled: boolean; autotrackingEnabled: boolean; fullscreen: boolean; + streamName: string; + setStreamName?: (value: string | undefined) => void; + preferredLiveMode: string; }; function FrigateCameraFeatures({ camera, @@ -761,14 +785,22 @@ function FrigateCameraFeatures({ audioDetectEnabled, autotrackingEnabled, fullscreen, + streamName, + setStreamName, + preferredLiveMode, }: FrigateCameraFeaturesProps) { - const { payload: detectState, send: sendDetect } = useDetectState(camera); - const { payload: recordState, send: sendRecord } = useRecordingsState(camera); - const { payload: snapshotState, send: sendSnapshot } = - useSnapshotsState(camera); - const { payload: audioState, send: sendAudio } = useAudioState(camera); + const { payload: detectState, send: sendDetect } = useDetectState( + camera.name, + ); + const { payload: recordState, send: sendRecord } = useRecordingsState( + camera.name, + ); + const { payload: snapshotState, send: sendSnapshot } = useSnapshotsState( + camera.name, + ); + const { payload: audioState, send: sendAudio } = useAudioState(camera.name); const { payload: autotrackingState, send: sendAutotracking } = - useAutotrackingState(camera); + useAutotrackingState(camera.name); // desktop shows icons part of row if (isDesktop || isTablet) { @@ -820,6 +852,47 @@ function FrigateCameraFeatures({ } /> )} + {Object.values(camera.live.streams).length > 1 && ( + + )} ); } @@ -878,6 +951,40 @@ function FrigateCameraFeatures({ } /> )} + {Object.values(camera.live.streams).length > 1 && ( +
+
Live stream selection
+ +
+ )} );