diff --git a/zebra-report/index.html b/zebra-report/index.html index 8d0493a..9987cbb 100644 --- a/zebra-report/index.html +++ b/zebra-report/index.html @@ -223,7 +223,24 @@
disconnected — start audio first, then pick a role below +
+
+ stuck on the same LAN? host candidates as .local? +

+ modern browsers anonymize host IPs as .local mDNS names for + privacy. if a router blocks multicast (common on guest Wi-Fi, sometimes + consumer defaults), peers cannot resolve each other's .local + addresses & host candidates fail. workaround: turn off the obfuscation. +

+

+ Firefox: about:config → set + media.peerconnection.ice.obfuscate_host_addresses to false.
+ Chromium: chrome://flags/#enable-webrtc-hide-local-ips-with-mdns + → set to Disabled.
+ reload the page after toggling, then retry. +

+

role A · start a new connection

@@ -977,6 +994,14 @@ function ensurePC() { const stream = ev.streams[0] || new MediaStream([ev.track]); attachInboundTrack(stream, ev.track.id); }; + pc.onicecandidate = (ev) => { + if (!ev.candidate) return; + const c = ev.candidate.candidate; + const tm = c.match(/typ\s+(\S+)/); + const typ = tm ? tm[1] : '?'; + const mdns = c.includes('.local') ? ' (mdns-anonymized)' : ''; + logLine('sys', `ice candidate: ${typ}${mdns}`); + }; pc.onconnectionstatechange = () => { const s = pc.connectionState; rtcStatus.textContent = 'connection: ' + s; @@ -986,6 +1011,7 @@ function ensurePC() { announceHello().catch(() => {}); } else if (s === 'failed' || s === 'closed' || s === 'disconnected') { dotRtc.className = 'dot'; + logLine('err', `webrtc ${s} — click "reset connection" then retry`); } }; pc.oniceconnectionstatechange = () => { @@ -994,6 +1020,30 @@ function ensurePC() { return pc; } +function resetConnection() { + if (pc) { + try { pc.close(); } catch (_) {} + pc = null; + } + for (const [, d] of rxDecoders) { + try { d.node.disconnect(); } catch (_) {} + try { d.audio.pause(); d.audio.srcObject = null; } catch (_) {} + } + rxDecoders.clear(); + $('local-offer').value = ''; + $('local-answer').value = ''; + $('remote-offer').value = ''; + $('remote-answer').value = ''; + for (const id of ['offer-status','answer-status','ans-status']) { + $(id).textContent = ''; $(id).className = 'status-line'; + } + rtcStatus.textContent = 'disconnected — pick a role below'; + dotRtc.className = 'dot warn'; + logLine('sys', 'connection reset — ready to retry'); +} + +$('btn-reset-rtc').addEventListener('click', resetConnection); + function waitForIceGathering(p) { return new Promise(resolve => { if (p.iceGatheringState === 'complete') return resolve();