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