Condividi tramite


Sottoscrivere eventi dell'SDK

Servizi di comunicazione di Azure SDK sono dinamici e contengono molte proprietà. Quando cambiano, gli sviluppatori potrebbero voler sapere quando e cosa cambiano di più. Ecco come!

Eventi in Azure Communication Calling SDK

Questa guida descrive i vari eventi o le modifiche apportate alle proprietà che l'app può sottoscrivere. La sottoscrizione a tali eventi consente all'app di essere informati sulla modifica dello stato nell'SDK chiamante e reagire di conseguenza.

Il rilevamento degli eventi è fondamentale perché consente allo stato dell'applicazione di rimanere sincronizzati con lo stato del framework ACSCalling, tutto senza che sia necessario implementare un meccanismo pull sugli oggetti SDK.

Questa guida presuppone che sia stata eseguita la guida introduttiva o che sia stata implementata un'applicazione in grado di effettuare e ricevere chiamate. Se la guida introduttiva non è stata completata, vedere la guida introduttiva.

Ogni oggetto nell'SDK chiamante JavaScript ha properties e collections. I valori cambiano per tutta la durata dell'oggetto. Utilizzare il on() metodo per sottoscrivere gli eventi degli oggetti e usare il metodo per annullare la off() sottoscrizione agli eventi degli oggetti.

Proprietà

È possibile sottoscrivere l'evento '<property>Changed' per ascoltare le modifiche al valore nella proprietà .

Esempio di sottoscrizione in una proprietà

In questo esempio si sottoscrivono le modifiche apportate al valore della isLocalVideoStarted proprietà .

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

Raccolte

È possibile sottoscrivere l'evento '<collection Updated' per ricevere notifiche sulle modifiche in una raccolta>di oggetti. L'evento '<collection>Updated' viene attivato ogni volta che gli elementi vengono aggiunti o rimossi dalla raccolta monitorata.

  • Il '<collection>Updated' payload dell'evento ha una added matrice che contiene valori aggiunti alla raccolta.
  • Il '<collection>Updated' payload dell'evento include anche una removed matrice che contiene valori rimossi dalla raccolta.

Sottoscrizione di esempio in una raccolta

In questo esempio si sottoscrivono le modifiche apportate ai valori dell'oggetto LocalVideoStreamCall .

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

Eventi nell'oggetto CallAgent

Nome evento: incomingCall

L'evento incomingCall viene generato quando il client riceve una chiamata in arrivo.

Come deve reagire l'applicazione all'evento?

L'applicazione deve notificare all'utente la chiamata in arrivo. La richiesta di notifica deve proporre all'utente di accettare o rifiutare la chiamata.

Esempio di codice:

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

Nome evento: callsUpdated

L'evento callsUpdated aggiornato viene generato quando una chiamata viene rimossa o aggiunta all'agente di chiamata. Questo evento si verifica quando l'utente effettua, riceve o termina una chiamata.

Come deve reagire l'applicazione all'evento? L'applicazione deve aggiornare l'interfaccia utente in base al numero di chiamate attive per l'istanza di CallAgent.

Nome evento: connectionStateChanged

Evento connectionStateChanged generato quando viene aggiornato lo stato di segnalazione di CallAgent .

Come deve reagire l'applicazione all'evento?

L'applicazione deve aggiornare l'interfaccia utente in base al nuovo stato. I possibili valori dello stato di connessione sono Connected e Disconnected

Esempio di codice:

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

Eventi nell'oggetto Call

Nome evento: stateChanged

L'evento stateChanged viene generato quando lo stato della chiamata cambia. Ad esempio, quando una chiamata passa da connected a disconnected.

Come deve reagire l'applicazione all'evento?

L'applicazione deve aggiornare di conseguenza l'interfaccia utente. Disabilitazione o abilitazione dei pulsanti appropriati e di altri elementi dell'interfaccia utente in base al nuovo stato della chiamata.

Esempio di codice:

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

