你当前正在访问 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
})

指定传出呼叫的呼叫收件人信息

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

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 调用 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, didRecieveIncomingCall 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 必须反映扬声器和麦克风的状态。 呼叫中的远程参与者/秒将看到用户也已静音音频。 在这种情况下,用户必须手动取消静音。

后续步骤