Live streaming tech selection (#24374)

* allow users to select live streaming technology

* fix webrtc being downgraded to mse on load

`useUserPersistence` seeds state with the default and loads asynchronously, so the first render always used `mse` instead of the saved choice, and `useWebRTCGloballyAvailable` reports `checking` until the probe settles and re-enters that state on every consumer mount, so a saved `webrtc` was rewritten to `mse` even after the probe had already passed. On Safari the MSE player then timed out and latched the jsmpeg fallback. A pending probe now counts as available, the player waits on `autoLive` until the stored preferences load, and `handleError` gates on the mode in use since the fallback flag no longer implies webrtc is untried. A rejected IndexedDB read also resolves `loaded` now, so a blocked store can't leave the player waiting forever.

* add support for configurable ICE servers in WebRTC player

* add mic error state, fix dialog overwriting saved choice and dashboard ignoring stream

* tweaks
This commit is contained in:
Josh Hawkins
2026-09-16 09:12:06 -05:00
committed by GitHub
parent 79ca18d439
commit 64d6366ac4
26 changed files with 1940 additions and 283 deletions
+16 -4
View File
@@ -43,6 +43,7 @@ import { cn } from "@/lib/utils";
import {
AudioState,
LivePlayerError,
LivePlayerMode,
StatsState,
VolumeState,
} from "@/types/live";
@@ -271,6 +272,16 @@ export default function LiveDashboardView({
return streams;
}, [cameras, currentGroupStreamingSettings]);
// Per-camera streaming-technology choice from the camera group settings.
const preferredModes = useMemo(() => {
const modes: { [cameraName: string]: LivePlayerMode | undefined } = {};
cameras.forEach((camera) => {
modes[camera.name] =
currentGroupStreamingSettings?.[camera.name]?.playerMode;
});
return modes;
}, [cameras, currentGroupStreamingSettings]);
const {
preferredLiveModes,
setPreferredLiveModes,
@@ -278,7 +289,8 @@ export default function LiveDashboardView({
isRestreamedStates,
supportsAudioOutputStates,
streamMetadata,
} = useCameraLiveMode(cameras, windowVisible, activeStreams);
webRTCUsableStates,
} = useCameraLiveMode(cameras, windowVisible, activeStreams, preferredModes);
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
@@ -286,7 +298,7 @@ export default function LiveDashboardView({
(cameraName: string, error: LivePlayerError) => {
setPreferredLiveModes((prevModes) => {
const newModes = { ...prevModes };
if (error === "mse-decode") {
if (error === "mse-decode" && webRTCUsableStates[cameraName]) {
newModes[cameraName] = "webrtc";
} else {
newModes[cameraName] = "jsmpeg";
@@ -294,7 +306,7 @@ export default function LiveDashboardView({
return newModes;
});
},
[setPreferredLiveModes],
[setPreferredLiveModes, webRTCUsableStates],
);
// audio states
@@ -673,7 +685,7 @@ export default function LiveDashboardView({
fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen}
preferredLiveModes={preferredLiveModes}
setPreferredLiveModes={setPreferredLiveModes}
handleError={handleError}
resetPreferredLiveMode={resetPreferredLiveMode}
isRestreamedStates={isRestreamedStates}
supportsAudioOutputStates={supportsAudioOutputStates}