Compartir a través de


Suscripción a eventos de SDK

Se recomienda suscribirse a eventos del SDK de llamada. Los SDK de Azure Communication Services son dinámicos y contienen propiedades que pueden cambiar con el tiempo. Puede suscribirse a estos eventos para recibir notificaciones con antelación de cualquier cambio. Siga las instrucciones de este artículo para suscribirse a eventos del SDK de Azure Communication Services.

Eventos en el SDK de Azure Communication Calling

En esta sección se describen los eventos y los cambios de propiedad a los que puede suscribirse la aplicación. La suscripción a esos eventos permite que la aplicación se informe sobre el cambio de estado en el SDK de llamada y reaccionar en consecuencia.

El seguimiento de eventos es fundamental porque permite que el estado de la aplicación permanezca sincronizado con el estado del marco de llamadas de Azure Communication Services. El seguimiento de eventos le ayuda a rastrear los cambios sin tener que implementar un mecanismo de consulta en los objetos del SDK.

En esta sección se supone que ha pasado por el Inicio rápido o que ha implementado una aplicación que puede realizar y recibir llamadas. Si no ha completado la guía de inicio, consulte Incorporación de llamadas de voz a la aplicación.

Cada objeto del SDK de llamadas de JavaScript tiene properties y collections. Sus valores cambian a lo largo de la duración del objeto.

Use el on() método para suscribirse a eventos de objeto. Use el método off() para cancelar la suscripción a eventos de objeto.

Propiedades

Puede suscribirse al evento '<property>Changed' para escuchar los cambios de valor en la propiedad.

Ejemplo de suscripción en una propiedad

En este ejemplo, se suscribe a los cambios en el valor de la propiedad isLocalVideoStarted.

call.on('isLocalVideoStartedChanged', () => {
    // At that point the value call.isLocalVideoStarted is updated
    console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
});

Colecciones

Puede suscribirse al \<collection>Updated evento para recibir notificaciones sobre los cambios en una colección de objetos. El \<collection>Updated evento se desencadena cada vez que se agregan o quitan elementos de la colección que está supervisando.

  • La carga del evento '<collection>Updated'tiene una matriz added que contiene los valores que se quitaron de la colección.
  • La carga del evento '<collection>Updated' también tiene una matriz removed que contiene los valores que se quitaron de la colección.

Suscripción de ejemplo en una colección

En este ejemplo, nos suscribimos a los cambios en los valores del objeto Llamada LocalVideoStream.

call.on('localVideoStreamsUpdated', updateEvent => {
    updateEvent.added.forEach(async (localVideoStream) => {
        // Contains an array of LocalVideoStream that were added to the call
        // Add a preview and start any processing if needed
        handleAddedLocalVideoStream(localVideoStream )
    });
    updateEvent.removed.forEach(localVideoStream => {
        // Contains an array of LocalVideoStream that were removed from the call
        // Remove the preview and stop any processing if needed
        handleRemovedLocalVideoStream(localVideoStream ) 
    });
});

Eventos en el objeto CallAgent

Nombre del evento: incomingCall

El incomingCall evento se desencadena cuando el cliente recibe una llamada entrante.

¿Cómo reacciona la aplicación al evento?

La aplicación debe notificar al destinatario de la llamada entrante. La solicitud de notificación debe permitir que el destinatario acepte o rechace la llamada.

Código de ejemplo:

