與 CallKit 整合

在本文件中,我們將逐步解說如何整合 CallKit 與您的 iOS 應用程式。

必要條件

CallKit 整合 (在 SDK 内)

Azure 通訊服務 iOS SDK 中的 CallKit 整合會為我們處理與 CallKit 的互動。 若要執行任何通話作業,例如靜音/取消靜音、保留/繼續,我們只需要在 Azure 通訊服務 SDK 上通話 API。

使用 CallKitOptions 初始化通話代理程式

透過 CallKitOptions 的已設定執行個體,我們可以使用處理 CallKit 來建立 CallAgent

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
}

如果 nil 提供給 configureAudioSession 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 通話 CXProviderDelegate 上的 didActivateAudioSession之前,不會使用實體音訊裝置。 否則通話可能會中斷,或音訊將無法運作。 當 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 必須反映喇叭和麥克風的狀態。 通話中的遠端參與者也會看到使用者已靜音音訊。 在這些情況下,使用者必須手動取消靜音。

下一步