【问题标题】:How to respond to Sinch call in the background state?后台状态下如何响应 Sinch 调用?
【发布时间】:2018-04-06 10:46:30
【问题描述】:

我使用 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)
        }
    }


}

我也有SinchManager,它有这个方法

extension SinchManager {

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

}

【问题讨论】:

  • 你能帮我完成整个代码吗?
  • @PratyushPratik 写下你的问题,给它一个链接,如果我知道我会写答案。
  • 我正在将 sinch 应用程序添加到应用程序调用中。我的应用程序到应用程序调用正在运行,但我想向它添加调用工具包,但我找不到任何示例代码来添加它。我自己尝试过,但没有奏效。你能帮我一些示例代码吗?提前致谢。
  • @Alexander 嗨,Alex,很抱歉给您带来麻烦,首先,我要感谢您努力让 CallKit 在 Swift 代码中工作。看到 Sinch 提供的 Swift 支持如此之少,我感到有点惊讶和失望。我还在我的应用程序中部署了 Sinch,即使我的生活依赖于它,我也无法编写 Obj-C!我想知道您是否乐意分享您的 Sinch CallKit 快速实现?如果您能提供帮助,我们将不胜感激!
  • @cjensen 你好 Christian,我想知道 Sinch 是否会很快提供 Swift CallKit 示例?谢谢,伙计!

标签: ios swift voip sinch callkit


【解决方案1】:

我解决了这个问题。我把这个方法加到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())
        }
    }

并在此处调用此方法

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

【讨论】:

    【解决方案2】:

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

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

    Sinch CallKit App Demo Video

    【讨论】:

    • 在目标 C 中也没有示例,请李波你能给我目标 C 代码或直接链接以获取代码,我将对此表示感谢。我已经在后台处理了呼叫,但是当应用程序终止时我无法处理它
    • 我已经更新了上面原始答案中的 Sinch Mobile SDK 下载地址,请重试并从“Voice SDK w/ Video”部分下载包
    猜你喜欢
    • 2014-12-07
    • 1970-01-01
    • 2018-08-19
    • 1970-01-01
    • 2019-07-15
    • 2020-01-05
    • 1970-01-01
    • 1970-01-01
    • 2013-12-03
    相关资源
    最近更新 更多