From 1a6e9554246b4128a31b8dec4c5cbdedb0f98ca2 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Sun, 31 May 2026 13:25:51 -0400 Subject: [PATCH] zebra-spaces: client wires grant-mic for hand-raised listeners The 'give the mic' button (visible only when the listener has raised their hand) now sends grant-mic, which the server promotes directly without waiting for accept-mic. The 'invite mic' button (cold invite) still triggers the existing mic-invite + accept dance. Also fold in the earlier sfuPublish diagnostic logging so silent returns surface in the page log. --- web/zebra-spaces.html | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index e5ef772..ddf56de 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -449,7 +449,12 @@ let sfuPubPC = null, sfuPubPeerID = null; let sfuSubPC = null, sfuSubPeerID = null, sfuSubEvents = null; async function sfuPublish(){ - if (sfuPubPC || !micStream || !myKeys || !roomID) return; + if (sfuPubPC){ logLine('','sfu publish: already publishing'); return; } + if (!micStream || !myKeys || !roomID){ + logLine('err','sfu publish skipped: mic='+(!!micStream)+' keys='+(!!myKeys)+' room='+(!!roomID)); + return; + } + logLine('', 'sfu publish: starting (base='+SFU_BASE+')'); const pc = new RTCPeerConnection(rtcConfig); for (const tr of micStream.getTracks()){ tagTrack(tr); pc.addTrack(tr, micStream); } setSenderBitrate(pc.getSenders().find(s=>s.track && s.track.kind==='audio')); @@ -1011,6 +1016,12 @@ async function modInvite(uuid){ const sig = await signBytes(sigAction(roomID, roomEpoch, 'mic-invite', uuid)); send({ type:'mic-invite', to: uuid, epoch: roomEpoch, sig }); } +/* grant-mic: hand-raised listener doesn't need to accept — server promotes + * them directly to speaker. Use modInvite for cold (unsolicited) invites. */ +async function modGrant(uuid){ + const sig = await signBytes(sigAction(roomID, roomEpoch, 'grant-mic', uuid)); + send({ type:'grant-mic', to: uuid, epoch: roomEpoch, sig }); +} async function modPromote(uuid){ const sig = await signBytes(sigAction(roomID, roomEpoch, 'promote', uuid, 'cohost')); send({ type:'promote', target: uuid, to: 'cohost', epoch: roomEpoch, sig }); @@ -1067,8 +1078,11 @@ function renderRoom(){ if (isMod(myRole) && m.uuid !== myUUID){ if (m.role === 'listener'){ const b = document.createElement('button'); b.className='small invert'; - b.textContent = handraise.has(m.uuid) ? 'give the mic' : 'invite mic'; - b.onclick = () => modInvite(m.uuid).catch(e=>logLine('err','invite: '+e.message)); + const raised = handraise.has(m.uuid); + b.textContent = raised ? 'give the mic' : 'invite mic'; + b.onclick = raised + ? () => modGrant(m.uuid).catch(e=>logLine('err','grant: '+e.message)) + : () => modInvite(m.uuid).catch(e=>logLine('err','invite: '+e.message)); acts.appendChild(b); } if (m.role === 'speaker' && myRole === 'host'){ @@ -1197,8 +1211,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');