Как ответить на вызов Sinch в фоновом режиме?

Я использую Sinch для аудиозвонков, также я использую CallKit. У меня проблема, когда приложение находится в фоновом состоянии, я получаю вызов CallKit через CXProviderDelegate, но у синч-клиента нет активных входящих вызовов. Подскажите, пожалуйста, как мне решить эту проблему?

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

}

person Alexander Khitev    schedule 06.04.2018    source источник
comment
Можете ли вы помочь мне с общим кодом?   -  person Pratyush Pratik    schedule 29.11.2018
comment
@PratyushPratik Напишите свой вопрос, дайте ссылку на него, если узнаю напишу ответ.   -  person Alexander Khitev    schedule 29.11.2018
comment
Я добавляю приложение sinch к вызову приложения. Мое приложение для вызова приложения работает, но я хочу добавить к нему набор вызовов, и я не смог найти пример кода для его добавления. Я попробовал это сам, но это не сработало. Можете ли вы помочь мне с примером кода? Заранее спасибо.   -  person Pratyush Pratik    schedule 29.11.2018
comment
@Alexander Привет, Алекс, извините за беспокойство, и, прежде всего, я хотел бы поблагодарить вас за то, что вы приложили усилия, чтобы заставить CallKit работать в коде Swift. Я немного удивлен и разочарован, увидев, как мало поддержки Swift предложил Sinch. Я также развертываю Sinch в своем приложении и не могу кодировать Obj-C, даже если от этого зависит моя жизнь! Мне интересно, не могли бы вы поделиться своей быстрой реализацией Sinch CallKit? Очень признателен, если вы можете помочь!   -  person    schedule 30.12.2018
comment
@cjensen Привет, Кристиан, мне интересно, скоро ли Синч предоставит пример Swift CallKit? Спасибо друг!   -  person    schedule 31.12.2018


Ответы (2)


Я решил эту проблему. Я добавил этот метод в 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)
        }
    }
person Alexander Khitev    schedule 06.04.2018

В пакете загрузки Sinch iOS из загрузки Sinch SDK, существует одна эталонная реализация (пример приложения Sinch CallKit), которая показывает большинство вариантов использования обработки входящих вызовов через платформу CallKit. Пожалуйста, взгляните туда (хотя пример приложения все еще находится в Objc). Если вы загрузили действительный push-сертификат VOIP для своего приложения на портале Sinch, установить это приложение и поиграть с ним на своем iPhone должно быть довольно просто.

Ниже приведено демонстрационное видео примера приложения CallKit для обработки вызовов на переднем плане, в фоновом режиме и в режиме блокировки экрана.

Демонстрационное видео приложения Sinch CallKit

person Bo Li    schedule 09.04.2018
comment
в задаче C также нет примера, пожалуйста, Бо Ли, не могли бы вы дать мне код цели C или прямую ссылку, чтобы добраться до кода, я буду благодарен за это. я обрабатываю вызов в фоновом режиме, но когда приложение убивает, я не могу его обработать - person Shakeel Ahmed; 01.07.2019
comment
Я обновил URL-адрес загрузки Sinch Mobile SDK в исходном ответе выше, попробуйте еще раз и загрузите пакет из раздела Voice SDK с видео. - person Bo Li; 03.07.2019