zebra-report: pre-bless audio-element pool on entry click
Mirror of zebra-report 44e05b2.
This commit is contained in:
parent
cef405c204
commit
0f70d62148
1 changed files with 59 additions and 17 deletions
|
|
@ -1627,17 +1627,17 @@ const sfuStreamsByPubHex = new Map(); // pubHex -> MediaStream
|
|||
function attachSfuTrack(uuid, stream){
|
||||
let a = remoteAudio.get(uuid);
|
||||
if (!a){
|
||||
a = document.createElement('audio'); a.autoplay = true;
|
||||
a.playsInline = true;
|
||||
document.body.appendChild(a); remoteAudio.set(uuid, a);
|
||||
/* lease from the pre-blessed audio pool so Firefox Android's
|
||||
* per-element autoplay grant carries over from the entry-button
|
||||
* gesture. attachSfuTrack runs after several async hops (WS join,
|
||||
* SFU subscribe negotiation, ontrack) so the click activation
|
||||
* window has expired by now — only a pre-played pool element
|
||||
* still has the engagement bit set. */
|
||||
a = leaseAudioElement();
|
||||
remoteAudio.set(uuid, a);
|
||||
applySinkTo(a);
|
||||
}
|
||||
a.srcObject = stream;
|
||||
/* programmatic play in case autoplay policy needs the nudge after a track
|
||||
* swap — caller already had a user gesture (entered the space). On Firefox
|
||||
* Android the play() promise will reject if the gesture grace has aged
|
||||
* out; the listener's play button re-runs play() inside its own gesture
|
||||
* to recover. */
|
||||
try {
|
||||
const p = a.play();
|
||||
if (p && p.catch) p.catch(e => logLine('err','rtc autoplay '+uuid+': '+e.message));
|
||||
|
|
@ -4743,23 +4743,65 @@ const SILENCE_WAV = 'data:audio/wav;base64,UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAA
|
|||
* after a successful first prime. Mobile browsers can suspend the
|
||||
* audio session on tab background / leave; the next entry click needs
|
||||
* a fresh play() to re-bless audio for the new session. */
|
||||
/* Pool of audio elements pre-blessed for autoplay by the user gesture
|
||||
* (entry button click). Firefox Android grants media autoplay
|
||||
* permission PER ELEMENT, not per document — so attachSfuTrack creating
|
||||
* a fresh <audio> later (after the gesture activation has aged out)
|
||||
* gets its play() rejected with NotAllowedError. We pre-create a pool
|
||||
* inside the click handler, call play() on each (succeeds inside the
|
||||
* gesture), and attachSfuTrack consumes from the pool when a real
|
||||
* stream arrives. The element has retained 'engagement' so setting
|
||||
* srcObject and play() works without a fresh gesture. */
|
||||
const AUDIO_POOL_SIZE = 16;
|
||||
const audioPool = [];
|
||||
function primeAudioOnGesture(){
|
||||
/* legacy silent primer kept for AudioContext + chime engagement */
|
||||
const a = document.createElement('audio');
|
||||
a.src = SILENCE_WAV; a.muted = false; a.volume = 0;
|
||||
a.style.display = 'none';
|
||||
document.body.appendChild(a);
|
||||
const p = a.play();
|
||||
if (p && p.then) p.then(() => {
|
||||
/* keep the element around briefly so the play() actually emits a
|
||||
* frame; then yank it from the DOM to avoid a growing pile of
|
||||
* primer elements after many entry clicks. */
|
||||
setTimeout(() => { try { a.remove(); } catch(_){} }, 500);
|
||||
}).catch(()=>{ try { a.remove(); } catch(_){} });
|
||||
/* Also try resuming any existing AudioContext (meter / chime).
|
||||
* resume() on an already-running context is a no-op, and on a
|
||||
* suspended one it transitions to running — granting Web Audio
|
||||
* playback permission alongside <audio>. */
|
||||
try { if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume(); } catch(_){}
|
||||
/* pre-bless a pool of audio elements for unmuted autoplay. Each one
|
||||
* gets play() called inside this click handler — the gesture grants
|
||||
* each element individual engagement that survives the click's
|
||||
* activation window. attachSfuTrack later assigns srcObject onto a
|
||||
* pool element instead of creating a fresh one. */
|
||||
for (let i = 0; i < AUDIO_POOL_SIZE; i++){
|
||||
const el = document.createElement('audio');
|
||||
el.autoplay = true; el.playsInline = true;
|
||||
el.style.display = 'none';
|
||||
document.body.appendChild(el);
|
||||
/* assign a 1-frame silent source so play() has something to chew
|
||||
* on. The element retains engagement after this play(); subsequent
|
||||
* srcObject= assignments inherit it. */
|
||||
el.src = SILENCE_WAV;
|
||||
try {
|
||||
const pp = el.play();
|
||||
if (pp && pp.catch) pp.catch(()=>{});
|
||||
} catch(_){}
|
||||
audioPool.push(el);
|
||||
}
|
||||
logLine('', 'audio pool primed: '+audioPool.length+' elements');
|
||||
}
|
||||
function leaseAudioElement(){
|
||||
/* hand out a pre-blessed element from the pool; if the pool is dry,
|
||||
* fall back to creating a fresh one (will probably fail autoplay
|
||||
* on Firefox Android listeners but at least logs the rejection). */
|
||||
if (audioPool.length > 0){
|
||||
const el = audioPool.shift();
|
||||
try { el.removeAttribute('src'); el.load(); } catch(_){}
|
||||
return el;
|
||||
}
|
||||
logLine('err', 'audio pool exhausted — autoplay may fail');
|
||||
const fresh = document.createElement('audio');
|
||||
fresh.autoplay = true; fresh.playsInline = true;
|
||||
fresh.style.display = 'none';
|
||||
document.body.appendChild(fresh);
|
||||
return fresh;
|
||||
}
|
||||
$('btn-enter').addEventListener('click', () => { primeAudioOnGesture(); joinSpace(); });
|
||||
$('rdv-code').addEventListener('keydown', e=>{
|
||||
|
|
@ -4880,8 +4922,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-06-04</span><br>
|
||||
md5 <span class="stamp-md5">627a038a99648b35f87e7ad75f3d057a</span><br>
|
||||
sha256 <span class="stamp-sha">a3ea7e91b1a8ed05a02414bd86ab4a4cd9968f8ec7f2bf9521f24ccc41783c29</span><br>
|
||||
md5 <span class="stamp-md5">e8ab005d36f6cf2fb05d8064d8f15a97</span><br>
|
||||
sha256 <span class="stamp-sha">8688b810622d9696cad31f3f4da127c2e8a7613ab12919b5a679d79887ca2ac3</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