implement auto mode for single camera live view

This commit is contained in:
Josh Hawkins
2026-09-30 10:43:51 -05:00
parent 1bb61eb808
commit 3dddea3d3a
14 changed files with 1155 additions and 164 deletions
+32 -16
View File
@@ -10,6 +10,7 @@ import { MdCircle } from "react-icons/md";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { useCameraActivity } from "@/hooks/use-camera-activity";
import {
LiveHealthSample,
LivePlayerError,
TwoWayTalkError,
LivePlayerMode,
@@ -52,6 +53,8 @@ type LivePlayerProps = {
onClick?: () => void;
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onError?: (error: LivePlayerError) => void;
onHealthSample?: (sample: LiveHealthSample) => void;
streamAuto?: boolean;
onMicrophoneError?: (error: TwoWayTalkError) => void;
onResetLiveMode?: () => void;
onLiveAspectChange?: (aspectRatio: number | undefined) => void;
@@ -79,6 +82,8 @@ export default function LivePlayer({
onClick,
setFullResolution,
onError,
onHealthSample,
streamAuto = false,
onMicrophoneError,
onResetLiveMode,
onLiveAspectChange,
@@ -109,6 +114,14 @@ export default function LivePlayer({
droppedFrameRate: 0, // percentage
});
const streamLabel = useMemo(
() =>
Object.keys(cameraConfig.live.streams).find(
(label) => cameraConfig.live.streams[label] === streamName,
),
[cameraConfig.live.streams, streamName],
);
// camera activity
const {
@@ -230,21 +243,17 @@ export default function LivePlayer({
}, [preferredLiveMode]);
const [key, setKey] = useState(0);
const prevStreamNameRef = useRef(streamName);
const resetPlayer = () => {
setLiveReady(false);
setKey((prevKey) => prevKey + 1);
};
useEffect(() => {
if (prevStreamNameRef.current !== streamName) {
prevStreamNameRef.current = streamName;
if (streamName) {
resetPlayer();
}
// the stream is part of the MSE and WebRTC keys, so a stream change
// remounts them in the same render and the new player is hidden until it
// plays. jsmpeg plays the camera, not the stream, and keeps playing
const [renderedStream, setRenderedStream] = useState(streamName);
if (renderedStream !== streamName) {
setRenderedStream(streamName);
if (preferredLiveMode !== "jsmpeg") {
setLiveReady(false);
}
}, [streamName]);
}
useEffect(() => {
if (showStillWithoutActivity && !autoLive) {
@@ -294,7 +303,7 @@ export default function LivePlayer({
} else if (preferredLiveMode == "webrtc") {
player = (
<WebRtcPlayer
key={"webrtc_" + key}
key={`webrtc_${streamName}_${key}`}
className={`size-full ${liveReady ? "" : "hidden"}`}
camera={streamName}
playbackEnabled={cameraActive || liveReady}
@@ -308,6 +317,7 @@ export default function LivePlayer({
onPlaying={playerIsPlaying}
pip={pip}
onError={onError}
onHealthSample={onHealthSample}
onMicrophoneError={onMicrophoneError}
/>
);
@@ -315,7 +325,7 @@ export default function LivePlayer({
if ("MediaSource" in window || "ManagedMediaSource" in window) {
player = (
<MSEPlayer
key={"mse_" + key}
key={`mse_${streamName}_${key}`}
className={`size-full ${liveReady ? "" : "hidden"}`}
camera={streamName}
playbackEnabled={cameraActive || liveReady}
@@ -328,6 +338,7 @@ export default function LivePlayer({
pip={pip}
setFullResolution={handleFullResolution}
onError={onError}
onHealthSample={onHealthSample}
/>
);
} else {
@@ -561,7 +572,12 @@ export default function LivePlayer({
)}
</div>
{showStats && (
<PlayerStats stats={stats} minimal={cameraRef !== undefined} />
<PlayerStats
stats={stats}
minimal={cameraRef !== undefined}
streamLabel={preferredLiveMode === "jsmpeg" ? undefined : streamLabel}
streamAuto={streamAuto}
/>
)}
</div>
);