From da78dc8612dea1dc9d777675f03411db79df2053 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Thu, 28 May 2026 14:58:16 -0400 Subject: [PATCH] zebra-audio: advertise mic (mute) state to the partner Each peer signals its mute state over the rendezvous channel (encrypted with the room code, so the relay never learns it) on connect and on every toggle. A "muted" badge now shows on both your own and the partner's level meter, so both players can see who is muted. Verified both directions headless. --- web/zebra-audio.html | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/web/zebra-audio.html b/web/zebra-audio.html index 15e90f5..4b91560 100644 --- a/web/zebra-audio.html +++ b/web/zebra-audio.html @@ -94,10 +94,12 @@
you
+
them
+
@@ -217,7 +219,7 @@ async function ensurePC(){ const s = pc.connectionState; setStatus('call: '+s, s==='connected'?'ok':null); $('dot-call').className = 'dot ' + (s==='connected'?'ok':'warn'); - if (s==='connected'){ $('btn-mute').disabled=false; $('btn-hangup').disabled=false; setTimeout(reportPath,1200); } + if (s==='connected'){ $('btn-mute').disabled=false; $('btn-hangup').disabled=false; setTimeout(reportPath,1200); setMicLabels(); sendMicState(); } else if (s==='failed'||s==='disconnected'||s==='closed'){ setPath('path: —', null); } }; return pc; @@ -298,9 +300,11 @@ async function startCall(){ $('remote-audio').srcObject=null; $('meter-rem').style.width='0%'; $('dot-call').className='dot warn'; setPath('path: —', null); $('btn-mute').disabled=true; muted=false; $('btn-mute').textContent='mute'; $('btn-mute').className=''; + setPartnerMic(false); setMicLabels(); setStatus('partner left — waiting for them to return', null); } else if (m.type==='sdp'){ try { await onSDP(m.kind, await aesDecrypt(relayKey, unb64(m.data))); } catch(e){ logLine('err','sdp failed (codes must match): '+e.message); } } + else if (m.type==='mic'){ try { const s=JSON.parse(await aesDecrypt(relayKey, unb64(m.data))); setPartnerMic(!!s.muted); } catch(_){} } else if (m.type==='error'){ setStatus('rendezvous: '+m.message,'err'); } }; } @@ -311,15 +315,29 @@ function hangup(){ $('btn-mute').disabled=true; $('btn-hangup').disabled=true; $('btn-call').disabled=false; $('dot-call').className='dot warn'; setStatus('idle', null); setPath('path: —', null); $('meter-rem').style.width='0%'; + muted=false; partnerMuted=false; $('btn-mute').textContent='mute'; $('btn-mute').className=''; setMicLabels(); logLine('', 'call ended'); } -let muted=false; +let muted=false, partnerMuted=false; +function setMicLabels(){ + $('you-state').textContent = muted ? 'muted' : ''; + $('them-state').textContent = partnerMuted ? 'muted' : ''; +} +function setPartnerMic(m){ partnerMuted=m; setMicLabels(); logLine('', 'partner '+(m?'muted':'unmuted')); } +/* advertise our mic state to the partner over the rendezvous channel, encrypted + * with the room code so the relay never learns it */ +async function sendMicState(){ + if (!relayWS || relayWS.readyState!==1 || !relayKey) return; + try { relayWS.send(JSON.stringify({ type:'mic', data: b64(await aesEncrypt(relayKey, JSON.stringify({muted}))) })); } catch(_){} +} function toggleMute(){ if (!micStream) return; muted=!muted; micStream.getAudioTracks().forEach(t=>t.enabled=!muted); $('btn-mute').textContent = muted?'unmute':'mute'; $('btn-mute').className = muted?'invert':''; + setMicLabels(); + sendMicState(); } $('btn-call').addEventListener('click', startCall);