Buffer ICE candidates until setRemoteDescription completes (fix CONNECTING hang) - #14
Merged
Merged
Conversation
The "stuck on CONNECTING" symptom: client UI never transitions to
CONNECTED even with STUN configured. Root cause was hiding in a
warning we'd been ignoring -
RTCPeerConnection addIceCandidate called without remote description
aiortc's addIceCandidate REJECTS the call when no remote description
is set yet, just emits that warning, and the candidate is gone. The
publisher was spawning the answer task and per-candidate tasks as
independent asyncio tasks; the candidate tasks raced ahead of the
answer task's setRemoteDescription and got dropped on the floor.
Every successful ICE we've seen so far was a NAT-punch lottery on the
remaining candidates that happened to arrive after the answer settled.
Fix: per-Viewer asyncio.Event (`remote_set`) flips when
setRemoteDescription completes. _apply_ice_candidate awaits it (10 s
cap, in case the answer never lands) before calling addIceCandidate.
Candidates now flow correctly regardless of arrival order.
Also done while we're in this neighborhood:
- STUN_URL / TURN_URL / TURN_USERNAME / TURN_PASSWORD env-overridable.
Default STUN stays at stun.l.google.com:19302; TURN empty. If a
symmetric NAT or CGNAT prevents direct punching, the operator can
point at a free public TURN (openrelay.metered.ca) without code
changes.
Tests: 11 publisher cases pass including a new regression that emits
ICE BEFORE the answer and asserts addIceCandidate is held until the
answer arrives. ruff + mypy strict + 219 tests overall.
https://claude.ai/code/session_016DuCjRJK995Tj9aDhhB9at
3 of 4 tasks
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.
Why "CONNECTING" hängt
Live: TS6-UI bleibt bei
CONNECTINGstehen, ICE läuft nie aufconnected. Die ganze Zeit haben wir diese Warning in den Logs ignoriert:Die ist nicht harmlos. aiortc REJECTED die
addIceCandidate-Calls wennsetRemoteDescriptionnoch nicht durch ist — das Candidate ist weg. Wir haben in jedem Versuch alle 5 ersten ICE-Candidates verloren.Race-Condition:
_on_signaling_messagespawnt sowohl die Answer-Task (_apply_answer→setRemoteDescription) als auch jede ICE-Candidate-Task (_apply_ice_candidate→addIceCandidate) als unabhängigeasyncio.create_tasks. Die ICE-Tasks laufen typisch VOR der Answer-Task durch.Fix
Per-Viewer
asyncio.Event(remote_set). Wird gesetzt wennsetRemoteDescriptiondurch ist._apply_ice_candidateawaitet das Event (10 s Cap als Safety) bevor esaddIceCandidateruft. Damit kommen ICE-Candidates byte-genau an, egal in welcher Reihenfolge sie reinkommen.Bonus
STUN_URL/TURN_URL/TURN_USERNAME/TURN_PASSWORDals ENV-Variablen. Default-STUN bleibtstun.l.google.com:19302. Wenn das echte ICE-Problem trotz dieses Fixes weiterläuft (symmetric NAT / CGNAT), kann der Operator einen freien TURN-Server eintragen ohne Code-Änderung:Tests
addIceCandidatenicht aufgerufen wird bis Answer da ist.Operator commands
cd /opt/ts6-stream-bot git pull docker compose down docker compose build --no-cache docker compose up -d docker compose logs -fDann im TS6-Client den Bot-Stream beitreten. Erwartet:
UI wechselt auf CONNECTED und du siehst Bot's Chromium (initial leerer Screen —
POST /play <url>machen für Inhalt).https://claude.ai/code/session_016DuCjRJK995Tj9aDhhB9at
Generated by Claude Code