L'evento idChanged viene generato quando cambia l'ID di una chiamata. L'ID di una chiamata cambia quando la chiamata passa dallo connecting stato a connected. Una volta connessa la chiamata, l'ID della chiamata rimane identico.

In che modo l'applicazione può reagire all'evento?

L'applicazione deve salvare il nuovo ID chiamata, ma può anche essere recuperato dall'oggetto chiamata in un secondo momento, se necessario.

Esempio di codice:

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

L'evento isMutedChanged viene generato quando l'audio locale viene disattivato o non modificato.

In che modo l'applicazione può reagire all'evento?

L'applicazione deve aggiornare il pulsante mute/unmute allo stato corretto.

Esempio di codice:

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

Evento: isScreenSharingOnChanged

L'evento isScreenSharingOnChanged viene generato quando la condivisione dello schermo per l'utente locale è abilitata o disabilitata.

In che modo l'applicazione può reagire all'evento?

L'applicazione dovrebbe visualizzare un'anteprima e/o un avviso all'utente se la condivisione dello schermo è diventata attiva. Se la condivisione dello schermo è stata disattivata, l'applicazione dovrebbe rimuovere l'anteprima e l'avviso.

Esempio di codice:

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

Evento: isLocalVideoStartedChanged

L'evento isLocalVideoStartedChanged viene generato quando l'utente ha abilitato il video locale disabilitato.

In che modo l'applicazione può reagire all'evento?

L'applicazione dovrebbe visualizzare un'anteprima del video locale e abilitare o disabilitare il pulsante di attivazione della fotocamera.

Esempio di codice:

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

Evento: remoteParticipantsUpdated

L'applicazione deve sottoscrivere l'evento per ogni evento aggiunto RemoteParticipants e annullare la sottoscrizione degli eventi per i partecipanti che hanno lasciato la chiamata.

In che modo l'applicazione può reagire all'evento? L'applicazione dovrebbe visualizzare un'anteprima del video locale e abilitare o disabilitare il pulsante di attivazione della fotocamera.

Esempio di codice:

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
        //   - unsubcribe from the remote participants events 
        //   - update the UI  
        removeParticipant(participant);
    });
});

Evento: localVideoStreamsUpdated

L'evento localVideoStreamsUpdated viene generato quando cambia l'elenco del flusso video locale. Queste modifiche si verificano all'avvio o alla rimozione di un flusso video.

In che modo l'applicazione può reagire all'evento?

L'applicazione dovrebbe visualizzare le anteprime per ognuna delle LocalVideoStream aggiunte. L'applicazione deve rimuovere l'anteprima e arrestare l'elaborazione per ogni LocalVideoStream rimosso.

Esempio di codice:

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

L'evento remoteAudioStreamsUpdated viene generato quando cambia l'elenco di flussi audio remoti. Queste modifiche si verificano quando i partecipanti remoti aggiungono o rimuovono flussi audio alla chiamata.

In che modo l'applicazione può reagire all'evento?

Se un flusso è in corso di elaborazione e ora viene rimosso, l'elaborazione deve essere arrestata. D'altra parte, se viene aggiunto un flusso, la ricezione dell'evento è un buon punto di partenza per avviare l'elaborazione del nuovo flusso audio.

Evento: totalParticipantCountChanged

Viene totalParticipantCountChanged generato quando il numero di totalParticipant è cambiato in una chiamata.

In che modo l'applicazione può reagire all'evento?

Se l'applicazione visualizza un contatore dei partecipanti, l'applicazione può aggiornare il contatore dei partecipanti al momento della ricezione dell'evento.

Esempio di codice:

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

Evento: roleChanged

Il roleChanged partecipante viene attivato quando i ruoli localParticipant cambiano nella chiamata. Un esempio è quando il partecipante locale diventa relatore ACSCallParticipantRolePresenter in una chiamata.

In che modo l'applicazione può reagire all'evento? L'applicazione deve abilitare o disabilitare il pulsante in base al nuovo ruolo dell'utente.

Esempio di codice:

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

Evento: mutedByOthers

