Ios 如何在后台状态下响应Sinch呼叫?

Ios 如何在后台状态下响应Sinch呼叫?,ios,swift,voip,sinch,callkit,Ios,Swift,Voip,Sinch,Callkit,我使用Sinch进行音频通话,也使用CallKit。当应用程序处于后台状态时,我遇到了一个问题,我通过CXProviderDelegate接到了对CallKit的呼叫,但sinch客户端没有活动的传入呼叫。请告诉我如何解决这个问题 final class ProviderDelegate: NSObject, CXProviderDelegate { private let provider: CXProvider static let shared = ProviderDe

我使用Sinch进行音频通话,也使用
CallKit
。当应用程序处于后台状态时,我遇到了一个问题,我通过
CXProviderDelegate
接到了对
CallKit
的呼叫,但sinch客户端没有活动的传入呼叫。请告诉我如何解决这个问题

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)
        }
    }


}

我解决了这个问题。我将此方法添加到
sinchmager

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())
        }
    }
在这里调用这个方法

   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)
        }
    }

在Sinch iOS的下载包中,有一个参考实现(Sinch CallKit示例应用程序),它显示了通过CallKit框架处理来电的大多数用例。请看一下(尽管示例应用程序仍在Objc中)。如果您已经在Sinch portal上为您的应用上传了有效的VOIP推送证书,那么安装该应用并在iPhone上使用它应该相当容易

下面是CallKit示例应用程序的演示视频,用于在前台、后台和锁定屏幕模式下处理呼叫


你能帮我写下全部代码吗?@PratyushPratik写下你的问题,给它一个链接,如果我知道我会写答案的话。我正在将sinch应用程序添加到应用程序调用中。我的app-to-app call正在运行,但我想向其中添加call kit,但找不到任何示例代码来添加它。我自己试过了,但没用。你能帮我一些示例代码吗?提前谢谢。@Alexander嗨Alex,很抱歉打扰您,首先,我要感谢您为使CallKit在Swift代码中工作所付出的努力。看到辛奇提供的快速支持如此之少,我有点惊讶和失望。我还在我的应用程序中部署Sinch,即使我的生活依赖于它,我也无法编写Obj-C!我想知道你是否乐意分享你的迅捷工具Sinch CallKit?如果你能帮忙,我真的很感激@cjensen嗨,Christian,我想知道Sinch是否会很快提供Swift呼叫套件示例?谢谢,伙计!在objective C中也没有例子,请Bo Li,你能给我objective C代码或直接链接到代码吗?我将对此表示感谢。我已在后台处理该呼叫,但当app kill i未能处理itI时,我已在上述原始答案中更新了Sinch Mobile SDK下载url,请重试并从“Voice SDK w/Video”部分下载该软件包
   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)
        }
    }