matsandClaude Sonnet 5 3517bdf52b fix(webrtc): rebuild the RTCPeerConnection on failure instead of restartIce()
"restarting ICE" on a connection that has reached 'failed' only restarts
ICE gathering — it reuses the same DTLS transport and certificate. If
that transport is what's actually broken, the next renegotiation throws
"Failed to set SSL role for the transport" (DTLS role can't be flipped
on an established transport), which is exactly what showed up in the
console on a LAN test, ruling out NAT/TURN as the cause.

Fix: on 'failed', fully close and recreate the RTCPeerConnection (fresh
certificate, nothing to conflict with) via recreatePeerConnection().
Since ICE/DTLS failure isn't guaranteed to fire on both ends at once,
the side that notices sends a new 'reset' signal (relayed by the
server like offer/answer/candidate) telling the other side to tear
down and wait for a fresh offer too — otherwise its old, now-mismatched
transport would just fail the same way against our new one. Capped at
3 rebuild attempts per peer before giving up with a toast, reset on any
successful 'connected' state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:08:07 +02:00

gibbz voice — POC

Discord-clone proof of concept: peer-to-peer WebRTC voice chat + screen sharing with position-based spatial audio and pluggable STUN/TURN.

Run (Node 18+, only dep is ws):

npm install
PORT=8081 npm start
# open http://localhost:8081, click a voice channel, allow mic

What works

  • Mesh WebRTC voice (perfect negotiation; lowest peer id initiates)
  • Screen share with audio (mic is spatialized, screen audio plays direct)
  • Spatial audio: drag your avatar on the plane; remote mics run through a PannerNode (HRTF, inverse distance). Positions sync over signaling.
  • Custom STUN/TURN: gear icon → JSON override in localStorage, applied on next join. Server default comes from env (below).
  • Mute (M), deafen (D), per-peer volume, speaking indicators.

ICE configuration (server side)

Env var Meaning
STUN_URLS comma-separated stun: urls (Google defaults)
TURN_URLS comma-separated turn:/turns: urls
TURN_USERNAME / TURN_CREDENTIAL static TURN creds
ICE_SERVERS_JSON full {"iceServers":[...]} override, wins over the above
PORT / HOST listen address (default 8081 / 0.0.0.0)

Example with coturn:

TURN_URLS='turn:turn.example.com:3478?transport=udp,turns:turn.example.com:5349' \
TURN_USERNAME=mats TURN_CREDENTIAL=secret npm start

POC shortcuts (productionize later)

  • No auth, no reconnection, rooms are in-memory, signaling is unencrypted ws behind the preview ingress (use wss/TLS in front for real deploys).
  • No media server: N² mesh — fine for small rooms, needs SFU (mediasoup/livekit) beyond ~6–8 participants.
  • Text channels are decorative. No persistence, no rate limiting.
  • Screen-share negotiation storms on join/leave are expected at small scale.
S
Description
No description provided
Readme
38 MiB
0 Stars 1 Watchers 0 Forks
Languages
JavaScript 75.1%
CSS 16.2%
HTML 7.5%
Makefile 0.9%
Dockerfile 0.3%