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
+9 -2
View File
@@ -11,6 +11,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { useCameraActivity } from "@/hooks/use-camera-activity";
import {
LivePlayerError,
TwoWayTalkError,
LivePlayerMode,
PlayerStatsType,
VideoResolutionType,
@@ -51,6 +52,7 @@ type LivePlayerProps = {
onClick?: () => void;
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
onError?: (error: LivePlayerError) => void;
onMicrophoneError?: (error: TwoWayTalkError) => void;
onResetLiveMode?: () => void;
};
@@ -76,6 +78,7 @@ export default function LivePlayer({
onClick,
setFullResolution,
onError,
onMicrophoneError,
onResetLiveMode,
}: LivePlayerProps) {
const { t } = useTranslation(["components/player"]);
@@ -98,7 +101,6 @@ export default function LivePlayer({
const [stats, setStats] = useState<PlayerStatsType>({
streamType: "-",
bandwidth: 0, // in kBps
latency: undefined, // in seconds
totalFrames: 0,
droppedFrames: undefined,
decodedFrames: 0,
@@ -272,6 +274,7 @@ export default function LivePlayer({
onPlaying={playerIsPlaying}
pip={pip}
onError={onError}
onMicrophoneError={onMicrophoneError}
/>
);
} else if (preferredLiveMode == "mse") {
@@ -363,7 +366,11 @@ export default function LivePlayer({
{cameraEnabled &&
!offline &&
(!showStillWithoutActivity || isReEnabling) &&
!liveReady && <ActivityIndicator />}
!liveReady && (
<div className="absolute inset-0 flex items-center justify-center">
<ActivityIndicator />
</div>
)}
{((showStillWithoutActivity && !liveReady) || liveReady) &&
objects.length > 0 && (