后台状态下如何响应Sinch的调用?
How to respond to Sinch call in the background state?
我使用 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)
}
}
我解决了这个问题。我将此方法添加到 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)
}
}
在 Sinch SDK download 的 Sinch iOS 下载包中,有一个参考实现(Sinch CallKit 示例应用程序)显示了通过 CallKit 框架处理来电的大多数用例。请看那里(尽管示例应用程序仍在 Objc 中)。如果您在 Sinch 门户上为您的应用程序上传了有效的 VOIP 推送证书,那么安装该应用程序并在您的 iPhone.
上使用它应该相当容易
下面是 CallKit 示例应用程序的演示视频,用于在前台、后台和锁屏模式下处理呼叫。
我使用 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)
}
}
我解决了这个问题。我将此方法添加到 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)
}
}
在 Sinch SDK download 的 Sinch iOS 下载包中,有一个参考实现(Sinch CallKit 示例应用程序)显示了通过 CallKit 框架处理来电的大多数用例。请看那里(尽管示例应用程序仍在 Objc 中)。如果您在 Sinch 门户上为您的应用程序上传了有效的 VOIP 推送证书,那么安装该应用程序并在您的 iPhone.
上使用它应该相当容易下面是 CallKit 示例应用程序的演示视频,用于在前台、后台和锁屏模式下处理呼叫。