zebra-report: zebra-audio music mode (disable voice isolation)

This commit is contained in:
russell@unturf.com 2026-05-28 17:38:43 -04:00
parent cb0e5454b1
commit d22c43a2e3
No known key found for this signature in database

View file

@ -88,6 +88,9 @@
<div class="dot" id="dot-path"></div> <div class="dot" id="dot-path"></div>
<span id="path-status" class="status-line">path: —</span> <span id="path-status" class="status-line">path: —</span>
</div> </div>
<div class="row">
<label class="note"><input type="checkbox" id="music-mode"> music mode — raw mic, no echo/noise cancellation (for playing audio through it)</label>
</div>
<p class="note"> <p class="note">
both partners type the same code and hit call. audio is end-to-end both partners type the same code and hit call. audio is end-to-end
encrypted (DTLS-SRTP), peer-to-peer when the network allows, relayed through encrypted (DTLS-SRTP), peer-to-peer when the network allows, relayed through
@ -173,14 +176,44 @@ async function refreshTurnCred(){
} }
/* ---- audio ---- */ /* ---- audio ---- */
let micStream = null, audioCtx = null; let micStream = null, audioCtx = null, musicMode = false;
/* voice mode = echo-cancel + noise-suppress + auto-gain (clean speech).
* music mode = all of that OFF so music/audio passes through un-mangled. */
function micConstraints(){
return musicMode
? { echoCancellation:false, noiseSuppression:false, autoGainControl:false }
: { echoCancellation:true, noiseSuppression:true, autoGainControl:true };
}
function tagTrack(t){ if (t) t.contentHint = musicMode ? 'music' : 'speech'; }
async function setSenderBitrate(sender){
if (!sender) return;
try {
const p = sender.getParameters();
if (!p.encodings || !p.encodings.length) p.encodings = [{}];
p.encodings[0].maxBitrate = musicMode ? 160000 : 40000; /* bits/s */
await sender.setParameters(p);
} catch(_){}
}
async function getMic(){ async function getMic(){
if (micStream) return micStream; if (micStream) return micStream;
micStream = await navigator.mediaDevices.getUserMedia({ micStream = await navigator.mediaDevices.getUserMedia({ audio: micConstraints(), video:false });
audio: { echoCancellation:true, noiseSuppression:true, autoGainControl:true }, video:false tagTrack(micStream.getAudioTracks()[0]);
});
return micStream; return micStream;
} }
/* switch voice<->music mid-call: re-acquire the mic and hot-swap the track on
* the live connection (no renegotiation), preserving mute state */
async function applyMicMode(){
const ns = await navigator.mediaDevices.getUserMedia({ audio: micConstraints(), video:false });
const nt = ns.getAudioTracks()[0];
tagTrack(nt); nt.enabled = !muted;
if (pc){
const sender = pc.getSenders().find(s=>s.track && s.track.kind==='audio') || pc.getSenders()[0];
if (sender){ try { await sender.replaceTrack(nt); } catch(_){} setSenderBitrate(sender); }
}
if (micStream) micStream.getTracks().forEach(t=>t.stop());
micStream = ns;
meterFor(micStream, 'meter-mic');
}
function meterFor(stream, fillId){ function meterFor(stream, fillId){
if (!audioCtx) audioCtx = new (window.AudioContext||window.webkitAudioContext)(); if (!audioCtx) audioCtx = new (window.AudioContext||window.webkitAudioContext)();
const src = audioCtx.createMediaStreamSource(stream); const src = audioCtx.createMediaStreamSource(stream);
@ -214,7 +247,8 @@ function setPath(msg, cls){ const e=$('path-status'); e.textContent=msg; e.class
async function ensurePC(){ async function ensurePC(){
if (pc) return pc; if (pc) return pc;
pc = new RTCPeerConnection(rtcConfig); pc = new RTCPeerConnection(rtcConfig);
for (const tr of micStream.getTracks()) pc.addTrack(tr, micStream); for (const tr of micStream.getTracks()) { tagTrack(tr); pc.addTrack(tr, micStream); }
setSenderBitrate(pc.getSenders().find(s=>s.track && s.track.kind==='audio'));
pc.ontrack = (ev) => { pc.ontrack = (ev) => {
const stream = ev.streams[0] || new MediaStream([ev.track]); const stream = ev.streams[0] || new MediaStream([ev.track]);
$('remote-audio').srcObject = stream; $('remote-audio').srcObject = stream;
@ -353,6 +387,11 @@ $('btn-call').addEventListener('click', startCall);
$('btn-hangup').addEventListener('click', hangup); $('btn-hangup').addEventListener('click', hangup);
$('btn-mute').addEventListener('click', toggleMute); $('btn-mute').addEventListener('click', toggleMute);
$('rdv-code').addEventListener('keydown', e=>{ if(e.key==='Enter'){ e.preventDefault(); startCall(); } }); $('rdv-code').addEventListener('keydown', e=>{ if(e.key==='Enter'){ e.preventDefault(); startCall(); } });
$('music-mode').addEventListener('change', async (e)=>{
musicMode = e.target.checked;
logLine('', 'mic mode: '+(musicMode?'MUSIC — raw, no voice isolation':'VOICE — echo/noise cancel'));
if (micStream){ try { await applyMicMode(); } catch(err){ logLine('err','mic mode switch failed: '+err.message); } }
});
logLine('', 'ready — type a rendezvous code and call. mic stays muted to the room until connected.'); logLine('', 'ready — type a rendezvous code and call. mic stays muted to the room until connected.');
})(); })();
</script> </script>