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();