diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 5d78ff99..c82fc940 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1036,6 +1036,9 @@ import flutter_foreground_task finish() } + if !endAfterReport { + prepareCallAudioCategory() + } if let plugin = SwiftFlutterCallkitIncomingPlugin.sharedInstance { plugin.showCallkitIncoming(data, fromPushKit: true, completion: afterReport) return @@ -1053,6 +1056,9 @@ import flutter_foreground_task finish() return } + if !endAfterReport { + prepareCallAudioCategory() + } voipCallReporter.report( uuid: uuid, callerName: data.nameCaller, @@ -1072,6 +1078,9 @@ import flutter_foreground_task func onAccept(_ call: Call, _ action: CXAnswerCallAction) { resumeAudioAfterCallKitEnds = true + // Dart may call activate before didActivate. Claim the session now so + // that path does not setActive or start the audio unit without a track. + callKitOwnsAudioSession = true prepareCallAudioCategory() storePendingAcceptedCall(call) recordVoipDiagnostic( @@ -1114,9 +1123,10 @@ import flutter_foreground_task rtcSession.useManualAudio = true rtcSession.audioSessionDidActivate(audioSession) applyCallAudioConfiguration(active: false) - // Do not start the audio unit here. Incoming accept has no LiveKit - // microphone track yet; enabling the unit attaches capture to nothing, - // and a later `isAudioEnabled = true` is then a no-op. + // Incoming accept has no LiveKit microphone track yet. Starting the + // unit here attaches capture to nothing; a later isAudioEnabled=true + // is a no-op and the callee microphone stays silent. Dart starts the + // unit via restart after setMicrophoneEnabled. } func didDeactivateAudioSession(_ audioSession: AVAudioSession) { @@ -1145,6 +1155,16 @@ import flutter_foreground_task } private func prepareCallAudioCategory() { + let session = AVAudioSession.sharedInstance() + do { + try session.setCategory( + .playAndRecord, + mode: .videoChat, + options: [.allowBluetooth, .defaultToSpeaker] + ) + } catch { + NSLog("[EatvioVoIP] prepare call audio category failed: \(error)") + } applyCallAudioConfiguration(active: false) } @@ -1154,23 +1174,37 @@ import flutter_foreground_task // CallKit already activated the session on answer. A second setActive // fails or replaces that session, and the incoming microphone stays dead. applyCallAudioConfiguration(active: !callKitOwnsAudioSession) - // Starting the audio unit while the configuration lock is held deadlocks - // the capture thread, so the mic stays silent. + if callKitOwnsAudioSession { + // The unit starts in restartInAppCallAudioUnit after the microphone + // track exists. Enabling it here is the incoming-only silent-mic bug. + return + } rtcSession.isAudioEnabled = true } - /// Restarts capture after the previous audio graph has disconnected. - /// Toggling the unit in the same turn makes voice processing throw. + /// Starts capture after the microphone track exists. Incoming CallKit + /// must not have started the unit earlier, or this assignment is a no-op. private func restartInAppCallAudioUnit() { audioRestartGeneration += 1 let generation = audioRestartGeneration let rtcSession = RTCAudioSession.sharedInstance() rtcSession.useManualAudio = true - rtcSession.isAudioEnabled = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in - guard let self, generation == self.audioRestartGeneration else { return } - self.activateInAppCallAudioSession() + let needsTeardown = rtcSession.isAudioEnabled + if needsTeardown { + rtcSession.isAudioEnabled = false } + let delay: TimeInterval = needsTeardown ? 0.25 : 0 + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + guard let self, generation == self.audioRestartGeneration else { return } + self.enableInAppCallAudioUnit() + } + } + + private func enableInAppCallAudioUnit() { + let rtcSession = RTCAudioSession.sharedInstance() + rtcSession.useManualAudio = true + applyCallAudioConfiguration(active: !callKitOwnsAudioSession) + rtcSession.isAudioEnabled = true } private func applyCallAudioConfiguration(active: Bool) { diff --git a/pubspec.yaml b/pubspec.yaml index 353276e3..856e55fb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: eatvio_app description: A new Flutter project. -version: 1.3.61+150 +version: 1.3.62+151 publish_to: 'none' environment: