你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

与 CallKit 集成

本文档介绍如何将 CallKit 与 iOS 应用程序集成。

先决条件

CallKit 集成(在 SDK 中)

Azure 通信服务 iOS SDK 中的 CallKit 集成为我们处理与 CallKit 的交互。 若要执行任何调用作(如静音/取消静音、保留/恢复),只需在 Azure 通信服务 SDK 上调用 API。

使用 CallKitOptions 初始化调用代理

使用配置的 CallKitOptions 实例,我们可以创建 CallAgent 并处理 CallKit

let options = CallAgentOptions()
let callKitOptions = CallKitOptions(with: createProviderConfig())
options.callKitOptions = callKitOptions

// Configure the properties of `CallKitOptions` instance here

self.callClient!.createCallAgent(userCredential: userCredential,
    options: options,
    completionHandler: { (callAgent, error) in
    // Initialization
})

指定外拨电话的接收方信息

首先,我们需要为传出呼叫创建StartCallOptions()实例,或者为组呼叫创建JoinCallOptions()实例。

let options = StartCallOptions()

let options = JoinCallOptions()

然后创建CallKitRemoteInfo的实例

options.callKitRemoteInfo = CallKitRemoteInfo()
  1. callKitRemoteInfo.displayNameForCallKit 分配值以自定义呼叫接收者的显示名称,并配置 CXHandle 的值。 在displayNameForCallKit中指定的值正是它在最后一个拨号通话日志中显示的样子。 在最后一个拨号呼叫日志中。
options.callKitRemoteInfo.displayNameForCallKit = "DISPLAY_NAME"
  1. 分配 cxHandle 值,它是用户回拨该联系人时应用程序接收的值。
options.callKitRemoteInfo.cxHandle = CXHandle(type: .generic, value: "VALUE_TO_CXHANDLE")

为来电指定呼叫收件人信息

首先,我们需要创建一个CallKitOptions的实例。

let callKitOptions = CallKitOptions(with: createProviderConfig())

配置 CallKitOptions 实例的属性:

当我们收到来电时,SDK 会调用传递给变量 provideRemoteInfo 的块。我们需要获取来电者的显示名称,并将其传递给 CallKit。

callKitOptions.provideRemoteInfo = self.provideCallKitRemoteInfo

func provideCallKitRemoteInfo(callerInfo: CallerInfo) -> CallKitRemoteInfo
{
    let callKitRemoteInfo = CallKitRemoteInfo()
    callKitRemoteInfo.displayName = "CALL_TO_PHONENUMBER_BY_APP"      
    callKitRemoteInfo.cxHandle = CXHandle(type: .generic, value: "VALUE_TO_CXHANDLE")
    return callKitRemoteInfo
}

配置音频会话

在拨打或接听来电之前,以及在通话暂停后恢复通话之前,将调用“配置音频会话”。

callKitOptions.configureAudioSession = self.configureAudioSession

public func configureAudioSession() -> Error? {
    let audioSession: AVAudioSession = AVAudioSession.sharedInstance()
    var configError: Error?
    do {
        try audioSession.setCategory(.playAndRecord)
    } catch {
        configError = error
    }
    return configError
}

注意:如果 Contoso 已配置音频会话,则不会提供 nil 但会在块中返回 nil 错误

callKitOptions.configureAudioSession = self.configureAudioSession

public func configureAudioSession() -> Error? {
    return nil
}

如果 nilconfigureAudioSession 提供时,SDK 将调用 SDK 中的默认实现。

处理传入推送通知有效负载

当应用收到传入的推送通知负载时,我们需要调用 handlePush 来处理它。 Azure 通信服务通话 SDK 将引发 IncomingCall 事件。

public func handlePushNotification(_ pushPayload: PKPushPayload)
{
    let callNotification = PushNotificationInfo.fromDictionary(pushPayload.dictionaryPayload)
    if let agent = self.callAgent {
        agent.handlePush(notification: callNotification) { (error) in }
    }
}

// Event raised by the SDK
public func callAgent(_ callAgent: CallAgent, didReceiveIncomingCall incomingcall: IncomingCall) {
}

当应用关闭或其他情况时,我们可用于 reportIncomingCall 处理推送通知。

if let agent = self.callAgent {
  /* App is not in a killed state */
  agent.handlePush(notification: callNotification) { (error) in }
} else {
  /* App is in a killed state */
  CallClient.reportIncomingCall(with: callNotification, callKitOptions: callKitOptions) { (error) in
      if (error == nil) {
          DispatchQueue.global().async {
              self.callClient = CallClient()
              let options = CallAgentOptions()
              let callKitOptions = CallKitOptions(with: createProviderConfig())
              callKitOptions.provideRemoteInfo = self.provideCallKitRemoteInfo
              callKitOptions.configureAudioSession = self.configureAudioSession
              options.callKitOptions = callKitOptions
              self.callClient!.createCallAgent(userCredential: userCredential,
                  options: options,
                  completionHandler: { (callAgent, error) in
                  if (error == nil) {
                      self.callAgent = callAgent
                      self.callAgent!.handlePush(notification: callNotification) { (error) in }
                  }
              })
          }
      } else {
          os_log("SDK couldn't handle push notification", log:self.log)
      }
  }
}

CallKit 集成(在应用中)

如果要在应用中集成 CallKit,而不在 SDK 中使用 CallKit 实现,请参阅 此处的快速入门示例。 但要处理的重要事情之一是在正确的时间启动音频。 如下所示

let outgoingAudioOptions = OutgoingAudioOptions()
outgoingAudioOptions.muted = true

let incomingAudioOptions = IncomingAudioOptions()
incomingAudioOptions.muted = true

var copyAcceptCallOptions = AcceptCallOptions()
copyStartCallOptions.outgoingAudioOptions = outgoingAudioOptions
copyStartCallOptions.incomingAudioOptions = incomingAudioOptions

callAgent.startCall(participants: participants,
                    options: copyStartCallOptions,
                    completionHandler: completionBlock)

将扬声器和麦克风静音可确保在 CallKit 对 didActivateAudioSession 调用 CXProviderDelegate之前,不会使用物理音频设备。 否则,呼叫可能会被删除,否则音频将不起作用。 didActivateAudioSession 何时应启动音频流。

func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
    Task {
        guard let activeCall = await self.callKitHelper.getActiveCall() else {
            print("No active calls found when activating audio session !!")
            return
        }

        try await startAudio(call: activeCall)
    }
}

func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
    Task {
        guard let activeCall = await self.callKitHelper.getActiveCall() else {
            print("No active calls found when deactivating audio session !!")
            return
        }

        try await stopAudio(call: activeCall)
    }
}

private func stopAudio(call: Call) async throws {
    try await self.callKitHelper.muteCall(callId: call.id, isMuted: true)
    try await call.stopAudio(stream: call.activeOutgoingAudioStream)

    try await call.stopAudio(stream: call.activeIncomingAudioStream)
    try await call.muteIncomingAudio()
}

private func startAudio(call: Call) async throws {
    try await call.startAudio(stream: LocalOutgoingAudioStream())
    try await self.callKitHelper.muteCall(callId: call.id, isMuted: false)

    try await call.startAudio(stream: RemoteIncomingAudioStream())
    try await call.unmuteIncomingAudio()
}
    

在 CallKit 未调用 didActivateAudioSession 的情况下,在停止音频之前,务必要先将传出音频静音。 然后,用户可以手动解除麦克风的静音状态。

注释

在某些情况下,CallKit 不会调用 didActivateAudioSession ,即使应用具有提升的音频权限,在这种情况下,音频将保持静音,直到收到回叫。 UI 必须反映扬声器和麦克风的状态。 呼叫中的远程参与者将看到用户也已将音频静音。 在这种情况下,用户必须手动取消静音。

后续步骤