L'evento mutedByOthers si verifica quando altri partecipanti alla chiamata vengono disattivati dal partecipante locale.

In che modo l'applicazione può reagire all'evento? L'applicazione dovrebbe visualizzare un messaggio all'utente che informa che è stato disattivato.

Esempio di codice:

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

Evento: callerInfoChanged

L'evento callerInfoChanged si verifica quando sono state aggiornate le informazioni sul chiamante.

In che modo l'applicazione può reagire all'evento? L'applicazione può aggiornare le informazioni del chiamante.

Esempio di codice:

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

Evento: transferorInfoChanged

L'evento transferorInfoChanged si verifica quando sono state aggiornate le informazioni del transferor.

In che modo l'applicazione può reagire all'evento? L'applicazione può aggiornare le informazioni del transferor.

Esempio di codice:

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

Eventi nell'oggetto RemoteParticipant

Evento: roleChanged

L'evento roleChanged viene generato quando il RemotePartipant ruolo cambia nella chiamata. Un esempio è quando remoteParticipant diventa relatore ACSCallParticipantRolePresenter in una chiamata.

In che modo l'applicazione può reagire all'evento? L'applicazione deve aggiornare l'interfaccia utente in base al RemoteParticipant nuovo ruolo.

Esempio di codice:

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

Evento: isMutedChanged

L'evento isMutedChanged viene generato quando uno dei disattiva o annulla l'audio RemoteParticipant del microfono.

In che modo l'applicazione può reagire all'evento?

L'applicazione può visualizzare un'icona vicino alla visualizzazione che visualizza il partecipante.

Esempio di codice:

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

Evento: displayNameChanged

Oggetto displayNameChanged quando viene aggiornato il nome dell'oggetto RemoteParticipant .

In che modo l'applicazione può reagire all'evento?

L'applicazione deve aggiornare il nome del partecipante se viene visualizzato nell'interfaccia utente.

Esempio di codice:

remoteParticipant.on('displayNameChanged', () => {
    remoteParticipant.nameLabel.innerText = remoteParticipant.displayName;
});

Evento: isSpeakingChanged

Oggetto isSpeakingChanged quando cambia l'altoparlante dominante in una chiamata.

In che modo l'applicazione può reagire all'evento?

L'interfaccia utente dell'applicazione deve dare priorità alla visualizzazione dell'altoparlante RemotePartipant dominante.

Esempio di codice:

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

Evento: videoStreamsUpdated

Oggetto videoStreamsUpdated quando un partecipante remoto aggiunge o rimuove un oggetto VideoStream da/verso la chiamata.

In che modo l'applicazione può reagire all'evento?

Se l'applicazione stava elaborando un flusso rimosso. L'applicazione deve arrestare l'elaborazione. Quando viene aggiunto un nuovo flusso, l'applicazione potrebbe voler eseguire il rendering o elaborarlo.

Esempio di codice:

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 nell'oggetto AudioEffectsFeature

Evento: effectsStarted

Questo evento si verifica quando l'effetto audio selezionato viene applicato al flusso audio. Ad esempio, quando qualcuno attiva l'eliminazione del rumore, effectsStarted verrà licenziato.

In che modo l'applicazione può reagire all'evento?

L'applicazione può visualizzare o abilitare un pulsante che consente all'utente di disabilitare l'effetto audio.

Esempio di codice:

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

Evento: effectsStopped

Questo evento si verifica quando l'effetto audio selezionato viene applicato al flusso audio. Ad esempio, quando qualcuno disattiva l'eliminazione del rumore, effectsStopped verrà licenziato.

In che modo l'applicazione può reagire all'evento?

L'applicazione può visualizzare o abilitare un pulsante che consente all'utente di abilitare l'effetto audio.

Esempio di codice:

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

Evento: effectsError

Questo evento si verifica quando si verifica un errore durante l'avvio o l'applicazione di un effetto audio.

In che modo l'applicazione può reagire all'evento?

L'applicazione dovrebbe visualizzare un avviso o un messaggio di errore che indica che l'effetto audio non funziona come previsto.

