Our solution hooks into the didDeactivate delegate method. When an external cellular call ends and deactivates our audio session, we introduce a 0.5-second delay to avoid system race conditions. We then check if our VoIP call is stuck on hold (isOnHold == true) via CXCallObserver. If it is, we programmatically trigger a CXSetHeldCallAction(onHold: false) transaction to force recovery and reclaim the audio pipeline."
Clean Code Architecture
Swift
public func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
log("🚨 Audio session deactivated by OS.")
// 0.5s Delay to allow CoreAudio to clear the cellular teardown
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
self?.resumeVoIPCallIfNeeded()
}
}
func resumeVoIPCallIfNeeded() {
guard let uuid = CallStateManager.shared.getNewestBy(state: .active)?.uuid else { return }
let isOnHold = CallKitManager.shared.callObserver.calls.first { $0.uuid == uuid }?.isOnHold ?? false
log("📊 Current VoIP isOnHold status: \(isOnHold)")
guard isOnHold else { return }
log("🔄 Unholding VoIP call programmatically.")
// Apple Workaround: Refresh config right before unholding to fix SessionID 0x0
if let provider = CallKitManager.shared.provider {
provider.configuration = provider.configuration
}
let action = CXSetHeldCallAction(call: uuid, onHold: false)
let transaction = CXTransaction(action: action)
CXCallController().request(transaction) { [weak self] error in
if let error = error {
self?.log("❌ Unhold failed: \(error.localizedDescription)")
} else {
self?.log("✅ Call successfully resumed.")
}
}
}
public func provider(_ provider: CXProvider,
perform action: CXSetHeldCallAction) {
log("provider performSetHeldCallAction")
if (action.isOnHold) {
action.fulfill()
} else {
configureAudioSessionEarly()
action.fulfill()
}
}
Topic:
App & System Services
SubTopic:
General
Tags: