fix(RTC): stop the remote audio wedge watchdog firing when nothing is being sent - #3087
Merged
JonathanLennox merged 3 commits intoAug 13, 2026
Merged
Conversation
A 2-participant call keeps its JVB session alive as a fallback and merely suspends it for the duration of the P2P session, which leaves every eligibility condition the watchdog tests satisfied while no RTP can arrive: - _removeRemoteJVBTracks() only detaches the tracks from the JitsiParticipant and emits TRACK_REMOVED, so TraceablePeerConnection.remoteTracks - what the detector iterates - still holds them for the rest of the call. - setMediaTransferActive(false) deactivates the sender encodings on both peers, so the bridge has nothing to forward and the session reads zero inbound RTP. - The JVB stats collector keeps polling (stopRemoteStats runs only in _stopJvbSession), so INBOUND_AUDIO_STATS keeps arriving with zeros, and the detector itself only stops in JingleSessionPC.close(). - Mute state is signaling-derived, so the sources stay unmuted. A field report shows the result: 23 recoveries on one source over 15 minutes, one every ~40s, each recycling it via source-remove/source-add on the suspended peerconnection, ending only when P2P did. Skip evaluation while media transfer is suspended, clearing the streaks so the window restarts on resume. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A source confirmed healthy by receiving RTP was re-armed as soon as its SSRC stopped being mapped, on the rationale that a future source reusing the SSRC should be re-evaluated from scratch. That is backwards. Under SSRC rewriting addRemoteSsrc() reports an SSRC as new only once for the lifetime of the peerconnection, so processSourceMap creates the receive m-line once per SSRC and every later appearance of it in a source map is an in-place remap: same m-line, transceiver and track, metadata only, no SDP change. A remap therefore inherits an m-line that is already demuxing that SSRC correctly and cannot be wedged, whichever source now occupies it. Keep the confirmed-healthy mark for the lifetime of the peerconnection, so a remap onto a working m-line is never recycled just because its new owner happens to be silent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zero inbound RTP is not exclusive to the wedge, so a recovery that does not help must not repeat indefinitely. A bridge that discards silence forwards nothing at all for a sender that is digitally silent from the start of its stream: JVB's AudioLevelReader lets the first few packets of a stream through (forwarded-silence-packets-limit, default 3) and discards every silence packet after that, and the audio SSRC cache allocates the send SSRC - and sends the AudioSourcesMap that makes the client create the receive m-line - on a packet that got through. If that short head-of-stream burst is spent before the m-line is negotiated, the source is mapped and unmuted yet permanently at zero RTP without being wedged, and recycling it cannot change that. Cap recoveries at 2, so the unrecoverable case costs a couple of renegotiations rather than one every detection window for the rest of the call. Count the attempts per receive slot, keyed on the rewritten SSRC. The m-line cannot carry the count because a recovery replaces it - source-remove then source-add mints a fresh msid - so a per-m-line budget would reset on every attempt and never converge. The source name cannot either: it would hand a fresh budget to whichever source the bridge next remaps onto a slot that recycling has already failed to revive. The SSRC is the identity a recovery preserves, and a genuinely new slot for a source is a genuinely new wedge opportunity, so it still gets a budget of its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jitsi-ci Bot
pushed a commit
to jitsi/jitsi-pr-tests-pages
that referenced
this pull request
Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The remote audio wedge watchdog added in #3051 / #3062 fires on the absence of inbound RTP, but zero inbound RTP is not exclusive to the wedge. A field report shows it recycling a healthy source 23 times over 15 minutes in an ordinary two-person call.
What happened in the field
A 2-participant call keeps its JVB session alive as a fallback and merely suspends it for the duration of the P2P session, which leaves every eligibility condition the watchdog tests satisfied while no RTP can arrive:
_removeRemoteJVBTracks()only detaches the tracks from theJitsiParticipantand emitsTRACK_REMOVED;TraceablePeerConnection.remoteTracks— what the detector iterates — still holds them for the rest of the call.setMediaTransferActive(false)deactivates the sender encodings on both peers, so the bridge has nothing to forward and the session reads zero inbound RTP.stopRemoteStatsruns only in_stopJvbSession), soINBOUND_AUDIO_STATSkeeps arriving with zeros, and the detector itself only stops inJingleSessionPC.close().From the log, bracketed exactly by the suspend/resume pair:
Each firing ran a source-remove plus source-add — two offer/answer cycles — on the suspended peerconnection, with the freshly added track discarded by the conference every time (
Trying to add remote JVB track, when in P2P - IGNORED).Changes
Three separate commits:
Skip detection while media transfer is suspended. Bail out of
_evaluatewhen the peerconnection'saudioTransferActiveis false, clearing the streaks so the detection window restarts on resume instead of counting the suspended period against the sources. This is the fix for the report above.Keep a proven receive m-line exempt for the peerconnection's lifetime. A source confirmed healthy by receiving RTP was re-armed as soon as its SSRC stopped being mapped, so that a future source reusing the SSRC would be re-evaluated from scratch. That is backwards:
addRemoteSsrc()reports an SSRC as new only once, soprocessSourceMapcreates the receive m-line once per SSRC and every later appearance of it in a source map is an in-place remap — same m-line, transceiver and track, metadata only, no SDP change. A remap inherits an m-line that already demuxes that SSRC correctly and cannot be wedged, whichever source now occupies it.Cap recoveries at 2 per receive slot. A bridge that discards silence forwards nothing at all for a sender that is digitally silent from the start of its stream:
AudioLevelReaderlets the first few packets of a stream through (forwarded-silence-packets-limit, default 3) and discards every silence packet after that, andAudioSsrcCacheallocates the send SSRC — and sends theAudioSourcesMapthat makes the client create the receive m-line — on a packet that got through. If that short head-of-stream burst is spent before the m-line is negotiated, the source is mapped and unmuted yet permanently at zero RTP without being wedged, and recycling cannot change that. The attempts are counted per rewritten SSRC: the m-line cannot carry the count because a recovery replaces it (source-remove then source-add mints a fresh msid), so a per-m-line budget would reset on every attempt and never converge; the source name cannot either, since it would hand a fresh budget to whichever source the bridge next remaps onto a slot recycling has already failed to revive.Testing
npm run lintand the karma suite are green at each commit, not just at the tip (559 / 560 / 563 tests). Six newRemoteAudioWedgeDetectorspecs cover: no firing while suspended, the window restarting on resume, a healthy source staying exempt across unmap/remap, recoveries stopping at the cap, a fresh budget on a new receive slot, and an exhausted budget staying with a slot that is remapped to another source.🤖 Generated with Claude Code