diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 32b0008..8bdd112 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -1628,17 +1628,20 @@ function attachSfuTrack(uuid, stream){ let a = remoteAudio.get(uuid); if (!a){ a = document.createElement('audio'); a.autoplay = true; - /* always create muted=true so autoplay is universally allowed - * (muted autoplay is unconditional in all browsers). applyAudio- - * Mute() flips muted=false the moment we have a user gesture - * (btn-mute click) AND this peer's canonical path is 'rtc'. */ - a.muted = true; + a.playsInline = true; document.body.appendChild(a); remoteAudio.set(uuid, a); applySinkTo(a); } a.srcObject = stream; - if (!audioPath.has(uuid)) audioPath.set(uuid, 'rtc'); - applyAudioMute(); + /* 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)); + } catch(_){} stopMeter(uuid); startMeter(uuid, stream); logLine('', 'sfu: receiving '+((members.get(uuid)||{}).handle || uuid)); } @@ -3455,12 +3458,6 @@ async function handleSignal(raw){ * present-member-offers: existing speaker offers when a new speaker * arrives. deterministic by uuid string compare. */ if (canSpeak(myRole) && canSpeak(m.role)) connectToPeer(m.uuid, /*weOffer*/ myUUID < m.uuid); - /* if I'm a listener and a new speaker arrived, auto-enrol them - * into my DJ-mode HTTP playback after the SFU has had a moment - * to wire the Ogg writer for the publisher. */ - if (myRole === 'listener' && canSpeak(m.role) && m.uuid !== myUUID){ - setTimeout(autoEnableDjModeForListener, 600); - } renderRoom(); break; case 'peer-left': @@ -3587,11 +3584,6 @@ async function handleSignal(raw){ if (canSpeak(myRole) && canSpeak(m.role) && !peers.has(m.uuid)){ connectToPeer(m.uuid, myUUID < m.uuid); } - /* listener-side: if a peer just gained the ability to speak, - * auto-enrol them into our DJ-mode HTTP stream. */ - if (myRole === 'listener' && !canSpeak(prev) && canSpeak(m.role)){ - setTimeout(autoEnableDjModeForListener, 600); - } /* If the role transition stripped them of the ability to * publish (anything -> listener), their camera/screen/game * tiles can't get fresh RTP anymore. The demoted page calls @@ -3759,17 +3751,14 @@ async function onRoleEntered(){ if (canSpeak(mm.role)) connectToPeer(uuid, myUUID < uuid); } sfuPublish().catch(e => logLine('err','sfu publish: '+e.message)); - } else { - /* Listener landing — auto-enrol into DJ mode for every speaker so - * playback rides the deep-buffered HTTP Ogg path. Slight delay so - * the SFU has time to wire its Ogg writers for the existing - * publishers (lazy-inits on first /stream listener). Plus start - * the periodic retry loop so streams come up when the host's mic - * publish lands AFTER we already auto-enrolled (race the 600ms - * delay can't cover). */ - setTimeout(autoEnableDjModeForListener, 600); - startAutoEnrolRetryLoop(); } + /* No auto-enrol into DJ mode for listeners. Firefox Android refuses + * autoplay on every fresh