Reputation: 6861
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)
}
}
Upvotes: 1
Views: 2488
Reputation: 181
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.
Upvotes: 1
Reputation: 6861
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)
}
}
Upvotes: 1