callClient.on('incomingCall', (async (incomingCallEvent) => {
    try {
        // Store a reference to the call object
        incomingCall = incomingCallEvent.incomingCall; 
        // Update your UI to allow
        acceptCallButton.disabled = false; 
        callButton.disabled = true;
    } catch (error) {
        console.error(error);
    }
});

Nombre del evento: callsUpdated

El evento callsUpdated actualizado se desencadena cuando se quita o se agrega una llamada al agente de llamada. Este evento se produce cuando el usuario realiza, recibe o finaliza una llamada.

¿Cómo reacciona la aplicación al evento?

La aplicación debe actualizar su interfaz de usuario en función del número de llamadas activas para la instancia de CallAgent.

Nombre del evento: connectionStateChanged

El evento connectionStateChanged se desencadena cuando se actualiza el estado de señalización del CallAgent.

¿Cómo reacciona la aplicación al evento?

La aplicación debe actualizar su interfaz de usuario en función del nuevo estado. Los posibles valores de estado de conexión son Connected y Disconnected.

Código de ejemplo:

callClient.on('connectionStateChanged', (async (connectionStateChangedEvent) => {
    if (connectionStateChangedEvent.newState === "Connected") {
        enableCallControls() // Enable all UI element that allow user to make a call
    }

    if (connectionStateChangedEvent.newState === 'Disconnected') {
        if (typeof connectionStateChangedEvent.reason !== 'undefined') {
            alert(`Disconnected reason: ${connectionStateChangedEvent.reason}`)
        } 
        disableCallControls() // Disable all the UI element that allows the user to make a call
    }
});

Eventos en el objeto Call

Nombre del evento: stateChanged

El stateChanged evento se desencadena cuando cambia el estado de la llamada. Por ejemplo, cuando una llamada va de connected a disconnected.

¿Cómo reacciona la aplicación al evento?

La aplicación debe actualizar su interfaz de usuario en consecuencia. Deshabilitar o habilitar los botones adecuados y otros elementos de la interfaz de usuario en función del nuevo estado de llamada.

Código de ejemplo:

call.on('stateChanged', (async (connectionStateChangedEvent) => {
  if(call.state === 'Connected') {
      connectedLabel.hidden = false;
      acceptCallButton.disabled = true;
      startCallButton.disabled = true;
      startVideoButton.disabled = false;
      stopVideoButton.disabled = false
  } else if (call.state === 'Disconnected') {
      connectedLabel.hidden = true;
      startCallButton.disabled = false;
      console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
  }
});

Evento: idChanged

El idChanged evento se desencadena cuando cambia el identificador de una llamada. El id. de una llamada cambia cuando la llamada pasa del estado connecting al connected. Una vez conectada la llamada, el identificador de la llamada sigue siendo idéntico.

¿Cómo reacciona la aplicación al evento?

La aplicación puede guardar el nuevo identificador de llamada o recuperarlo del objeto de llamada más adelante cuando sea necesario.

Código de ejemplo:

let callId = "";
call.on('idChanged', (async (callIdChangedEvent) => {
  callId = call.id; // You can log it as the call ID is useful for debugging call issues
});

Evento: isMutedChanged

El isMutedChanged evento se desencadena cuando el audio local se silencia o se desactiva.

¿Cómo reacciona la aplicación al evento?

La aplicación debe actualizar el botón silenciar o desactivar al estado adecuado.

Código de ejemplo:

call.on('isMutedChanged', (async (isMutedChangedEvent) => {
    microphoneButton.disabled = call.isMuted;       
});

Evento: isScreenSharingOnChanged

El isScreenSharingOnChanged evento se desencadena cuando el uso compartido de pantalla para el usuario local está habilitado o deshabilitado.

¿Cómo reacciona la aplicación al evento?

La aplicación debe mostrar una vista previa o una advertencia al usuario si el uso compartido de pantalla está activado.

Si el uso compartido de pantalla está desactivado, la aplicación debe quitar la vista previa y la advertencia.

Código de ejemplo:

call.on('isScreenSharingOnChanged', () => {
  if (!this.call.isScreenSharing) {
      displayStartScreenSharingButton();
      hideScreenSharingWarning()
      removeScreenSharingPreview();    
  } else {
      displayScreenSharingWarning()
      displayStopScreenSharingButton();
      renderScreenSharingPreview(); 
  }
});

Evento: isLocalVideoStartedChanged

El isLocalVideoStartedChanged evento se activa cuando el usuario habilita o deshabilita su vídeo local.

¿Cómo reacciona la aplicación al evento?

La aplicación debe mostrar una vista previa del vídeo local y habilitar o deshabilitar el botón de activación de la cámara.

Código de ejemplo:

call.on('isLocalVideoStartedChanged', () => {
    showDisableCameraButton(call.isLocalVideoStarted);
});

Evento: remoteParticipantsUpdated

La aplicación debe suscribirse a eventos para cada evento agregado RemoteParticipants y cancelar la suscripción de eventos para los participantes que abandonan la llamada.

¿Cómo reacciona la aplicación al evento?

La aplicación debe mostrar una vista previa del vídeo local y habilitar o deshabilitar el botón de activación de la cámara.

Código de ejemplo:

call.on('remoteParticipantsUpdated', (remoteParticipantsUpdatedEvent) => {
    remoteParticipantsUpdatedEvent.added.forEach(participant => {
        // handleParticipant should
        //   - subscribe to the remote participants events 
        //   - update the UI 
        handleParticipant(participant);
    });
    
    remoteParticipantsUpdatedEvent.removed.forEach(participant => {
        // removeParticipant should
        //   - unsubscribe from the remote participants events 
        //   - update the UI  
        removeParticipant(participant);
    });
});

Evento: localVideoStreamsUpdated

El evento localVideoStreamsUpdated se desencadena cuando cambia la lista de transmisión de vídeo local. Estos cambios se producen cuando el usuario inicia o quita una transmisión de vídeo.

¿Cómo reacciona la aplicación al evento?

Su aplicación debe mostrar vistas previas para cada uno de los elementos añadidos LocalVideoStream. La aplicación debe eliminar la vista previa y detener el procesamiento por cada LocalVideoStream eliminado.

Código de ejemplo:

call.on('localVideoStreamsUpdated', (localVideoStreamUpdatedEvent) => {
    localVideoStreamUpdatedEvent.added.forEach(addedLocalVideoStream => { 
        // Add a preview and start any processing if needed
        handleAddedLocalVideoStream(addedLocalVideoStream) 
    });

    localVideoStreamUpdatedEvent.removed.forEach(removedLocalVideoStream => {
         // Remove the preview and stop any processing if needed
        this.handleRemovedLocalVideoStream(removedLocalVideoStream) 
    });
});

Evento: remoteAudioStreamsUpdated

El evento remoteAudioStreamsUpdated se activa cuando cambia la lista de secuencias de audio remotas. Estos cambios se producen cuando los participantes remotos agregan o quitan transmisiones de audio en la llamada.

¿Cómo reacciona la aplicación al evento?

Si se está procesando un flujo y ahora se ha eliminado, se debe detener el procesamiento. Por otra parte, si se agrega una transmisión, la recepción del evento es un buen lugar para iniciar el procesamiento de la nueva transmisión de audio.

Evento: totalParticipantCountChanged

totalParticipantCountChanged se desencadena cuando el número de totalParticipant ha cambiado en una llamada.

¿Cómo reacciona la aplicación al evento?

Si la aplicación muestra un contador de participantes, la aplicación puede actualizar su contador de participantes cuando se recibe el evento.

Código de ejemplo:

call.on('totalParticipantCountChanged', () => {
    participantCounterElement.innerText = call.totalParticipantCount;
});

Evento: roleChanged

El participante roleChanged se desencadena cuando el rol de localParticipant cambia en la llamada. Un ejemplo sería cuando el participante local se convierte en moderador ACSCallParticipantRolePresenter en una llamada.

¿Cómo reacciona la aplicación al evento?

La aplicación debe habilitar o deshabilitar el botón en función del nuevo rol de usuario.

Código de ejemplo:

call.on('roleChanged', () => {
    this.roleElement = call.role;
});

Evento: mutedByOthers

El mutedByOthers evento se produce cuando el participante local silencia a otros participantes en la llamada.

¿Cómo reacciona la aplicación al evento?

La aplicación debe mostrar un mensaje al usuario que notifique que está silenciado.

Código de ejemplo:

call.on('mutedByOthers', () => {
    messageBanner.innerText = "You have been muted by other participant in this call";
});

Evento: callerInfoChanged

El evento callerInfoChanged se produce cuando se actualizó la información del autor de la llamada. Esto ocurre cuando una persona que llama cambia su nombre visible.

¿Cómo reacciona la aplicación al evento? La aplicación puede actualizar la información del autor de la llamada.

Código de ejemplo:

call.on('callerInfoChanged', () => {
    showCallerInfo(call.callerInfo)
});

Evento: transferorInfoChanged

El evento transferorInfoChanged se produce cuando se actualizó la información del transferente. Esto ocurre cuando un transferente cambia su nombre para mostrar.

¿Cómo reacciona la aplicación al evento? La aplicación puede actualizar la información del transferor.

Código de ejemplo:

call.on('transferorInfoChanged', () => {
    showTransferorInfo(call.transferorInfo)
});

Eventos en el objeto RemoteParticipant

Evento: roleChanged

El evento roleChanged se desencadena cuando cambia el rol RemoteParticipant en la llamada. Un ejemplo sería cuando RemoteParticipant se convierte en moderador ACSCallParticipantRolePresenter en una llamada.

¿Cómo reacciona la aplicación al evento?

La aplicación debe actualizar su interfaz de usuario en función del RemoteParticipant nuevo rol.

Código de ejemplo:

remoteParticipant.on('roleChanged', () => {
    updateRole(remoteParticipant);
});

Evento: isMutedChanged

El evento isMutedChanged se desencadena cuando uno de los RemoteParticipant silencia o reactiva el audio de su micrófono.

¿Cómo reacciona la aplicación al evento?

La aplicación puede mostrar un icono cerca de la vista que muestra al participante.

Código de ejemplo:

remoteParticipant.on('isMutedChanged', () => {
    updateMuteStatus(remoteParticipant); // Update the UI based on the mute state of the participant
});

Evento: displayNameChanged

displayNameChanged cuando se actualiza el nombre del RemoteParticipant.

¿Cómo reacciona la aplicación al evento?

La aplicación debe actualizar el nombre del participante si se muestra en la interfaz de usuario.

Código de ejemplo:

remoteParticipant.on('displayNameChanged', () => {
    remoteParticipant.nameLabel.innerText = remoteParticipant.displayName;
});
remoteParticipant.on('displayNameChanged', (args: {newValue?: string, oldValue?: string, reason?: DisplayNameChangedReason}) => {
    remoteParticipant.nameLabel.innerText = remoteParticipant.displayName;
    console.log(`Display name changed from ${oldValue} to ${newValue} due to ${reason}`);
});

Evento: isSpeakingChanged

isSpeakingChanged cuando cambia el hablante dominante en una llamada.

¿Cómo reacciona la aplicación al evento?

La interfaz de usuario de su aplicación debería dar prioridad a mostrar el RemoteParticipant que se convirtió en hablante dominante.

Código de ejemplo:

remoteParticipant.on('isSpeakingChanged', () => {
    showAsRemoteSpeaker(remoteParticipant) // Display a speaking icon near the participant
});

Evento: videoStreamsUpdated

videoStreamsUpdated cuando un participante remoto agrega o quita un VideoStream en la llamada.

¿Cómo reacciona la aplicación al evento?

Si la aplicación estaba procesando una secuencia que se quita, la aplicación debe detener el procesamiento. Cuando se agrega una nueva secuencia, se recomienda que la aplicación empiece a representarla o procesarla.

Código de ejemplo:

remoteParticipant.on('videoStreamsUpdated', (videoStreamsUpdatedEvent) => {

     videoStreamsUpdatedEvent.added.forEach(addedRemoteVideoStream => { 
       // Remove a renderer and start processing the stream if any processing is needed
        handleAddedRemoteVideoStream(addedRemoteVideoStream) 
    });

    videoStreamsUpdatedEvent.removed.forEach(removedRemoteVideoStream => {
        // Remove the renderer and stop processing the stream if any processing is ongoing
        this.handleRemovedRemoteVideoStream(removedRemoteVideoStream) 
    });
});

Evento en el objeto AudioEffectsFeature

Evento: effectsStarted

Este evento se produce cuando el efecto de audio seleccionado se aplica a la transmisión de audio. Por ejemplo, cuando alguien activa la supresión de ruido, se activará la effectsStarted.

¿Cómo reacciona la aplicación al evento?

La aplicación puede mostrar o habilitar un botón que permita al usuario deshabilitar el efecto de audio.

Código de ejemplo:

audioEffectsFeature.on('effectsStarted', (effects) => {
    stopEffectButton.style.visibility = "visible"; 
});

Evento: effectsStopped

Este evento se produce cuando el efecto de audio seleccionado se aplica a la transmisión de audio. Por ejemplo, cuando alguien desactiva la supresión de ruido, se activará effectsStopped.

¿Cómo reacciona la aplicación al evento?

La aplicación puede mostrar o habilitar un botón que permita al usuario habilitar el efecto de audio.

Código de ejemplo:

audioEffectsFeature.on('effectsStopped', (effects) => {
    startEffectButton.style.visibility = "visible"; 
});

Evento: effectsError

Este evento se produce cuando se produce un error mientras se inicia o se aplica un efecto de audio.

¿Cómo reacciona la aplicación al evento?

La aplicación debe mostrar una alerta o un mensaje de error que indica que el efecto de audio no funciona según lo previsto.

Código de ejemplo:

audioEffectsFeature.on('effectsError', (error) => {
    console.log(`Error with the audio effect ${error}`);
    alert(`Error with the audio effect`);
});

Instalación del SDK

Busque el archivo build.gradle de nivel de proyecto y agregue mavenCentral() a la lista de repositorios en buildscript y allprojects:

buildscript {
    repositories {
    ...
        mavenCentral()
    ...
    }
}
allprojects {
    repositories {
    ...
        mavenCentral()
    ...
    }
}

Luego, en el archivo build.gradle de nivel de módulo, agregue las siguientes líneas a la sección dependencies:

dependencies {
    ...
    implementation 'com.azure.android:azure-communication-calling:1.0.0'
    ...
}

Inicialización de los objetos necesarios

Para crear una instancia de CallAgent, debe llamar al método createCallAgent en una instancia de CallClient. Esta llamada devuelve un objeto de instancia de CallAgent de manera asincrónica.

El método createCallAgent toma CommunicationUserCredential como argumento, que encapsula un token de acceso.

Para acceder a DeviceManager, primero debe crear una instancia de callAgent. A continuación, puede usar el método CallClient.getDeviceManager para obtener DeviceManager.

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential).get();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

Para establecer un nombre para mostrar para el autor de la llamada, use este método alternativo:

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgentOptions callAgentOptions = new CallAgentOptions();
callAgentOptions.setDisplayName("Alice Bob");
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential, callAgentOptions).get();

Ahora que instaló Android SDK, puede suscribirse a la mayoría de las propiedades y colecciones que se notificarán cuando cambien los valores.

Propiedades

Para suscribirse a eventos property changed:

// subscribe
PropertyChangedListener callStateChangeListener = new PropertyChangedListener()
{
    @Override
    public void onPropertyChanged(PropertyChangedEvent args)
    {
        Log.d("The call state has changed.");
    }
}
call.addOnStateChangedListener(callStateChangeListener);

//unsubscribe
call.removeOnStateChangedListener(callStateChangeListener);

Cuando use clientes de escucha de eventos definidos dentro de la misma clase, enlace el cliente de escucha a una variable. Pase la variable como argumento para agregar y quitar métodos del cliente de escucha.

Si intenta pasar el cliente de escucha directamente como un argumento, perderá la referencia a ese cliente de escucha. Java crea nuevas instancias de estos clientes de escucha y no hace referencia a las creadas anteriormente. Se seguirán apagando correctamente, pero no se pueden quitar porque ya no tendrá una referencia a ellos.

Colecciones

Para suscribirse a eventos collection updated:

LocalVideoStreamsChangedListener localVideoStreamsChangedListener = new LocalVideoStreamsChangedListener()
{
    @Override
    public void onLocalVideoStreamsUpdated(LocalVideoStreamsEvent localVideoStreamsEventArgs) {
        Log.d(localVideoStreamsEventArgs.getAddedStreams().size());
        Log.d(localVideoStreamsEventArgs.getRemovedStreams().size());
    }
}
call.addOnLocalVideoStreamsChangedListener(localVideoStreamsChangedListener);
// To unsubscribe
call.removeOnLocalVideoStreamsChangedListener(localVideoStreamsChangedListener);

Configuración del sistema

Siga estos pasos para configurar el sistema.

Creación del proyecto de Xcode

En Xcode, cree un nuevo proyecto de iOS y seleccione la plantilla Aplicación de una vista. En este artículo se usa el marco SwiftUI, por lo que debe establecer el Lenguaje en Swift y la Interfaz en SwiftUI.

No va a crear pruebas en este artículo. Puede desactivar la casilla Incluir pruebas.

Captura de pantalla que muestra la ventana para crear un proyecto en Xcode.

Instalación del paquete y las dependencias mediante CocoaPods

  1. Cree un Podfile para la aplicación, como en este ejemplo:

    platform :ios, '13.0'
    use_frameworks!
    target 'AzureCommunicationCallingSample' do
        pod 'AzureCommunicationCalling', '~> 1.0.0'
    end
    
  2. Ejecute pod install.

  3. Abra .xcworkspace mediante Xcode.

Solicitud de acceso al micrófono

Para acceder al micrófono del dispositivo, debe actualizar la lista de propiedades de información de la aplicación mediante NSMicrophoneUsageDescription. Establezca el valor asociado en una cadena que se incluye en el cuadro de diálogo empleado por el sistema para solicitar acceso al usuario.

Haga clic con el botón derecho en la entrada Info.plist del árbol del proyecto y seleccione Abrir como>Código fuente. Agregue las líneas siguientes a la sección <dict> de nivel superior y guarde el archivo.

<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>

Instalación del marco de la aplicación

Abra el archivo ContentView.swift del proyecto. Agregue una declaración import a la parte superior del archivo para importar la biblioteca AzureCommunicationCalling. Además, importe AVFoundation. Lo necesitará para las solicitudes de permiso de audio en el código.

import AzureCommunicationCalling
import AVFoundation

Inicialización de CallAgent

Para crear una instancia de CallAgent a partir de CallClient, debe usar el método callClient.createCallAgent, que devuelve de manera asincrónica un objeto CallAgent después de que se inicializa.

Para crear un cliente de llamada, pase un objeto CommunicationTokenCredential:

import AzureCommunication

let tokenString = "token_string"
var userCredential: CommunicationTokenCredential?
do {
    let options = CommunicationTokenRefreshOptions(initialToken: token, refreshProactively: true, tokenRefresher: self.fetchTokenSync)
    userCredential = try CommunicationTokenCredential(withOptions: options)
} catch {
    updates("Couldn't created Credential object", false)
    initializationDispatchGroup!.leave()
    return
}

// tokenProvider needs to be implemented by Contoso, which fetches a new token
public func fetchTokenSync(then onCompletion: TokenRefreshOnCompletion) {
    let newToken = self.tokenProvider!.fetchNewToken()
    onCompletion(newToken, nil)
}

Pase el objeto CommunicationTokenCredential que ha creado a CallClient y establezca el nombre para mostrar:

self.callClient = CallClient()
let callAgentOptions = CallAgentOptions()
options.displayName = " iOS Azure Communication Services User"

self.callClient!.createCallAgent(userCredential: userCredential!,
    options: callAgentOptions) { (callAgent, error) in
        if error == nil {
            print("Create agent succeeded")
            self.callAgent = callAgent
        } else {
            print("Create agent failed")
        }
})

Ahora que instaló el SDK de iOS, puede suscribirse a la mayoría de las propiedades y colecciones que se notificarán cuando cambien los valores.

Propiedades

Para suscribirse a eventos property changed, use el código siguiente.

call.delegate = self
// Get the property of the call state by getting on the call's state member
public func call(_ call: Call, didChangeState args: PropertyChangedEventArgs) {
{
    print("Callback from SDK when the call state changes, current state: " + call.state.rawValue)
}

// to unsubscribe
self.call.delegate = nil

Colecciones

Para suscribirse a eventos collection updated, use el código siguiente.

call.delegate = self
// Collection contains the streams that were added or removed only
public func call(_ call: Call, didUpdateLocalVideoStreams args: LocalVideoStreamsUpdatedEventArgs) {
{
    print(args.addedStreams.count)
    print(args.removedStreams.count)
}
// to unsubscribe
self.call.delegate = nil

Pasos siguientes