diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 813657e..95ad5f1 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -4245,19 +4245,28 @@ async function enableSelfListenerMode(){ try { sendMicState(); } catch(_){} } let added = 0; + /* INCLUDE self in the enrolment — listeners hear every speaker + * (including us), so the canonical "what listeners hear" experience + * pulls our own /stream too. Mic is already muted, so the loopback + * doesn't create feedback; the user gets full broadcast monitoring, + * AND a solo-host alone with listeners still has something to play. */ for (const [uuid, mm] of members){ - if (uuid === myUUID || !canSpeak(mm.role) || !mm.pubkey) continue; + if (!canSpeak(mm.role) || !mm.pubkey) continue; let pubHex; try { pubHex = hex(unb64(mm.pubkey)); } catch(_){ continue; } if (streamMode.has(pubHex)) continue; streamMode.add(pubHex); startStream(uuid, pubHex); + /* mute the matched WebRTC remote so we don't hear both paths. + * Self has no remoteAudio entry (we never subscribe to ourselves + * via SFU — see the self-echo skip in handleRemoteSfuTrack), so + * this is a no-op for the self row. */ const w = remoteAudio.get(uuid); if (w) try { w.muted = true; } catch(_){} added++; } renderRoom(); - logLine('', 'self-listener ON — '+added+' peers on buffered HTTP path, mic muted'); + logLine('', 'self-listener ON — '+added+' peers on buffered HTTP path (incl. self), mic muted'); } function disableSelfListenerMode(){ if (!selfListenerMode) return; @@ -5042,8 +5051,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');