From 85a2731510051c5a34357d2a2086cb3aa495f994 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 28 Sep 2026 08:50:56 -0500 Subject: [PATCH] fix two-way talk on cameras with AAC audio The mic button was enabled whenever WebRTC was globally available, but the live view only switched to the WebRTC player when the stream itself qualified for WebRTC, and AAC playback audio disqualifies it. On most cameras the mic showed as on while nothing was sent. Two-way talk only needs the stream's video to connect since the backchannel is sent, not received, so AAC playback audio no longer blocks it. The mic is also turned off when a stream switch makes talk unavailable. --- web/src/views/live/LiveCameraView.tsx | 43 +++++++++++++++++---------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/web/src/views/live/LiveCameraView.tsx b/web/src/views/live/LiveCameraView.tsx index cddcba1005..869bbb9ca3 100644 --- a/web/src/views/live/LiveCameraView.tsx +++ b/web/src/views/live/LiveCameraView.tsx @@ -27,10 +27,7 @@ import { } from "@/components/ui/popover"; import { useResizeObserver } from "@/hooks/resize-observer"; import useKeyboardListener from "@/hooks/use-keyboard-listener"; -import { - useWebRTCAvailableForStream, - useWebRTCGloballyAvailable, -} from "@/hooks/use-webrtc-availability"; +import { useWebRTCAvailableForStream } from "@/hooks/use-webrtc-availability"; import { CameraConfig, FrigateConfig } from "@/types/frigateConfig"; import { LivePlayerError, @@ -219,15 +216,16 @@ export default function LiveCameraView({ ); const isWebRTCAvailable = webRTCAvailability.available; - // Two-way talk is the sendonly backchannel: global, not per-stream. - const { globallyAvailable: webRTCGloballyAvailable } = - useWebRTCGloballyAvailable(); - // "checking" means the probe has not answered yet, and it re-enters that on // every mount, so treating it as unavailable downgrades the saved choice. const webRTCVerdictPending = webRTCAvailability.reason === "checking"; const webRTCUsable = isWebRTCAvailable || webRTCVerdictPending; + // Two-way talk is a sendonly backchannel, so playback audio that WebRTC + // can't carry doesn't rule it out, but the stream's video must connect + const talkAvailable = + isWebRTCAvailable || webRTCAvailability.reason === "audio-codec"; + // Resolves the saved preference without overwriting it. Transient error // fallbacks layer on top in preferredLiveMode. const resolvedUserMode = useMemo(() => { @@ -398,6 +396,14 @@ export default function LiveCameraView({ const [audio, setAudio] = useSessionPersistence("liveAudio", false); const [mic, setMic] = useState(false); + + // the mic only connects through the WebRTC player, so it can't stay on + // for a stream that has lost it + useEffect(() => { + if (!talkAvailable) { + setMic(false); + } + }, [talkAvailable]); const [webRTC, setWebRTC] = useState(false); const [pip, setPip] = useState(false); const [lowBandwidth, setLowBandwidth] = useState(false); @@ -424,7 +430,7 @@ export default function LiveCameraView({ }); const preferredLiveMode = useMemo(() => { - if (mic && isWebRTCAvailable) { + if (mic && talkAvailable) { return "webrtc"; } @@ -456,6 +462,7 @@ export default function LiveCameraView({ lowBandwidth, forceLowBandwidth, mic, + talkAvailable, webRTC, isRestreamed, resolvedUserMode, @@ -489,7 +496,7 @@ export default function LiveCameraView({ } break; case "t": - if (supports2WayTalk) { + if (supports2WayTalk && talkAvailable) { setMic(!mic); return true; } @@ -737,11 +744,15 @@ export default function LiveCameraView({ Icon={mic ? FaMicrophone : FaMicrophoneSlash} isActive={mic} title={ - !webRTCGloballyAvailable - ? t("twoWayTalk.requiresWebRTC", { ns: "views/live" }) - : mic - ? t("twoWayTalk.disable", { ns: "views/live" }) - : t("twoWayTalk.enable", { ns: "views/live" }) + webRTCVerdictPending + ? t("stream.technology.unavailable.checking", { + ns: "views/live", + }) + : !talkAvailable + ? t("twoWayTalk.requiresWebRTC", { ns: "views/live" }) + : mic + ? t("twoWayTalk.disable", { ns: "views/live" }) + : t("twoWayTalk.enable", { ns: "views/live" }) } onClick={() => { setMic(!mic); @@ -749,7 +760,7 @@ export default function LiveCameraView({ setAudio(true); } }} - disabled={!cameraEnabled || debug || !webRTCGloballyAvailable} + disabled={!cameraEnabled || debug || !talkAvailable} /> )} {supportsAudioOutput && preferredLiveMode != "jsmpeg" && (