I'm trying to implement unanswered case for voip call.
When inside reportNewIncomingCall
's completion I started internal timer to track timeout for 60sec.
public final class CallCenter: NSObject {
fileprivate var sessionPool = [UUID: String]()
public func showIncomingCall(of session: String, completion: @escaping () -> Void) {
let callUpdate = CXCallUpdate()
callUpdate.remoteHandle = CXHandle(type: .generic, value: session)
callUpdate.localizedCallerName = session
callUpdate.hasVideo = true
callUpdate.supportsDTMF = false
let uuid = pairedUUID(of: session)
provider.reportNewIncomingCall(with: uuid, update: callUpdate, completion: { [unowned self] error in
if let error = error {
print("reportNewIncomingCall error: \(error.localizedDescription)")
}
// We cant auto dismiss incoming call since there is a chance to get another voip push for cancelling the call screen ("reject") from server.
let timer = Timer(timeInterval: incomingCallTimeoutDuration, repeats: false, block: { [unowned self] timer in
self.endCall(of: session, at: nil, reason: .unanswered)
self.ringingTimer?.invalidate()
self.ringingTimer = nil
})
timer.tolerance = 0.5
RunLoop.main.add(timer, forMode: .common)
ringingTimer = timer
completion()
})
}
public func endCall(of session: String, at: Date?, reason: CallEndReason) {
let uuid = pairedUUID(of: session)
provider.reportCall(with: uuid, endedAt: at, reason: reason.reason)
}
}
When peer user (caller) declined, I will get another voip notification and i'm calling this.
callCenter.endCall(of: caller, at: Date(), reason: .declinedElsewhere)
Scenario:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP push.'
Appdelegate:
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
print("Payload: \(payload.dictionaryPayload)")
guard let data = payload.dictionaryPayload as? [String: Any],
let userID = data["user"] as? UInt64,
let event = data["event"] as? String,
let caller = data["callerName"] as? String
else {
print("Incoming call failed due to missing keys.")
callCenter.showIncomingCall(of: "Unknown") { [unowned self] in
self.callCenter.endCall(of: "Unknown", at: nil, reason: .failed)
completion()
}
return
}
switch event {
case "reject":
callCenter.endCall(of: caller, at: Date(), reason: .declinedElsewhere)
callingUser = nil
completion()
return;
case "cancel":
callCenter.endCall(of: caller, at: Date(), reason: .answeredElsewhere)
callingUser = nil
completion()
return;
default: break
}
let callingUser = CallingUser(session: caller, userID: userID)
callCenter.showIncomingCall(of: callingUser.session) {
completion()
}
self.callingUser = callingUser
}
Above scenario works well without unanswered case. Means, i can able to trigger endCall method (with any reason) when app is in background. And it works. So i think issue is with the timer. Basically I'm calling endCall method with same UUID and for different reasons. And its works fine if I remove timer logic.
What's best practice or recommended way to implement unanswered case.? Where did I go wrong?
I can above to resolve this issue by initiating a fake call if there is no Active calls in the app.
private func showFakeCall(of session: String, callerName: String, completion: @escaping () -> Void) {
callCenter.showIncomingCall(of: session, callerName: callerName) { [unowned self] in
self.callCenter.endCall(of: session, at: nil, reason: .failed)
print("Print end call inside Fakecall")
completion()
}
}
Added following check for all of the call events (reject, cancel)
if !callCenter.hasActiveCall(of: channelName) {
print("No Active calls found. Initiating Fake call.")
showFakeCall(of: channelName, callerName: callerName, completion: completion)
return
}
Extra tip: You have to reinstall (uninstall first), if you made any changes to CXProvider/CXCallController configurations.