简体   繁体   中英

Voip call got crashed when app is background and triggered endCall method. (After adding timer for unanswered case)

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:

  • The incoming call is shows when app is in foreground.
  • User do nothing and call was cancelled (the timer got triggered.)
  • User minimised the app (app is in background), and then received new voip call update. The app got crashed with message terminating with uncaught exception of type NSException

*** 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM