Partager via


S’abonner aux événements SDK

Les kits de développement logiciel (SDK) Azure Communication Services sont dynamiques et contiennent beaucoup de propriétés. Lorsque ces dernières changent, en tant que développeur, il peut vous être utile de savoir à quel moment le changement s’est produit et, plus important, à quel niveau. Voici comment faire !

Événements sur le Kit de développement logiciel (SDK) Azure Communication Calling

Ce guide décrit les différents événements ou propriétés auxquels votre application peut s’abonner. L’abonnement à ces événements permet à votre application d’être informée des changements d’état dans le Kit de développement logiciel (SDK) appelant et de réagir en conséquence.

Le suivi des événements est crucial, car il permet à l’état de votre application de rester synchronisé avec l’état de l’infrastructure ACSCalling, sans qu’il soit nécessaire de mettre en œuvre un mécanisme d’extraction sur les objets du Kit de développement logiciel (SDK).

Ce guide part du principe que vous avez suivi le guide de démarrage rapide ou que vous avez implémenté une application capable d’effectuer et de recevoir des appels. Si vous n’avez pas suivi le guide de démarrage, reportez-vous à notre Démarrage rapide.

Chaque objet du Kit de développement logiciel (SDK) d’appel JavaScript a des properties et des collections. Leurs valeurs changent tout au long de la durée de vie de l’objet. Utilisez la méthode on() pour vous abonner aux événements d’objets, et utilisez la méthode off() pour vous désabonner des événements d’objets.

Propriétés

Vous pouvez vous abonner à l’événement '<property>Changed' pour être informé des changements de valeur de la propriété.

Exemple d’abonnement sur une propriété

Dans cet exemple, nous nous abonnons aux changements de la valeur de la propriété isLocalVideoStarted.

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

Collections

Vous pouvez vous abonner à l’événement « <collection>Mise à jour » pour recevoir des notifications sur les modifications apportées à une collection d’objets. L’événement « <collection>Mise à jour » est déclenché chaque fois que des éléments sont ajoutés ou supprimés de la collection que vous surveillez.

  • La charge utile de l’événement '<collection>Updated' possède un tableau added qui contient des valeurs qui ont été ajoutées à la collection.
  • La charge utile de l’événement '<collection>Updated' possède également un tableau removed qui contient des valeurs qui ont été supprimées de la collection.

Exemple d’abonnement sur une collection

Dans cet exemple, nous nous abonnons aux changements de valeurs de l’objet d’appel 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 ) 
    });
});

Événements sur l’objet CallAgent

Nom de l’événement : incomingCall

L’événement incomingCall se déclenche lorsque le client reçoit un appel entrant.

Comment votre application doit-elle réagir à l’événement ?

Votre application doit avertir l’utilisateur de l’appel entrant. L’invite de notification doit proposer à l’utilisateur d’accepter ou de refuser l’appel.

Exemple de code :

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

Nom de l’événement : callsUpdated

L’événement callsUpdated mis à jour est déclenché lorsqu’un appel est supprimé ou ajouté à l’agent d’appel. Cet événement se produit lorsque l’utilisateur passe, reçoit ou met fin à un appel.

Comment votre application doit-elle réagir à l’événement ? Votre application doit mettre à jour son interface utilisateur en fonction du nombre d’appels actifs pour l’instance CallAgent.

Nom de l’événement : connectionStateChanged

L’événement connectionStateChanged déclenché lorsque l’état de signalement de CallAgent est mis à jour.

Comment votre application doit-elle réagir à l’événement ?

Votre application doit mettre à jour son interface utilisateur en fonction du nouvel état. Les valeurs d’état de connexion possibles sont Connected et Disconnected

Exemple de code :

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

Événements sur l’objet Call

Nom de l’événement : stateChanged

L’événement stateChanged est déclenché lorsque l’état de l’appel change. Par exemple, lorsqu’un appel passe de connected à disconnected.

Comment votre application doit-elle réagir à l’événement ?

Votre application doit mettre à jour son interface utilisateur en conséquence. Désactiver ou activer les boutons appropriés et d’autres éléments d’interface utilisateur en fonction du nouvel état de l’appel.

Exemple de code :

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

Événement : idChanged

L’événement idChanged est déclenché lorsque l’ID d’un appel change. L’ID d’un appel change lorsque l’appel passe de l’état connecting à connected. Une fois l’appel connecté, l’ID de l’appel reste identique.

Comment votre application peut-elle réagir à l’événement ?

Votre application doit enregistrer le nouvel ID de l’appel, mais il peut également être récupéré ultérieurement à partir de l’objet de l’appel, en cas de besoin.

Exemple de code :

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

Événement : isMutedChanged

L’événement isMutedChanged est déclenché lorsque le son local est désactivé ou réactivé.

Comment votre application peut-elle réagir à l’événement ?

Votre application doit mettre à jour le bouton de désactivation / réactivation du son pour qu’il soit dans le bon état.

Exemple de code :

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

Événement : isScreenSharingOnChanged

L’événement isScreenSharingOnChanged est déclenché lorsque le partage d’écran pour l’utilisateur local est activé ou désactivé.

Comment votre application peut-elle réagir à l’événement ?

Votre application doit afficher un aperçu et/ou un avertissement à l’utilisateur si le partage d’écran est activé. Si le partage d’écran est désactivé, l’application doit supprimer l’aperçu et l’avertissement.

Exemple de code :

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

Événement : isLocalVideoStartedChanged

L’événement isLocalVideoStartedChanged est déclenché lorsque l’utilisateur a activé ou désactivé sa vidéo locale.

Comment votre application peut-elle réagir à l’événement ?

Votre application doit afficher un aperçu de la vidéo locale et activer ou désactiver le bouton d’activation de la caméra.

Exemple de code :

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

Événement : remoteParticipantsUpdated

Votre application doit s’abonner à un événement pour chaque RemoteParticipants ajouté et se désabonner des événements pour les participants ayant quitté l’appel.

Comment votre application peut-elle réagir à l’événement ? Votre application doit afficher un aperçu de la vidéo locale et activer ou désactiver le bouton d’activation de la caméra.

Exemple de code :

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

Événement : localVideoStreamsUpdated

L’événement localVideoStreamsUpdated est déclenché lorsque la liste des flux vidéo locaux change. Ces modifications se produisent lorsque l’utilisateur démarre ou supprime un flux vidéo.

Comment votre application peut-elle réagir à l’événement ?

Votre application doit afficher des aperçus pour chaque LocalVideoStream ajouté. Votre application doit supprimer l’aperçu et arrêter le traitement pour chaque LocalVideoStream supprimé.

Exemple de code :

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

Événement : remoteAudioStreamsUpdated

L’événement remoteAudioStreamsUpdated est déclenché lorsque la liste des flux audio distants change. Ces modifications se produisent lorsque les participants distants ajoutent ou suppriment des flux audio à l’appel.

Comment votre application peut-elle réagir à l’événement ?

Si un flux a été traité et est maintenant supprimé, le traitement doit être arrêté. En revanche, si un flux est ajouté, la réception de l’événement est un bon point de départ pour le traitement du nouveau flux audio.

Événement : totalParticipantCountChanged

Le totalParticipantCountChanged se déclenche lorsque le nombre de totalParticipant a changé lors d’un appel.

Comment votre application peut-elle réagir à l’événement ?

Si votre application affiche un compteur de participants, votre application peut mettre à jour son compteur de participants lorsque l’événement est reçu.

Exemple de code :

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

Événement : roleChanged

Le participant roleChanged se déclenche lorsque les rôles localParticipant changent dans l’appel. Par exemple, lorsque le participant local devient présentateur ACSCallParticipantRolePresenter dans un appel.

Comment votre application peut-elle réagir à l’événement ? Votre application doit activer ou désactiver le bouton en fonction du nouveau rôle de l’utilisateur.

Exemple de code :

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