Esempio di codice:

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

Installazione dell'SDK

Individuare il file a livello build.gradle di progetto e aggiungere mavenCentral() all'elenco dei repository in buildscript e allprojects:

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

Quindi, nel file a livello build.gradle di modulo aggiungere le righe seguenti alla dependencies sezione :

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

Inizializzare gli oggetti necessari

Per creare un'istanza CallAgent, è necessario chiamare il metodo createCallAgent in un'istanza CallClient. Questa chiamata restituisce in modo asincrono un oggetto istanza CallAgent.

Il metodo createCallAgent accetta CommunicationUserCredential come argomento, che incapsula un token di accesso.

Per accedere a DeviceManager, è prima necessario creare un'istanza callAgent. Quindi è possibile usare il metodo CallClient.getDeviceManager per ottenere 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();

Per impostare nome visualizzato per il chiamante, usare questo metodo 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();

Con Android SDK è possibile sottoscrivere la maggior parte delle proprietà e delle raccolte per ricevere una notifica quando i valori cambiano.

Proprietà

Per sottoscrivere property changed gli eventi:

// 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);

Quando si usano listener di eventi definiti all'interno della stessa classe, associare il listener a una variabile. Passare la variabile come argomento per aggiungere e rimuovere i metodi del listener.

Se si tenta di passare il listener direttamente come argomento, si perderà il riferimento a tale listener. Java crea nuove istanze di questi listener e non fa riferimento a quelle create in precedenza. Verranno ancora spenti correttamente, ma non possono essere rimossi perché non si avrà più un riferimento a loro.

Raccolte

Per sottoscrivere collection updated gli eventi:

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

Configurare il sistema

Seguire questa procedura per configurare il sistema.

Creare il progetto Xcode

In Xcode creare un nuovo progetto iOS e selezionare il modello Single View Application. Questo articolo usa il framework SwiftUI, quindi è consigliabile impostare Language su Swift e impostare Interface su SwiftUI.

In questo articolo non verranno creati test. È possibile deselezionare la casella di controllo Includi i test.

Screenshot che mostra la finestra per la creazione di un progetto in Xcode.

Installare il pacchetto e le dipendenze usando CocoaPods

  1. Creare un file Podfile per l'applicazione, come questo esempio:

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

  3. Aprire .xcworkspace con Xcode.

Richiedere l'accesso al microfono

Per accedere al microfono del dispositivo, è necessario aggiornare l'elenco delle proprietà informazioni dell'app usando NSMicrophoneUsageDescription. Impostare il valore associato su una stringa inclusa nella finestra di dialogo usata dal sistema per richiedere l'accesso dall'utente.

Fare clic con il pulsante destro del mouse sulla voce info.plist dell'albero del progetto, quindi selezionare Apri come>codice sorgente. Aggiungere le righe seguenti nella sezione di primo livello <dict> e quindi salvare il file.

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

Configurare il framework dell'app

Aprire il file del ContentView.swift progetto. Aggiungere una dichiarazione import all'inizio del file per importare la libreria AzureCommunicationCalling. Inoltre, importare AVFoundation: È necessario per le richieste di autorizzazione audio nel codice.

import AzureCommunicationCalling
import AVFoundation

Inizializzare CallAgent

Per creare un'istanza CallAgent da CallClient, è necessario usare un metodo callClient.createCallAgent che restituisce in modo asincrono un oggetto CallAgent dopo l'inizializzazione.

Per creare un client di chiamata, passare un oggetto 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)
}

Passare l'oggetto CommunicationTokenCredential creato in CallClient e impostare il nome visualizzato:

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

Con iOS SDK è possibile sottoscrivere la maggior parte delle proprietà e delle raccolte per ricevere una notifica quando i valori cambiano.

Proprietà

Per sottoscrivere property changed gli eventi, usare il codice seguente.

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

Raccolte

Per sottoscrivere collection updated gli eventi, usare il codice seguente.

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

Passaggi successivi