简体   繁体   中英

How to respond to Sinch call in the background state?

I use Sinch for audio calls also I use CallKit . I have a problem when the application is in the background state, I get a call to CallKit via CXProviderDelegate , but the sinch client does not have active incoming calls. Please, tell me how can I solve this problem?

final class ProviderDelegate: NSObject, CXProviderDelegate {

    private let provider: CXProvider

    static let shared = ProviderDelegate()

    private override init() {
        provider = CXProvider(configuration: type(of: self).providerConfiguration)

        super.init()

        provider.setDelegate(self, queue: nil)
    }


    /// The app's provider configuration, representing its CallKit capabilities
    static var providerConfiguration: CXProviderConfiguration {
        let localizedName = NSLocalizedString("App name", comment: "Name of application")
        let providerConfiguration = CXProviderConfiguration(localizedName: localizedName)

        providerConfiguration.supportsVideo = true

        providerConfiguration.maximumCallsPerCallGroup = 1

        providerConfiguration.supportedHandleTypes = [.phoneNumber]

        if let iconMaskImage = UIImage(named: "IconMask") {
            providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation(iconMaskImage)
        }

        providerConfiguration.ringtoneSound = "Ringtone.aif"

        return providerConfiguration
    }

    // MARK: Incoming Calls

    /// Use CXProvider to report the incoming call to the system
    open func reportIncomingCall(uuid: UUID, handle: String, contactID: String, hasVideo: Bool = false, completion: ((Error?) -> Void)? = nil) {
        // Construct a CXCallUpdate describing the incoming call, including the caller.
        let update = CXCallUpdate()
        update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
        update.hasVideo = hasVideo

        // Report the incoming call to the system
        provider.reportNewIncomingCall(with: uuid, update: update) { error in
            if error == nil {
                let call = SwiftlyChatCall(uuid: uuid, contactID: contactID)
                call.handle = handle

                SwiftlyChatCallManager.shared.addCall(call)
            }

            completion?(error)
        }
    }

    // MARK: CXProviderDelegate

    func providerDidReset(_ provider: CXProvider) {
        print("Provider did reset")

        AudioManager.shared.stopAudio()

        /*
         End any ongoing calls if the provider resets, and remove them from the app's list of calls,
         since they are no longer valid.
         */
        for call in SwiftlyChatCallManager.shared.calls {
            call.endSpeakerboxCall()
        }

        // Remove all calls from the app's list of calls.
        SwiftlyChatCallManager.shared.removeAllCalls()
    }

    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        debugPrint("Provider, CXAnswerCallAction")
        guard SwiftlyChatCallManager.shared.callWithUUID(uuid: action.callUUID) != nil else {
            debugPrint("CXAnswerCallAction fail")
            action.fail()
            return
        }

        SinchManager.default.answerCall()

        // Signal to the system that the action has been successfully performed.
        action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        guard let call = SwiftlyChatCallManager.shared.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        debugPrint("CXEndCallAction", #function)
        SinchManager.default.cancelCall()

        // Signal to the system that the action has been successfully performed.
        action.fulfill()

        // Remove the ended call from the app's list of calls.
        SwiftlyChatCallManager.shared.removeCall(call)
    }

    func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
        debugPrint("provider CXSetHeldCallAction")
        guard let call = SwiftlyChatCallManager.shared.callWithUUID(uuid: action.callUUID) else {
            action.fail()
            return
        }

        // Update the SpeakerboxCall's underlying hold state.
        call.isOnHold = action.isOnHold

        // Stop or start audio in response to holding or unholding the call.
        if call.isOnHold {
            AudioManager.shared.stopAudio()
        } else {
            AudioManager.shared.startAudio()
        }

        // Signal to the system that the action has been successfully performed.
        action.fulfill()

        // Remove the ended call from the app's list of calls.

        SwiftlyChatCallManager.shared.removeCall(call)
    }

    func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
        print("Timed out \(#function)")

        // React to the action timeout if necessary, such as showing an error UI.
    }

    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        print("Received \(#function)")
        // Start call audio media, now that the audio session has been activated after having its priority boosted.
        SinchManager.default.callKitDidActive(provider, audioSession: audioSession)
    }

    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        print("Received \(#function)")

        /*
         Restart any non-call related audio now that the app's audio session has been
         de-activated after having its priority restored to normal.
         */
    }

}


final class VOIPManager: NSObject {

    private override init() {
        super.init()
    }

    static let `default` = VOIPManager()