Événement : mutedByOthers

L’événement mutedByOthers se produit lorsque le son des autres participants à l’appel est désactivé par le participant local.

Comment votre application peut-elle réagir à l’événement ? Votre application doit afficher un message à l’intention de l’utilisateur pour l’informer que son son a été désactivé.

Exemple de code :

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

Événement : callerInfoChanged

L’événement callerInfoChanged se produit quand les informations sur l’appelant ont été mises à jour.

Comment votre application peut-elle réagir à l’événement ? L’application peut mettre à jour les informations sur l’appelant.

Exemple de code :

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

Événement : transferorInfoChanged

L’événement transferorInfoChanged se produit quand les informations sur la personne à l’origine du transfert ont été mises à jour.

Comment votre application peut-elle réagir à l’événement ? L’application peut mettre à jour les informations sur la personne à l’origine du transfert.

Exemple de code :

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

Événements sur l’objet RemoteParticipant

Événement : roleChanged

L’événement roleChanged se déclenche lorsque le rôle du RemotePartipant change dans l’appel. Par exemple, lorsque le RemoteParticipant devient présentateur ACSCallParticipantRolePresenter dans un appel.

Comment votre application peut-elle réagir à l’événement ? Votre application doit mettre à jour son interface utilisateur en fonction du nouveau rôle du RemoteParticipant.

Exemple de code :

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

Événement : isMutedChanged

L’événement isMutedChanged se déclenche lorsque l’un des RemoteParticipant désactive ou réactive le son de son microphone.

Comment votre application peut-elle réagir à l’événement ?

Votre application peut afficher une icône près de la vue qui affiche le participant.

Exemple de code :

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

Événement : displayNameChanged

Le displayNameChanged lorsque le nom du RemoteParticipant est mis à jour.

Comment votre application peut-elle réagir à l’événement ?

Votre application doit mettre à jour le nom du participant s’il est affiché dans l’interface utilisateur.

Exemple de code :

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

Événement : isSpeakingChanged

Le isSpeakingChanged lorsque l’intervenant dominant d’un appel change.

Comment votre application peut-elle réagir à l’événement ?

L’interface utilisateur de votre application doit donner la priorité à l’affichage du RemotePartipant qui est devenu l’intervenant dominant.

Exemple de code :

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

Événement : videoStreamsUpdated

Le videoStreamsUpdated lorsqu’un participant distant ajoute ou retire un VideoStream de l’appel.

Comment votre application peut-elle réagir à l’événement ?

Si votre application traitait un flux qui est supprimé. Votre application doit arrêter le traitement. Lorsqu’un nouveau flux est ajouté, votre application peut souhaiter l’afficher ou le traiter.

Exemple de code :

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

Événement sur l’objet AudioEffectsFeature

Événement : effectsStarted

Cet événement se produit lorsque l’effet audio sélectionné est appliqué au flux audio. Par exemple, lorsqu’un utilisateur active la Suppression du bruit, le effectsStarted est déclenché.

Comment votre application peut-elle réagir à l’événement ?

Votre application peut afficher ou activer un bouton qui permet à l’utilisateur de désactiver l’effet audio.

Exemple de code :

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

Événement : effectsStopped

Cet événement se produit lorsque l’effet audio sélectionné est appliqué au flux audio. Par exemple, lorsqu’un utilisateur désactive la Suppression du bruit, le effectsStopped est déclenché.

Comment votre application peut-elle réagir à l’événement ?

Votre application peut afficher ou activer un bouton qui permet à l’utilisateur d’activer l’effet audio.

Exemple de code :

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

Événement : effectsError

Cet événement se produit lorsqu’une erreur se produit lors du démarrage ou de l’application d’un effet audio.

Comment votre application peut-elle réagir à l’événement ?

Votre application doit afficher une alerte ou un message d’erreur indiquant que l’effet audio ne fonctionne pas comme prévu.

Exemple de code :

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

Installer le SDK

