zebra-report: zebra-spaces meter survives re-renders, local mic also metered
This commit is contained in:
parent
a5a947861d
commit
b9938113ad
1 changed files with 28 additions and 13 deletions
|
|
@ -499,22 +499,37 @@ async function applyMicMode(){
|
|||
}
|
||||
if (micStream) micStream.getTracks().forEach(t=>t.stop());
|
||||
micStream = ns;
|
||||
/* old analyser is now dead — rewire local meter against the fresh stream */
|
||||
if (myUUID){ stopMeter(myUUID); startMeter(myUUID, micStream); }
|
||||
}
|
||||
function meterFor(stream, elId){
|
||||
/* per-peer meter: one analyser node + one rAF loop, keyed by uuid. The tick
|
||||
* reads members.get(uuid)._meterEl fresh each frame so renderRoom can replace
|
||||
* the DOM element without killing the meter. Cleanup happens when the uuid
|
||||
* leaves the room (members loses the key) or stopMeter is called. */
|
||||
const meterCtl = new Map(); /* uuid -> {an, buf} */
|
||||
function startMeter(uuid, stream){
|
||||
if (!stream || meterCtl.has(uuid)) return;
|
||||
if (!audioCtx) audioCtx = new (window.AudioContext||window.webkitAudioContext)();
|
||||
const src = audioCtx.createMediaStreamSource(stream);
|
||||
let src;
|
||||
try { src = audioCtx.createMediaStreamSource(stream); }
|
||||
catch(e){ logLine('err','meter for '+shortHex(uuid)+': '+e.message); return; }
|
||||
const an = audioCtx.createAnalyser(); an.fftSize = 512;
|
||||
src.connect(an);
|
||||
const buf = new Uint8Array(an.fftSize);
|
||||
const fill = typeof elId==='string' ? $(elId) : elId;
|
||||
meterCtl.set(uuid, { an, buf });
|
||||
(function tick(){
|
||||
if (!fill || !document.contains(fill)) return; /* meter removed with member row */
|
||||
an.getByteTimeDomainData(buf);
|
||||
let peak=0; for (let i=0;i<buf.length;i++){ const v=Math.abs(buf[i]-128)/128; if(v>peak)peak=v; }
|
||||
fill.style.width = Math.min(100, Math.round(peak*180))+'%';
|
||||
const c = meterCtl.get(uuid);
|
||||
if (!c) return;
|
||||
if (!members.has(uuid)){ meterCtl.delete(uuid); return; }
|
||||
c.an.getByteTimeDomainData(c.buf);
|
||||
let peak=0;
|
||||
for (let i=0;i<c.buf.length;i++){ const v=Math.abs(c.buf[i]-128)/128; if(v>peak)peak=v; }
|
||||
const m = members.get(uuid);
|
||||
if (m && m._meterEl) m._meterEl.style.width = Math.min(100, Math.round(peak*180))+'%';
|
||||
requestAnimationFrame(tick);
|
||||
})();
|
||||
}
|
||||
function stopMeter(uuid){ meterCtl.delete(uuid); }
|
||||
|
||||
/* ==================================================================
|
||||
* room state mirror (server is source of truth, we mirror locally for
|
||||
|
|
@ -766,10 +781,12 @@ async function ensureMicAndUI(){
|
|||
try {
|
||||
await getMic(); await refreshMicList();
|
||||
$('btn-mute').disabled = false;
|
||||
if (myUUID) startMeter(myUUID, micStream);
|
||||
} catch(e){ logLine('err','mic blocked: '+e.message); }
|
||||
updateRoleUI();
|
||||
}
|
||||
function dropMic(){
|
||||
if (myUUID) stopMeter(myUUID);
|
||||
if (micStream){ micStream.getTracks().forEach(t=>t.stop()); micStream = null; }
|
||||
$('btn-mute').disabled = true;
|
||||
}
|
||||
|
|
@ -796,10 +813,7 @@ async function connectToPeer(uuid, weOffer){
|
|||
let a = remoteAudio.get(uuid);
|
||||
if (!a){ a = document.createElement('audio'); a.autoplay = true; document.body.appendChild(a); remoteAudio.set(uuid, a); }
|
||||
a.srcObject = ev.streams[0] || new MediaStream([ev.track]);
|
||||
try {
|
||||
const m = members.get(uuid);
|
||||
if (m && m._meterEl) meterFor(a.srcObject, m._meterEl);
|
||||
} catch(_){}
|
||||
startMeter(uuid, a.srcObject);
|
||||
};
|
||||
pc.onicecandidate = (ev) => { /* using waitForIceGathering pattern, candidates ignored */ };
|
||||
pc.onconnectionstatechange = () => {
|
||||
|
|
@ -818,6 +832,7 @@ async function connectToPeer(uuid, weOffer){
|
|||
}
|
||||
}
|
||||
function tearPeer(uuid){
|
||||
stopMeter(uuid);
|
||||
const pc = peers.get(uuid);
|
||||
if (pc){ try { pc.close(); } catch(_){} peers.delete(uuid); }
|
||||
const a = remoteAudio.get(uuid);
|
||||
|
|
@ -1021,8 +1036,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
|
|||
|
||||
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-05-31</span><br>
|
||||
md5 <span class="stamp-md5">c5bb52fca1002251e888aa7effa2cf91</span><br>
|
||||
sha256 <span class="stamp-sha">d1082ceeb95d3c616a63d522922e919b60cc7bfef9bb5bc49921008b75064310</span><br>
|
||||
md5 <span class="stamp-md5">cd35adde9997d99b27d4ee03f54863cd</span><br>
|
||||
sha256 <span class="stamp-sha">818aefd367f7031750589fa3c8e870440f6dcd0df2f91793e118060169364767</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue