diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index b952f7e..84ef5c1 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -1260,6 +1260,11 @@ async function sfuSubscribe(){
for (const [uuid, mm] of members){
try {
if (mm.pubkey && hex(unb64(mm.pubkey)) === pubHex){
+ /* if we're a speaker and already have a mesh peer with this
+ * member, mesh carries their audio with lower latency — skip
+ * the duplicate SFU mic. Screens + cameras still come through
+ * because they're handled above. */
+ if (canSpeak(myRole) && peers.has(uuid)) return;
attachSfuTrack(uuid, ev.streams[0]);
return;
}
@@ -1796,14 +1801,17 @@ function applyState(state){
async function onRoleEntered(){
if (canSpeak(myRole)) await ensureMicAndUI();
else updateRoleUI();
+ /* Everyone subscribes to the SFU. Listeners use it for mic+screen+camera.
+ * Speakers use it for screen+camera (and as a backup audio path); the
+ * SFU mic track is suppressed by the ontrack handler when we already
+ * have a mesh peer for that pubkey, so we don't get double audio. */
+ sfuSubscribe().catch(e => logLine('err','sfu subscribe: '+e.message));
if (canSpeak(myRole)){
for (const [uuid, mm] of members){
if (uuid === myUUID) continue;
if (canSpeak(mm.role)) connectToPeer(uuid, myUUID < uuid);
}
sfuPublish().catch(e => logLine('err','sfu publish: '+e.message));
- } else {
- sfuSubscribe().catch(e => logLine('err','sfu subscribe: '+e.message));
}
}
async function onRoleChanged(prev, next){
@@ -1813,13 +1821,14 @@ async function onRoleChanged(prev, next){
if (uuid === myUUID) continue;
if (canSpeak(mm.role)) connectToPeer(uuid, myUUID < uuid);
}
- await sfuUnsubscribe();
+ /* keep sfuSub alive — screen + camera tracks still ride it. The
+ * ontrack handler suppresses SFU mic when mesh is also up. */
sfuPublish().catch(e => logLine('err','sfu publish: '+e.message));
} else if (canSpeak(prev) && !canSpeak(next)){
for (const u of [...peers.keys()]) tearPeer(u);
dropMic(); muted = false;
await sfuUnpublish();
- sfuSubscribe().catch(e => logLine('err','sfu subscribe: '+e.message));
+ /* still subscribed — listener role needs the same incoming streams */
}
updateRoleUI();
}
@@ -2406,8 +2415,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');