Compartir a través de


Integración con CallKit

En este documento, veremos cómo integrar CallKit con la aplicación iOS.

Prerrequisitos

Integración de CallKit (dentro del SDK)

La integración de CallKit en el SDK de iOS de Azure Communication Services controla la interacción con CallKit para nosotros. Para realizar operaciones de llamada como silenciar o desactivar, mantener o reanudar, solo es necesario llamar a la API en el SDK de Azure Communication Services.

Inicialización del agente de llamadas con CallKitOptions

Con la instancia configurada de CallKitOptions, podemos crear el CallAgent con el manejo de 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
})

Especificar la información del destinatario de la llamada para las llamadas salientes

En primer lugar, es necesario crear una instancia de StartCallOptions() para las llamadas salientes o JoinCallOptions() para la llamada de grupo:

let options = StartCallOptions()

or

let options = JoinCallOptions()

A continuación, cree una instancia de CallKitRemoteInfo

options.callKitRemoteInfo = CallKitRemoteInfo()
  1. Asigne un valor para callKitRemoteInfo.displayNameForCallKit a fin de personalizar el nombre para mostrar de los destinatarios de la llamada y configure el valor CXHandle. El valor especificado en displayNameForCallKit es exactamente tal como aparece en el último registro de llamadas marcadas. en el registro de la última llamada realizada.
options.callKitRemoteInfo.displayNameForCallKit = "DISPLAY_NAME"
  1. Asignar el valor cxHandle es lo que recibe la aplicación cuando el usuario vuelve a llamar en ese contacto
options.callKitRemoteInfo.cxHandle = CXHandle(type: .generic, value: "VALUE_TO_CXHANDLE")

Especificar la información del destinatario de la llamada para las llamadas entrantes

En primer lugar, es necesario crear una instancia de CallKitOptions:

let callKitOptions = CallKitOptions(with: createProviderConfig())

Configure las propiedades de la instancia CallKitOptions.

El bloque que se pasa a la variable provideRemoteInfo será llamado por el SDK cuando recibamos una llamada entrante y necesitemos obtener un nombre para mostrar del llamante entrante, el cual deberemos pasar al 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
}

Configuración de la sesión de audio

Se configurará la sesión de audio antes de realizar o aceptar una llamada entrante y antes de reanudar la llamada después de que se haya puesto en espera.

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
}

NOTA: En los casos en los que Contoso ya ha configurado sesiones de audio, NO proporcione nil sino que devuelva el error nil en el bloque.

callKitOptions.configureAudioSession = self.configureAudioSession

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

si se proporciona nil para configureAudioSession, entonces el SDK llama a la implementación predeterminada en el SDK.

Gestionar la carga útil de notificaciones push entrantes

Cuando la aplicación recibe la carga de notificaciones push entrantes, es necesario llamar a handlePush para procesarla. El SDK de llamadas de Azure Communication Services generará el evento 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) {
}

Podemos usar reportIncomingCall para controlar las notificaciones push cuando la aplicación está cerrada o de lo contrario.

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)
      }
  }
}

Integración de CallKit (dentro de la aplicación)

Si desea integrar CallKit en la aplicación y no usar la implementación de CallKit en el SDK, consulte el ejemplo de inicio rápido aquí. Pero una de las cosas importantes que se deben cuidar es iniciar el audio en el momento adecuado. Como se indica a continuación

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)

Silenciar el altavoz y el micrófono garantiza que los dispositivos de audio físicos no se usen hasta que CallKit invoque a didActivateAudioSession en CXProviderDelegate. De lo contrario, la llamada puede quitarse o el audio no funcionará. Cuando didActivateAudioSession es cuando se deben iniciar las secuencias de audio.

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()
}
    

Es importante silenciar también el audio saliente antes de detener el audio en casos en los que CallKit no invoca didActivateAudioSession. A continuación, el usuario puede desactivar manualmente el micrófono.

Nota:

En algunos casos, CallKit no llama a didActivateAudioSession aunque la aplicación tenga permisos de audio elevados, en ese caso el audio permanecerá silenciado hasta que se reciba la llamada de retorno. Y la interfaz de usuario tiene que reflejar el estado del altavoz y el micrófono. Los participantes remotos de la llamada verán que el usuario también ha silenciado el audio. El usuario tendrá que reactivar el audio manualmente en esos casos.

Pasos siguientes