Recherchez votre fichier build.gradle au niveau du projet et ajoutez mavenCentral() à la liste des référentiels sous buildscript et allprojects :

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

Ensuite, dans votre fichier build.gradle au niveau du module, ajoutez les lignes suivantes à la section dependencies :

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

Initialiser les objets nécessaires

Pour créer une instance CallAgent, vous devez appeler la méthode createCallAgent sur une instance CallClient. Cet appel retourne de façon asynchrone un objet d’instance CallAgent.

La méthode createCallAgent prend CommunicationUserCredential en tant qu’argument, qui encapsule un jeton d’accès.

Pour accéder à DeviceManager, vous devez d’abord créer une instance callAgent. Vous pouvez ensuite utiliser la méthode CallClient.getDeviceManager pour obtenir 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();

Pour définir un nom d’affichage pour l’appelant, utilisez cette autre méthode :

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

Avec notre SDK Android, vous pouvez vous abonner à la plupart des propriétés et collections pour être averti quand des valeurs changent.

Propriétés

Pour vous abonner à des événements 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);

Lorsque vous utilisez des écouteurs d’événements qui sont définis dans la même classe, liez l’écouteur à une variable. Passez la variable en tant qu’argument pour ajouter et supprimer des méthodes d’écouteur.

Si vous essayez de passer l’écouteur directement comme argument, vous perdrez la référence à cet écouteur. Java crée de nouvelles instances de ces écouteurs et ne fait pas référence à celles créées précédemment. Elles se déclencheront toujours correctement, mais ne pourront pas être supprimées, car vous n’aurez plus de référence à celles-ci.

Collections

Pour vous abonner à des événements 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);

Configurer votre système

Effectuez les étapes suivantes pour configurer votre système.

Créer le projet Xcode

Dans Xcode, créez un projet iOS et sélectionnez le modèle Single View App. Cet article utilise l’infrastructure SwiftUI. Vous devez donc définir Langage sur Swift et Interface sur SwiftUI.

Vous n’allez pas créer de tests dans cet article. N’hésitez pas à désactiver la case Inclure des tests.

Capture d’écran montrant la fenêtre de création d’un projet dans Xcode.

Installer le package et les dépendances à l’aide de CocoaPods

  1. Créez un Podfile pour votre application, comme cet exemple :

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

  3. Ouvrez .xcworkspace en utilisant Xcode.

Demander l’accès au microphone

Pour accéder au microphone de l’appareil, vous devez mettre à jour la liste des propriétés d’informations de votre application à l’aide de NSMicrophoneUsageDescription. Définissez la valeur associée à une chaîne qui est incluse dans la boîte de dialogue utilisée par le système pour demander l’accès à l’utilisateur.

Cliquez avec le bouton droit sur l’entrée Info.plist de l’arborescence du projet, puis sélectionnez Ouvrir en tant que>Code source. Ajoutez les lignes suivantes à la section <dict> tout en haut, puis enregistrez le fichier.

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

Configurer le framework d’application

Ouvrez le fichier ContentView.swift de votre projet. Ajoutez une déclaration import en haut du fichier pour importer la bibliothèque AzureCommunicationCalling. En outre, importez AVFoundation. Vous en avez besoin pour les demandes d’autorisations audio dans le code.

import AzureCommunicationCalling
import AVFoundation

Initialiser CallAgent

Pour créer une instance de CallAgent à partir de CallClient, vous devez utiliser une méthode callClient.createCallAgent qui retourne de manière asynchrone un objet CallAgent après qu’il a été initialisé.

Pour créer un client d’appel, transmettez un objet 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)
}

Transmettez l’objet CommunicationTokenCredential que vous avez créé à CallClient et définissez le nom complet :

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

Avec votre SDK iOS, vous pouvez vous abonner à la plupart des propriétés et des collections pour être averti quand des valeurs changent.

Propriétés

Pour vous abonner aux événements property changed, utilisez le code suivant.

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

Collections

Pour vous abonner aux événements collection updated, utilisez le code suivant.

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

Étapes suivantes