fix(ios audio): stop the speaker<->BT oscillation; single override, no re-force

v1.1.1's route observer re-forced speaker on every change, and WebKit re-added Bluetooth
each time -> the audio flapped speaker<->BT many times/sec (telemetry: dozens of route
flips from 3 taps), which read as 'sound doesn't switch'. WKWebView won't let the app
hold the built-in speaker over an active BT device. So: one override per tap, observer
only REPORTS the output (no fighting). Also stop dimming the iOS button (it's a live
output indicator, not on/off; the dim read as 'disabled' on BT). Probe now carries the
native marker so we can confirm the binary. plugin v1.1.2-stable, web batch150.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 11:54:42 +05:30
parent cfbf950173
commit 44526e1ee9
3 changed files with 31 additions and 33 deletions
+14 -8
View File
@@ -1158,7 +1158,7 @@
</head>
<body>
<script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-22-batch149';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
<script>window.__BUILD='2026-07-22-batch150';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.
//
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
@@ -4438,9 +4438,15 @@ function routeLabel(){
function refreshSpkBtn(){
const b=document.getElementById('meetSpkBtn'); if(!b) return;
if(meetRoute==='earpiece' && nativeAudioRoute()) meetRoute='speaker'; // iOS: earpiece is unreachable in the WebView; coerce stale prefs
const onSpk = iosRoute() ? (_nativeOutput==='speaker'||_nativeOutput==='receiver'||_nativeOutput==='unknown') : (meetRoute==='speaker');
meetSpeakerOn=onSpk;
b.classList.toggle('off', !onSpk);
if(iosRoute()){
// On iOS the button is a live OUTPUT indicator (speaker/bluetooth/headphones), not a speaker on/off — so
// never dim it (a dimmed icon read as "disabled" when audio was simply on Bluetooth).
meetSpeakerOn=(_nativeOutput==='speaker');
b.classList.remove('off');
} else {
meetSpeakerOn=(meetRoute==='speaker');
b.classList.toggle('off', meetRoute!=='speaker');
}
b.innerHTML=ic(spkIcon(),20);
b.title=routeLabel()+' — tap to switch';
}
@@ -4449,14 +4455,14 @@ let _bzNativeRouteReady=false;
function bzInitNativeRoute(){
const ar=nativeAudioRoute(); if(!ar) return;
if(!_bzNativeRouteReady && ar.addListener){ _bzNativeRouteReady=true;
try{ ar.addListener('routeChange', function(e){ bzOnNativeRoute(e && e.output); }); }catch(_){}
try{ ar.addListener('routeChange', function(e){ bzOnNativeRoute(e && e.output, e && e.native); }); }catch(_){}
}
if(ar.getRoute){ try{ ar.getRoute().then(function(r){ bzOnNativeRoute(r && r.output); }).catch(function(){}); }catch(_){} }
if(ar.getRoute){ try{ ar.getRoute().then(function(r){ bzOnNativeRoute(r && r.output, r && r.native); }).catch(function(){}); }catch(_){} }
}
function bzOnNativeRoute(output){
function bzOnNativeRoute(output, nat){
if(!output) return;
_nativeOutput=output;
if(window.bzDbg) window.bzDbg('nroute',{out:output, want:meetRoute}); // TEMP verify speaker-over-BT (remove after confirm)
if(window.bzDbg) window.bzDbg('nroute',{out:output, want:meetRoute, nat:nat||null}); // TEMP verify speaker-over-BT (remove after confirm)
meetRoute = (output==='bluetooth'||output==='wired') ? 'bt' : (output==='speaker' ? 'speaker' : meetRoute);
refreshSpkBtn();
}