    private let incomingCallIdentificator = "SIN_INCOMING_CALL"
    private let canceledIncomingCallIndentificator = "SIN_CANCEL_CALL"

    open func registration() {
        let mainQueue = DispatchQueue.main
        // Create a push registry object
        let voipRegistry = PKPushRegistry(queue: mainQueue)
        // Set the registry's delegate to self
        voipRegistry.delegate = self
        // Set the push type to VoIP
        voipRegistry.desiredPushTypes = [.voIP]
    }

}

extension VOIPManager: PKPushRegistryDelegate {

    func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        // Register VoIP push token (a property of PKPushCredentials) with server
        guard type == .voIP else { return }
        SinchManager.default.registerDeviceToken(pushCredentials.token)
    }

    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        guard type == .voIP else { return }
    }

    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        guard type == .voIP else { return }
        debugPrint("didReceiveIncomingPushWith")
        DispatchQueue.global(qos: .default).async {
            guard var dict = payload.dictionaryPayload as? [String : Any] else { return }
            debugPrint("dict", dict)
            guard let sinString = dict["sin"] as? String else { return }
            guard let sinDict = sinString.toDictionary() else { return }
            dict["sin"] = sinDict
            guard let sinchIncomingCall = Mapper<SinchIncomingCallModel>().map(JSON: dict) else { return }
            let lockKey = sinchIncomingCall.aps.alert.locKey

            if lockKey == self.incomingCallIdentificator {
                self.incomingCallAction(sinchIncomingCall)
            } else if lockKey == self.canceledIncomingCallIndentificator {
                self.canceledIncomingCallAction(sinchIncomingCall)
            }
        }
    }

}

// MARK: - Actions

extension VOIPManager {

    private func incomingCallAction(_ sinchIncomingCall: SinchIncomingCallModel) {
        self.getDataForIncomingCall(sinchIncomingCall) { (contactID, phone) in
            DispatchQueue.global(qos: .default).async {
                self.displayIncomingCall(uuid: UUID(), handle: phone, contactID: contactID)
            }
        }
    }

    private func canceledIncomingCallAction(_ sinchIncomingCall: SinchIncomingCallModel) {
        self.getDataForIncomingCall(sinchIncomingCall) { (contactID, _) in
            SwiftlyChatCallManager.shared.end(contactID)
        }
    }

    private func displayIncomingCall(uuid: UUID, handle: String, contactID: String, hasVideo: Bool = false, completion: ((Error?) -> Void)? = nil) {
        ProviderDelegate.shared.reportIncomingCall(uuid: uuid, handle: handle, contactID: contactID, hasVideo: hasVideo, completion: completion)
    }

    private func getDataForIncomingCall(_ sinchIncomingCall: SinchIncomingCallModel, completion: ((_ contactID: String, _ phone: String) -> Void)?) {
        DispatchQueue.global(qos: .default).async {
            let contactsRealmManager = ContactsRealmManager()
            guard let contact = contactsRealmManager.getContact(sinchIncomingCall.sin.userID) else { return }
            let phoneNumber = contact.firstPhoneNumber() ?? ""
            completion?(contact.id, phoneNumber)
        }
    }


}

I also have SinchManager and it has this method

extension SinchManager {

    open func activeAudioSession(_ provider: CXProvider, audioSession: AVAudioSession) {
        sinch?.call().provider(provider, didActivate: audioSession)
    }

}

I solved this problem. I added this method to SinchManager

open func relayRemotePushNotification(_ userInfo: [AnyHashable : Any]) {
        DispatchQueue.main.async {
            guard let result = self.sinch?.relayRemotePushNotification(userInfo) else { return }
            guard result.isCall() else { return }
            debugPrint("result.isCall()", result.isCall())
        }
    }

and call this method here

   func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        guard type == .voIP else { return }
        debugPrint("VOIPmanager didReceiveIncomingPushWith")
        DispatchQueue.global(qos: .default).async {
            SinchManager.default.relayRemotePushNotification(payload.dictionaryPayload)
        }
    }

In the download package of Sinch iOS from Sinch SDK download , there is one reference implementation (Sinch CallKit sample App) which shows most use cases of handling incoming calls via CallKit framework. Please take a look there (though the sample app is still in Objc). If you have uploaded a valid VOIP push certificate for your App on Sinch portal, it should be fairly easy to install that App and play with it on your iPhone.

Below is a demo video of the CallKit sample App, for handling call in foreground, background and locked screen mode.

Sinch CallKit App Demo Video

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