meeting: unlock iOS media playback on Join so remote call audio isn't silent until you mute/unmute (batch143)
This commit is contained in:
+15
-2
@@ -1158,7 +1158,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="/icons.js?v=6"></script>
|
<script src="/icons.js?v=6"></script>
|
||||||
<script>window.__BUILD='2026-07-20-batch142';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
<script>window.__BUILD='2026-07-20-batch143';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||||
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
|
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
|
||||||
//
|
//
|
||||||
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
|
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
|
||||||
@@ -4535,7 +4535,7 @@ function addTile(id, stream, label, muted){
|
|||||||
if(!tile){ tile=document.createElement('div'); tile.className='meet-tile'; tile.id='meet-tile-'+id;
|
if(!tile){ tile=document.createElement('div'); tile.className='meet-tile'; tile.id='meet-tile-'+id;
|
||||||
const av=(id==='__local')?((ME&&ME.avatarUrl)||null):(meetAvatars.get(id)||null); // profile pic on the tile
|
const av=(id==='__local')?((ME&&ME.avatarUrl)||null):(meetAvatars.get(id)||null); // profile pic on the tile
|
||||||
tile.innerHTML='<video autoplay playsinline'+(muted?' muted':'')+'></video><div class="meet-av" style="background:'+avColor(label||'?')+'">'+pEsc(initials(label||'?'))+(av?'<img src="'+pEsc(av)+'" alt="" onerror="this.remove()">':'')+'</div><div class="meet-mute" style="display:none">'+ic('micOff',14)+'</div><span class="nm">'+pEsc(label||'')+'</span>'; grid.appendChild(tile); }
|
tile.innerHTML='<video autoplay playsinline'+(muted?' muted':'')+'></video><div class="meet-av" style="background:'+avColor(label||'?')+'">'+pEsc(initials(label||'?'))+(av?'<img src="'+pEsc(av)+'" alt="" onerror="this.remove()">':'')+'</div><div class="meet-mute" style="display:none">'+ic('micOff',14)+'</div><span class="nm">'+pEsc(label||'')+'</span>'; grid.appendChild(tile); }
|
||||||
const v=tile.querySelector('video'); if(v && stream && v.srcObject!==stream){ v.srcObject=stream; applySink(v); } // #5: route audio to the chosen speaker
|
const v=tile.querySelector('video'); if(v && stream && v.srcObject!==stream){ v.srcObject=stream; applySink(v); try{ v.play().catch(function(){}); }catch(_){} } // #5: route audio to the chosen speaker + kick playback (iOS won't autostart WebRTC audio)
|
||||||
const hasVid=!!(stream && stream.getVideoTracks && stream.getVideoTracks().some(t=>t.enabled && t.readyState!=='ended'));
|
const hasVid=!!(stream && stream.getVideoTracks && stream.getVideoTracks().some(t=>t.enabled && t.readyState!=='ended'));
|
||||||
tile.classList.toggle('novid', !hasVid || (meetCamOff.get(id)===true && !meetSharers.has(id))); // camOff → avatar, UNLESS they're sharing a screen (#9: screen must show even with camera off)
|
tile.classList.toggle('novid', !hasVid || (meetCamOff.get(id)===true && !meetSharers.has(id))); // camOff → avatar, UNLESS they're sharing a screen (#9: screen must show even with camera off)
|
||||||
if(meetMuted.has(id)) setTileMute(id, meetMuted.get(id)); // apply any known mute state
|
if(meetMuted.has(id)) setTileMute(id, meetMuted.get(id)); // apply any known mute state
|
||||||
@@ -4732,7 +4732,20 @@ function startSR(){ if(meetSR) return; const SR=window.SpeechRecognition||window
|
|||||||
function stopSR(){ if(meetSR){ try{ meetSR.onend=null; meetSR.stop(); }catch(_){} meetSR=null; } }
|
function stopSR(){ if(meetSR){ try{ meetSR.onend=null; meetSR.stop(); }catch(_){} meetSR=null; } }
|
||||||
function updateTransBtn(){ const b=document.getElementById('meetTransBtn'); if(!b) return; b.classList.toggle('on', meetTranscribe); b.title=meetTranscribe?'Stop my transcript':'Transcribe (your private copy)'; }
|
function updateTransBtn(){ const b=document.getElementById('meetTransBtn'); if(!b) return; b.classList.toggle('on', meetTranscribe); b.title=meetTranscribe?'Stop my transcript':'Transcribe (your private copy)'; }
|
||||||
function transcribeNotice(on){ let el=document.getElementById('txNotice'); if(on){ if(!el){ el=document.createElement('div'); el.id='txNotice'; el.className='tx-notice'; el.innerHTML=ic('fileText',12)+' Transcribing'; document.body.appendChild(el); } } else if(el){ el.remove(); } }
|
function transcribeNotice(on){ let el=document.getElementById('txNotice'); if(on){ if(!el){ el=document.createElement('div'); el.id='txNotice'; el.className='tx-notice'; el.innerHTML=ic('fileText',12)+' Transcribing'; document.body.appendChild(el); } } else if(el){ el.remove(); } }
|
||||||
|
// iOS blocks media/WebRTC audio playback until a user gesture, so remote call audio stays SILENT until you
|
||||||
|
// tap something (e.g. mute/unmute — that tap is the gesture). Unlock it: within the Join tap we play a short
|
||||||
|
// silent clip (grants the page media-playback activation) and (re)start every meeting <video>. A tap fallback
|
||||||
|
// re-runs it in case the activation lapsed before a remote stream arrived. Web fix — helps native app + PWA.
|
||||||
|
let _bzSilent=null;
|
||||||
|
function bzUnlockAudio(){
|
||||||
|
try{ if(_audioCtx && _audioCtx.state==='suspended') _audioCtx.resume(); }catch(_){}
|
||||||
|
try{ if(!_bzSilent){ _bzSilent=new Audio('data:audio/wav;base64,UklGRjIAAABXQVZFZm10IBAAAAABAAEAgLsAAAB3AQACABAAZGF0YQ4AAAAAAAAAAAAAAAAAAAAAAAA='); _bzSilent.setAttribute('playsinline',''); } _bzSilent.play().catch(function(){}); }catch(_){}
|
||||||
|
try{ document.querySelectorAll('#meetGrid video').forEach(function(v){ if(v && v.paused) v.play().catch(function(){}); }); }catch(_){}
|
||||||
|
}
|
||||||
|
document.addEventListener('touchend', function(){ if(meetState==='call') bzUnlockAudio(); }, {passive:true}); // fallback: any tap during a call restarts silent remote audio
|
||||||
|
document.addEventListener('click', function(){ if(meetState==='call') bzUnlockAudio(); }, {passive:true});
|
||||||
async function enterMeeting(code, audioOnly){
|
async function enterMeeting(code, audioOnly){
|
||||||
|
bzUnlockAudio(); // runs inside the Join tap → unlock playback so remote audio isn't silent until you tap
|
||||||
if(meetState==='call'){ switchTab('meeting'); return; } // already in a call — ignore double-join
|
if(meetState==='call'){ switchTab('meeting'); return; } // already in a call — ignore double-join
|
||||||
// Joining this room → clear any lingering incoming-call invite popup for it (and stop its ring).
|
// Joining this room → clear any lingering incoming-call invite popup for it (and stop its ring).
|
||||||
// Fixes: joining via the header "Join" button left the Join/Decline popup on screen. Belt-and-braces
|
// Fixes: joining via the header "Join" button left the Join/Decline popup on screen. Belt-and-braces
|
||||||
|
|||||||
Reference in New Issue
Block a user