diff --git a/mobile/plugins/native-call/NativeCall.podspec b/mobile/plugins/native-call/NativeCall.podspec index c274b48..ccbc86b 100644 --- a/mobile/plugins/native-call/NativeCall.podspec +++ b/mobile/plugins/native-call/NativeCall.podspec @@ -18,8 +18,9 @@ Pod::Spec.new do |s| s.ios.deployment_target = '14.0' s.dependency 'Capacitor' # LiveKit iOS SDK — carries the call media NATIVELY so audio survives backgrounding AND coordinates with - # CallKit's audio session (AudioManager.setEngineAvailability on didActivate/didDeactivate), which the - # WebView's WebRTC could not do. Pulls in the WebRTC binary transitively. + # CallKit's audio session (auto-config OFF via AudioManager.shared.audioSession.isAutomaticConfigurationEnabled; + # we configure the session in CXProvider didActivate), which the WebView's WebRTC could not do. Pulls in the + # WebRTC binary transitively. s.dependency 'LiveKitClient', '~> 2.0' # CallKit + PushKit + AVFoundation are system frameworks (no external pod). s.frameworks = 'CallKit', 'PushKit', 'AVFoundation' diff --git a/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift b/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift index 00cadc0..103613f 100644 --- a/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift +++ b/mobile/plugins/native-call/ios/Sources/NativeCallPlugin/NativeCallPlugin.swift @@ -55,12 +55,12 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega registry.desiredPushTypes = [.voIP] pushRegistry = registry - // CallKit audio coordination. LiveKit auto-configures & activates the AVAudioSession by default, which - // RACES CallKit's own activation → intermittent dead mic / no audio, and the output route only settling - // once audio starts flowing (the "speaker turns on late" symptom). Fix (per LiveKit's CallKit guide): - // disable auto-config, keep the audio engine OFF, and start it ONLY inside CXProvider didActivate. + // CallKit audio coordination. LiveKit auto-configures the AVAudioSession by default, which RACES + // CallKit's own activation → intermittent dead mic / no audio + the output route only settling once + // audio starts flowing (the "speaker turns on late" symptom). Fix (per LiveKit's audio.md): turn OFF + // LiveKit's automatic session configuration and configure the session ourselves in CXProvider + // didActivate (when CallKit hands us the active session). AudioManager.shared.audioSession.isAutomaticConfigurationEnabled = false - try? AudioManager.shared.setEngineAvailability(.none) } // MARK: - LiveKit media @@ -291,19 +291,19 @@ public class NativeCallPlugin: CAPPlugin, CAPBridgedPlugin, PKPushRegistryDelega action.fulfill() } - // CallKit owns the AVAudioSession lifecycle. Configure the session and START LiveKit's audio engine ONLY - // here (never before) — this is the fix for the intermittent dead mic / no-audio and late speaker routing. + // CallKit owns the AVAudioSession lifecycle. Since we disabled LiveKit's automatic configuration, configure + // the session HERE, when CallKit activates it — this is the fix for the intermittent dead mic / no-audio + // and late speaker routing (LiveKit's audio engine then runs on a correctly-configured, active session). + // Note: don't call setActive(true) — CallKit already activated it. public func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) { do { try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.mixWithOthers]) - try AudioManager.shared.setEngineAvailability(.default) notifyListeners("audioActivated", data: ["ok": true]) } catch { notifyListeners("audioActivated", data: ["ok": false, "error": String(describing: error)]) } } public func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { - try? AudioManager.shared.setEngineAvailability(.none) // stop the engine until the next call's didActivate notifyListeners("audioDeactivated", data: ["ok": true]) } }