diff --git a/web/src/components/player/LivePlayer.tsx b/web/src/components/player/LivePlayer.tsx index 1009f03bc..af0511ac1 100644 --- a/web/src/components/player/LivePlayer.tsx +++ b/web/src/components/player/LivePlayer.tsx @@ -48,6 +48,9 @@ type LivePlayerProps = { pip?: boolean; autoLive?: boolean; showStats?: boolean; + onStatsUpdate?: (stats: PlayerStatsType) => void; + onLoadingChange?: (loading: boolean) => void; + onActiveMotionChange?: (active: boolean) => void; onClick?: () => void; setFullResolution?: React.Dispatch>; onError?: (error: LivePlayerError) => void; @@ -73,6 +76,9 @@ export default function LivePlayer({ pip, autoLive = true, showStats = false, + onStatsUpdate, + onLoadingChange, + onActiveMotionChange, onClick, setFullResolution, onError, @@ -105,6 +111,10 @@ export default function LivePlayer({ droppedFrameRate: 0, // percentage }); + useEffect(() => { + onStatsUpdate?.(stats); + }, [stats, onStatsUpdate]); + // camera activity const { @@ -274,6 +284,42 @@ export default function LivePlayer({ } }, [liveReady, isReEnabling]); + useEffect(() => { + if (!onLoadingChange) return; + const loading = !!( + cameraEnabled && + !offline && + (!showStillWithoutActivity || isReEnabling) && + !liveReady + ); + onLoadingChange(loading); + }, [ + onLoadingChange, + cameraEnabled, + offline, + showStillWithoutActivity, + isReEnabling, + liveReady, + ]); + + useEffect(() => { + if (!onActiveMotionChange) return; + const motionVisible = !!( + autoLive && + !offline && + activeMotion && + ((showStillWithoutActivity && !liveReady) || liveReady) + ); + onActiveMotionChange(motionVisible); + }, [ + onActiveMotionChange, + autoLive, + offline, + activeMotion, + showStillWithoutActivity, + liveReady, + ]); + if (!cameraConfig) { return ; } @@ -407,7 +453,8 @@ export default function LivePlayer({ /> - {cameraEnabled && + {!onLoadingChange && + cameraEnabled && !offline && (!showStillWithoutActivity || isReEnabling) && !liveReady && } @@ -530,14 +577,15 @@ export default function LivePlayer({ {cameraName} )} - {autoLive && + {!onActiveMotionChange && + autoLive && !offline && activeMotion && ((showStillWithoutActivity && !liveReady) || liveReady) && ( )} - {showStats && ( + {showStats && !onStatsUpdate && ( )} diff --git a/web/src/views/live/DraggableGridLayout.tsx b/web/src/views/live/DraggableGridLayout.tsx index 522787bcf..5cf596cfc 100644 --- a/web/src/views/live/DraggableGridLayout.tsx +++ b/web/src/views/live/DraggableGridLayout.tsx @@ -23,9 +23,13 @@ import { AudioState, LivePlayerMode, LiveStreamMetadata, + PlayerStatsType, StatsState, VolumeState, } from "@/types/live"; +import ActivityIndicator from "@/components/indicators/activity-indicator"; +import { PlayerStats } from "@/components/player/PlayerStats"; +import { MdCircle } from "react-icons/md"; import { Skeleton } from "@/components/ui/skeleton"; import { isEqual } from "lodash"; @@ -431,6 +435,15 @@ export default function DraggableGridLayout({ const [cameraZoomStates, setCameraZoomStates] = useState< Record >({}); + const [cameraStatsData, setCameraStatsData] = useState< + Record + >({}); + const [cameraLoadingStates, setCameraLoadingStates] = useState< + Record + >({}); + const [cameraMotionStates, setCameraMotionStates] = useState< + Record + >({}); const cameraZoomViewportRefs = useRef>( {}, ); @@ -808,7 +821,7 @@ export default function DraggableGridLayout({ streamMetadata={streamMetadata} >
{ cameraZoomViewportRefs.current[camera.name] = node; @@ -855,7 +868,25 @@ export default function DraggableGridLayout({ preferredLiveModes[camera.name] ?? "mse" } playInBackground={false} - showStats={statsStates[camera.name] ?? true} + showStats={false} + onStatsUpdate={(stats) => + setCameraStatsData((prev) => ({ + ...prev, + [camera.name]: stats, + })) + } + onLoadingChange={(loading) => + setCameraLoadingStates((prev) => ({ + ...prev, + [camera.name]: loading, + })) + } + onActiveMotionChange={(active) => + setCameraMotionStates((prev) => ({ + ...prev, + [camera.name]: active, + })) + } onClick={() => { !isEditMode && onSelectCamera(camera.name); }} @@ -875,6 +906,23 @@ export default function DraggableGridLayout({ volume={volumeStates[camera.name]} />
+ {cameraLoadingStates[camera.name] && ( +
+ +
+ )} + {statsStates[camera.name] && + cameraStatsData[camera.name] && ( + + )} + {cameraMotionStates[camera.name] && ( +
+ +
+ )} {isEditMode && showCircles && }