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.
This commit is contained in:
Josh Hawkins
2026-09-28 08:50:56 -05:00
parent 71d7741e12
commit 85a2731510
+27 -16
View File
@@ -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<LivePlayerMode>(() => {
@@ -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" && (