zebra-report: swap fresh <audio> in listener click — Firefox Android fix

Mirror of zebra-report 1de3f2c.
This commit is contained in:
russell@unturf.com 2026-06-03 21:26:39 -04:00
parent c81e7e00f2
commit 2c5d2618d6
No known key found for this signature in database

View file

@ -4661,17 +4661,39 @@ function applyMuteState(){
if (mm){ mm.muted = muted; renderRoom(); }
}
$('btn-mute').addEventListener('click', () => {
/* listener path: toggle output mute. The click itself is the
* user gesture mobile browsers wait for — even if listenerOutput-
* Muted was already false, applyAudioMute() will re-call play()
* inside this gesture and unblock any audio element that's been
* waiting. */
if (myRole === 'listener'){
listenerOutputMuted = !listenerOutputMuted;
logLine('', 'play-button: listenerOutputMuted=' + listenerOutputMuted +
' remoteAudio=' + remoteAudio.size +
' streamAudio=' + streamAudio.size +
' audioPath=' + audioPath.size);
/* Firefox Android internalisation defect: an <audio> created with
* muted=true and later unmuted reports paused=false rs=4 srcObject=true
* but emits zero output. Same shape as the video supplant defect we
* fixed earlier with swapFreshVideoElement. Inside the click gesture,
* swap every audio element with a fresh one (created UNMUTED) and
* re-attach the source. play() fires inside the gesture so autoplay
* is allowed. */
if (!listenerOutputMuted){
for (const [uuid, oldA] of [...remoteAudio]){
const stream = oldA.srcObject;
const fresh = swapFreshAudioElement(oldA);
fresh.muted = false;
applySinkTo(fresh);
fresh.srcObject = stream;
remoteAudio.set(uuid, fresh);
try { const p = fresh.play(); if (p && p.catch) p.catch(e => logLine('err','swap rtc '+uuid+': '+e.message)); } catch(_){}
}
for (const [uuid, oldA] of [...streamAudio]){
const src = oldA.src;
const fresh = swapFreshAudioElement(oldA);
fresh.muted = false;
applySinkTo(fresh);
if (src) fresh.src = src;
streamAudio.set(uuid, fresh);
try { const p = fresh.play(); if (p && p.catch) p.catch(e => logLine('err','swap dj '+uuid+': '+e.message)); } catch(_){}
}
}
applyAudioMute();
const btn = $('btn-mute');
btn.textContent = listenerOutputMuted ? 'play' : 'mute';
@ -4684,6 +4706,23 @@ $('btn-mute').addEventListener('click', () => {
applyMuteState();
sendMicState();
});
/* Audio-element analogue of swapFreshVideoElement — replace an <audio>
* with a freshly-built one in the same DOM slot. Used to dodge the
* Firefox-Android internal routing defect where an element created
* muted=true silently stays silent after .muted=false. */
function swapFreshAudioElement(oldA){
if (!oldA) return oldA;
const fresh = document.createElement('audio');
fresh.autoplay = true; fresh.playsInline = true; fresh.preload = 'auto';
if (oldA.parentNode){
try { oldA.srcObject = null; oldA.removeAttribute('src'); oldA.load(); } catch(_){}
oldA.parentNode.replaceChild(fresh, oldA);
} else {
document.body.appendChild(fresh);
}
return fresh;
}
$('mic-select').addEventListener('change', async (e) => {
micDeviceId = e.target.value;
try { localStorage.setItem(MIC_DEV_KEY, micDeviceId); } catch(_){}
@ -4932,8 +4971,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> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-04</span><br>
md5 <span class="stamp-md5">71493d3cbf0438797dcf8c1b4948202f</span><br>
sha256 <span class="stamp-sha">2a12f6f390f0592daa5ee6ad23f8a661d558ece7a57cce51567ddc34adc89a7d</span><br>
md5 <span class="stamp-md5">eac4e2b4b8c40c4adbdd03c004eb8cc4</span><br>
sha256 <span class="stamp-sha">d2ca231a1a04199c5e5cc01215e6a82ab4949119d5ac40bd5150a7805fd3cdf1</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>