3

I'm integrating calls in my app. In CallKit documentation https://developer.apple.com/documentation/callkit there is a text:

After the call is connected, the system calls the provider(_:perform:) method of the provider delegate. In your implementation, the delegate is responsible for configuring an AVAudioSession and calling fulfill() on the action when finished.

And code snippet:

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
    // configure audio session
    action.fulfill()
}

And about fulfill() method:

You should only call this method from the implementation of a CXProviderDelegate method.

So as I understood, we should call action.fulfill() immediately, after callee accepts a call.

Problem: When device is locked, call timer on native CallKit screen starts counting, but connection is not established yet.

Question: How can I call action.fulfill() or start the timer on the locked CallKit screen, when connection will be established. Can I control this timer label on locked screen somehow?

1 Answers1

1

It is expected that you call the answering logic in this delegate callback

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
    // configure audio session
    // Performing call answering logic
    // Call action to fulfill after answering logic is finished
    action.fulfill()
}

I am using PJSIP for VoIP calls and this is how my answering logic looks like this

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {

    guard let call = CallList.sharedInstance().call(withUUID: action.callUUID.uuidString) else {
        action.fail()
        return
    }

    CallAudio.configureAudioSession()

    // Answer call (signal Pjsua)
    Pjsua2Wrapper.sharedInstance()?.answerCall(withCallUUID: call.callUUID, completion: { error in
        if error != nil {
            // we have error on answer call
            action.fail()
        } else {
            action.fulfill()
        }
    })
}

Answer call's completion handler is called when answering is done, so the timer will start when the call is connected.

EDIT

Sound issue with PJSIP

If using PJSIP, you need to stop the audio I/O when PJSIP is initialized, like so (it is C++):

enableSoundDevice( false );

And when call is answered, CallKit will respond with func provider(_:didActivate:). That is where you need to activate the PJSIP sound device in order to have the sound.

This is the example from my code:

func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
    // Start call audio I/O once CallKit activates AVAudioSession
    Pjsua2Wrapper.sharedInstance()?.enableSoundDevice(true)
}

And, ofcourse, when call is ended or set on hold, when CallKit responds with provider(_:didDeactivate:), you disable PJSIP sound device again and that's it

func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
    // Stop call audio IO
    Pjsua2Wrapper.sharedInstance()?.enableSoundDevice(false)
}
Miki
  • 903
  • 7
  • 26
  • Tried this, it's worked for the timer, but somehow I can't hear anything after connection established. Did you have problems with audio? – Roman Osadchuk Apr 02 '19 at 14:44
  • Yes, I had problems with audio. If you are using PJSIP, there is a simple solution, in code, where PJSIP is initialized, you just need to stop audio I/O. In my code, it looks like this: enableSoundDevice( false ); – Miki Apr 03 '19 at 07:05
  • And remember that you need to enable the sound device (start audio I/O) when CXProviderDelegate activates the audio session with, func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) – Miki Apr 03 '19 at 07:06