diff --git a/mobile/plugins/audio-route/ios/Sources/AudioRoutePlugin/AudioRoutePlugin.swift b/mobile/plugins/audio-route/ios/Sources/AudioRoutePlugin/AudioRoutePlugin.swift index bd0f3d6..df0fa9c 100644 --- a/mobile/plugins/audio-route/ios/Sources/AudioRoutePlugin/AudioRoutePlugin.swift +++ b/mobile/plugins/audio-route/ios/Sources/AudioRoutePlugin/AudioRoutePlugin.swift @@ -7,14 +7,15 @@ import AVFoundation // // WHY THIS SHAPE (earlier version left the earpiece silent): // * The category is configured WITHOUT .defaultToSpeaker, so overrideOutputAudioPort(.none) reliably means -// the earpiece/receiver. WITH .defaultToSpeaker, .none falls back to the loudspeaker and the earpiece -// never receives audio. -// * setSpeaker flips ONLY overrideOutputAudioPort. It must NOT re-setCategory/setActive mid-call: doing so -// tears down the audio unit WebKit's WebRTC engine is actively using and leaves the earpiece route silent -// (forcing .speaker is a strong enough override to survive it, which is why only speaker "worked"). -// * WebKit reconfigures the shared AVAudioSession when call audio starts (and on headset plug/unplug), which -// would clobber our choice — so we observe routeChangeNotification and re-assert the desired port. That -// also removes the "call starts on speaker then drops to silence" flicker. +// the earpiece/receiver. WITH .defaultToSpeaker, .none falls back to the loudspeaker. +// * setSpeaker flips ONLY overrideOutputAudioPort — no re-setCategory/setActive mid-call, which would tear +// down the audio unit WebKit's WebRTC engine is using and silence the earpiece. +// * WebKit reconfigures the shared AVAudioSession when call audio starts / on headset plug-unplug, which +// would clobber our choice — so we observe routeChangeNotification and re-assert the desired port. +// +// DIAGNOSTICS: setSpeaker resolves with the ACTUAL current output port + live category/mode/options and a +// native-build marker, so the JS `route` telemetry shows exactly where iOS put the audio (the web __BUILD +// tag can't distinguish native binaries). This is what turns "override didn't throw" into "audio is on X". @objc(AudioRoutePlugin) public class AudioRoutePlugin: CAPPlugin, CAPBridgedPlugin { public let identifier = "AudioRoutePlugin" @@ -23,6 +24,9 @@ public class AudioRoutePlugin: CAPPlugin, CAPBridgedPlugin { CAPPluginMethod(name: "setSpeaker", returnType: CAPPluginReturnPromise) ] + // Bump on every native change so the telemetry unambiguously identifies which binary is running. + private static let nativeTag = "1.0.2-diag" + // Desired output — the source of truth, re-applied on every route change. Defaults to earpiece; the JS // side calls setSpeaker(true) at call start when it wants the loudspeaker (meetRoute defaults to 'speaker'). private var wantSpeaker = false @@ -66,13 +70,27 @@ public class AudioRoutePlugin: CAPPlugin, CAPBridgedPlugin { } catch { } } + // Snapshot of what iOS actually selected — this is the data that ends the guessing. + private func diag(_ applied: String) -> [String: Any] { + let s = AVAudioSession.sharedInstance() + let outs = s.currentRoute.outputs.map { $0.portType.rawValue }.joined(separator: "+") + return [ + "route": applied, + "native": AudioRoutePlugin.nativeTag, + "outputs": outs.isEmpty ? "(none)" : outs, // "Receiver"=earpiece, "Speaker", "BluetoothA2DP"... + "category": s.category.rawValue, + "mode": s.mode.rawValue, + "opts": Int(s.categoryOptions.rawValue) + ] + } + @objc func setSpeaker(_ call: CAPPluginCall) { wantSpeaker = call.getBool("on") ?? true let session = AVAudioSession.sharedInstance() do { // Flip ONLY the output port. No setCategory/setActive here — that would disrupt WebKit's audio. try session.overrideOutputAudioPort(wantSpeaker ? .speaker : .none) - call.resolve(["route": wantSpeaker ? "speaker" : "earpiece"]) + call.resolve(diag(wantSpeaker ? "speaker" : "earpiece")) } catch { call.reject(error.localizedDescription) } diff --git a/mobile/plugins/audio-route/package.json b/mobile/plugins/audio-route/package.json index f9e50ff..37b361e 100644 --- a/mobile/plugins/audio-route/package.json +++ b/mobile/plugins/audio-route/package.json @@ -1,6 +1,6 @@ { "name": "audio-route", - "version": "1.0.1", + "version": "1.0.2", "description": "iOS earpiece/speaker audio route toggle for Biz Connect", "main": "dist/plugin.cjs.js", "module": "dist/esm/index.js",