diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index 9dc788f..061c37d 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -4085,32 +4085,38 @@ const audioPath = new Map(); // uuid -> 'dj' | 'rtc'
let listenerOutputMuted = true;
function applyAudioMute(){
const silenced = (myRole === 'listener' && listenerOutputMuted);
- let touched = 0, played = 0, failed = 0;
+ let touched = 0;
for (const [uuid, a] of remoteAudio){
- const wantRtc = !silenced && audioPath.get(uuid) !== 'dj';
+ const path = audioPath.get(uuid);
+ const wantRtc = !silenced && path !== 'dj';
+ /* In a listener click context, we ALWAYS attempt to play —
+ * the gesture might be the only chance to unblock a paused
+ * element. Unmute on wantRtc=true; keep muted on wantRtc=false. */
try {
a.muted = !wantRtc;
touched++;
- if (wantRtc) {
- const p = a.play();
- if (p && p.then) p.then(()=>played++).catch(e=>{ failed++; logLine('err','rtc play '+uuid+': '+e.message); });
- else played++;
- }
- } catch(e){ failed++; logLine('err','rtc set mute '+uuid+': '+e.message); }
+ const p = a.play();
+ if (p && p.catch) p.catch(e => logLine('err','rtc play '+uuid+' paused='+a.paused+' rs='+a.readyState+' src='+!!a.srcObject+' err='+e.name+': '+e.message));
+ } catch(e){ logLine('err','rtc setmute '+uuid+': '+e.message); }
}
for (const [uuid, a] of streamAudio){
- const wantDj = !silenced && audioPath.get(uuid) === 'dj';
+ const path = audioPath.get(uuid);
+ const wantDj = !silenced && path === 'dj';
try {
a.muted = !wantDj;
touched++;
- if (wantDj) {
- const p = a.play();
- if (p && p.then) p.then(()=>played++).catch(e=>{ failed++; logLine('err','dj play '+uuid+': '+e.message); });
- else played++;
- }
- } catch(e){ failed++; logLine('err','dj set mute '+uuid+': '+e.message); }
+ const p = a.play();
+ if (p && p.catch) p.catch(e => logLine('err','dj play '+uuid+' paused='+a.paused+' rs='+a.readyState+' src='+!!a.src+' err='+e.name+': '+e.message));
+ } catch(e){ logLine('err','dj setmute '+uuid+': '+e.message); }
}
- logLine('', 'applyAudioMute silenced='+silenced+' touched='+touched+' streamMode='+streamMode.size);
+ /* state snapshot — per-element so we can see what each is actually doing */
+ for (const [uuid, a] of remoteAudio){
+ logLine('', 'rtc '+uuid.slice(0,8)+' paused='+a.paused+' muted='+a.muted+' rs='+a.readyState+' srcObj='+!!a.srcObject+' path='+audioPath.get(uuid));
+ }
+ for (const [uuid, a] of streamAudio){
+ logLine('', 'dj '+uuid.slice(0,8)+' paused='+a.paused+' muted='+a.muted+' rs='+a.readyState+' netState='+a.networkState+' src='+(a.src?a.src.slice(-20):'(none)')+' path='+audioPath.get(uuid));
+ }
+ logLine('', 'applyAudioMute silenced='+silenced+' touched='+touched);
}
function streamUrlFor(pubHex){
return SFU_BASE + '/stream?room=' + encodeURIComponent(roomID) + '&pub=' + pubHex;
@@ -4926,8 +4932,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');