Gérer la vidéo pendant les appels

Découvrez comment gérer les appels vidéo avec les kits de développement logiciel (SDK) Azure Communication Services. Nous allons apprendre à gérer la réception et l’envoi de vidéo pendant un appel.

Prérequis

Installer le SDK

Utilisez la commande npm install pour installer le SDK Azure Communication Services Common et le SDK Azure Communication Services Calling pour JavaScript :

npm install @azure/communication-common --save
npm install @azure/communication-calling --save

Initialiser les objets nécessaires

Une instance CallClient est requise pour la plupart des opérations d’appel. Lorsque vous créez une instance CallClient, vous pouvez la configurer avec des options personnalisées comme une instance Logger.

Avec l’instance CallClient, vous pouvez créer une instance CallAgent en appelant createCallAgent. Cette méthode renvoie un objet d’instance CallAgent de manière asynchrone.

La méthode createCallAgent utilise CommunicationTokenCredential comme argument. Elle accepte un jeton d’accès utilisateur.

Vous pouvez utiliser la méthode getDeviceManager sur l’instance CallClient pour accéder à deviceManager.

const { CallClient } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential} = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");

// Set the logger's log level
setLogLevel('verbose');

// Redirect log output to console, file, buffer, REST API, or whatever location you want
AzureLogger.log = (...args) => {
    console.log(...args); // Redirect log output to console
};

const userToken = '<USER_TOKEN>';
callClient = new CallClient(options);
const tokenCredential = new AzureCommunicationTokenCredential(userToken);
const callAgent = await callClient.createCallAgent(tokenCredential, {displayName: 'optional Azure Communication Services user name'});
const deviceManager = await callClient.getDeviceManager()

Comment gérer au mieux la connectivité du SDK à l’infrastructure Microsoft

L’instance Call Agent vous aide à gérer les appels (pour rejoindre ou démarrer des appels). Pour fonctionner, votre SDK d’appel doit se connecter à l’infrastructure Microsoft pour recevoir des notifications d’appels entrants et coordonner d’autres détails de l’appel. Votre Call Agent a deux états possibles :

Connecté : un Call Agent dont la valeur connectionStatue est égale à Connected signifie que le SDK client est connecté et capable de recevoir des notifications de l’infrastructure Microsoft.

Déconnecté : un Call Agent dont la valeur connectionStatue est égale à Disconnected indique qu’un problème empêche le SDK de se connecter correctement. Call Agent doit être recréé.

  • invalidToken : si un jeton a expiré ou n’est pas valide, l’instance Call Agent se déconnecte avec cette erreur.
  • connectionIssue : en cas de problème de connexion du client à l’infrastructure Microsoft, après plusieurs tentatives, Call Agent lève l’erreur connectionIssue.

Vous pouvez vérifier si votre Call Agent local est connecté à l’infrastructure Microsoft en inspectant la valeur actuelle de la propriété connectionState. Pendant un appel actif, vous pouvez écouter l’événement connectionStateChanged pour déterminer si Call Agent passe de l’état Connecté à l’état Déconnecté.

const connectionState = callAgentInstance.connectionState;
console.log(connectionState); // it may return either of 'Connected' | 'Disconnected'

const connectionStateCallback = (args) => {
    console.log(args); // it will return an object with oldState and newState, each of having a value of either of 'Connected' | 'Disconnected'
    // it will also return reason, either of 'invalidToken' | 'connectionIssue'
}
callAgentInstance.on('connectionStateChanged', connectionStateCallback);

Gestion des appareils

Pour commencer à utiliser de la vidéo avec le SDK Calling, vous devez être en mesure de gérer les appareils. Les appareils vous permettent de contrôler ce que transmet l’audio et la vidéo à l’appel.

Avec deviceManager, vous pouvez énumérer les appareils locaux qui peuvent transmettre vos flux audio et vidéo dans un appel. Vous pouvez également utiliser deviceManager pour demander l’autorisation d’accéder aux micros et appareils photo de l’appareil local.

Vous pouvez accéder à deviceManager en appelant la méthode callClient.getDeviceManager() :

const deviceManager = await callClient.getDeviceManager();

Récupérer les appareils locaux

Pour accéder aux appareils locaux, vous pouvez utiliser les méthodes d’énumération deviceManager suivantes : getCameras() et getMicrophones. Ces méthodes sont des actions asynchrones.

//  Get a list of available video devices for use.
const localCameras = await deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

// Get a list of available microphone devices for use.
const localMicrophones = await deviceManager.getMicrophones(); // [AudioDeviceInfo, AudioDeviceInfo...]

// Get a list of available speaker devices for use.
const localSpeakers = await deviceManager.getSpeakers(); // [AudioDeviceInfo, AudioDeviceInfo...]

Définir les appareils par défaut

Après avoir identifié les appareils disponibles, vous pouvez définir les appareils par défaut pour le micro, le haut-parleur et l’appareil photo. Si les paramètres par défaut du client ne sont pas définis, le Kit de développement logiciel (SDK) Communication Services utilise les paramètres par défaut du système d’exploitation.

Microphone

Accéder à l’appareil utilisé

// Get the microphone device that is being used.
const defaultMicrophone = deviceManager.selectedMicrophone;

Définir l’appareil à utiliser

// Set the microphone device to use.
await deviceManager.selectMicrophone(localMicrophones[0]);

Intervenant

Accéder à l’appareil utilisé

// Get the speaker device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;

Définir l’appareil à utiliser

// Set the speaker device to use.
await deviceManager.selectSpeaker(localSpeakers[0]);

Appareil photo

Accéder à l’appareil utilisé

// Get the camera device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;

Définir l’appareil à utiliser

// Set the speaker device to use.
await deviceManager.selectSpeaker(localCameras[0]);

Chaque CallAgent peut choisir son propre microphone et haut-parleurs sur son associé(e)DeviceManager. Nous recommandons à différents CallAgents d’utiliser des micros et des haut-parleurs différents. Ils ne doivent pas partager les mêmes micros et haut-parleurs. En cas de partage, les diagnostics accessibles à l’utilisateur du micro peuvent être déclenchés et entraîner l’arrêt du micro en fonction du navigateur/système d’exploitation.

Flux vidéo local

Pour pouvoir envoyer une vidéo dans un appel, vous devez créer un objet LocalVideoStream.

const localVideoStream = new LocalVideoStream(camera);

L’appareil photo passé en tant que paramètre est l’un des objets VideoDeviceInfo retournés par la méthode deviceManager.getCameras().

Un LocalVideoStream possède les propriétés suivantes :

  • source : les informations des appareils.
const source = localVideoStream.source;
  • mediaStreamType : Peut être Video, ScreenSharing ou RawMedia.
const type: MediaStreamType = localVideoStream.mediaStreamType;

Aperçu de la caméra locale

Vous pouvez utiliser deviceManager et VideoStreamRenderer pour commencer à afficher le flux de votre caméra locale. Une fois qu’un LocalVideoStream est créé, utilisez-le pour configurer VideoStreamRenderer. Une fois le VideoStreamRenderer créé, appelez sa méthode createView() pour obtenir une vue que vous pouvez ajouter en tant qu’enfant à votre page.

Ce flux n’est pas envoyé à d’autres participants ; il s’agit d’un flux d’aperçu local.

// To start viewing local camera preview
const cameras = await deviceManager.getCameras();
const camera = cameras[0];
const localVideoStream = new LocalVideoStream(camera);
const videoStreamRenderer = new VideoStreamRenderer(localVideoStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

Arrêter l’aperçu local

Pour arrêter l’appel d’aperçu local, supprimez la vue dérivée de VideoStreamRenderer. Une fois le VideoStreamRenderer supprimé, supprimez la vue de l’arborescence HTML en appelant la méthode removeChild() à partir du nœud DOM contenant votre aperçu.

// To stop viewing local camera preview
view.dispose();
htmlElement.removeChild(view.target);

Demander l’autorisation d’accès à une caméra et à un microphone

Une application ne peut pas utiliser l’appareil photo ou le micro sans autorisations. Vous pouvez utiliser le deviceManager pour demander à un utilisateur d’autoriser l’accès à son appareil photo et/ou micro :

const result = await deviceManager.askDevicePermission({audio: true, video: true});

Une fois la promesse résolue, la méthode est retournée avec un objet DeviceAccess qui indique si les autorisations audio et video ont été accordées :

console.log(result.audio);
console.log(result.video);

Notes

  • L’événement videoDevicesUpdated se déclenche lorsque des appareils vidéo sont en cours de branchement/débranchés.
  • L’événement audioDevicesUpdated se déclenche lorsque des appareils audio sont branchés.
  • Lorsque le DeviceManager est créé, il n’a d’abord connaissance d’aucun appareil si les autorisations n’ont pas encore été accordées. Aucun nom d’appareil n’est donc initialement indiqué et il ne contient pas d’informations détaillées sur les appareils. Si nous appelons ensuite l’API DeviceManager.askPermission(), l’utilisateur est invité à accéder à l’appareil. Lorsque l’utilisateur sélectionne « allow » pour autoriser l’accès, le gestionnaire d’appareils s’informe sur les appareils présents sur le système, met à jour ses listes d’appareils et émet les événements « audioDevicesUpdated » et « videoDevicesUpdated ». Si un utilisateur actualise la page et crée un gestionnaire d’appareils, ce dernier est en mesure de se renseigner sur les appareils puisque l’utilisateur lui a précédemment accordé l’accès. Ses listes d’appareils sont initialement remplies et il n’émet pas d’événements « audioDevicesUpdated » ou « videoDevicesUpdated ».
  • L’énumération/sélection d’intervenants n’est pas prise en charge sur Android Chrome, iOS Safari et macOS Safari.

Passer un appel avec une caméra vidéo

Important

Actuellement, un seul flux vidéo local sortant est pris en charge.

Pour passer un appel vidéo, vous devez énumérer les caméras locales avec la méthode getCameras() dans deviceManager.

Après avoir sélectionné une caméra, utilisez-la pour construire une instance LocalVideoStream. Transmettez-la dans videoOptions en tant qu’élément du tableau localVideoStream à la méthode CallAgent startCall.

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
const placeCallOptions = {videoOptions: {localVideoStreams:[localVideoStream]}};
const userCallee = { communicationUserId: '<ACS_USER_ID>' }
const call = callAgent.startCall([userCallee], placeCallOptions);
  • Vous pouvez également joindre un appel avec une vidéo avec l’API CallAgent.join(), et accepter et appeler avec la vidéo avec l’API Call.Accept().
  • Quand votre appel est connecté, il commence automatiquement à envoyer un flux vidéo à l’autre participant à partir de la caméra sélectionnée.

Démarrer et arrêter l’envoi d’une vidéo locale pendant un appel

Démarrer la vidéo

Pour démarrer une vidéo pendant un appel, vous devez énumérer les caméras à l’aide de la méthode getCameras sur l’objet deviceManager. Créez ensuite une instance de LocalVideoStream avec la caméra souhaitée, puis passez l’objet LocalVideoStream dans la méthode startVideo d’un objet d’appel existant :

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
await call.startVideo(localVideoStream);

Arrêter la vidéo

Après que vous avez commencé à envoyer une vidéo, une instance LocalVideoStream de type Video est ajoutée à la collection localVideoStreams sur une instance d’appel.

Rechercher le flux vidéo dans l’objet Call

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'Video'} );

Arrêter la vidéo locale Pour arrêter la vidéo locale pendant un appel, passez l’instance localVideoStream utilisée pour la vidéo à la méthode stopVideo de Call :

await call.stopVideo(localVideoStream);

Vous pouvez basculer vers un autre appareil photo tout en ayant un LocalVideoStream actif en appelant switchSource sur cette instance LocalVideoStream :

const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);

Si l’appareil vidéo spécifié n’est pas disponible :

  • Lors d’un appel, si votre vidéo est désactivée et que vous démarrez la vidéo à l’aide de call.startVideo(), cette méthode génère un SourceUnavailableError et le diagnostic face à l’utilisateur(-trice) cameraStartFailed est défini sur true.
  • Un appel à la méthode localVideoStream.switchSource() entraîne la modification de cameraStartFailed en True. Notre guide Diagnostics des appels fournit des informations supplémentaires sur la façon de diagnostiquer les problèmes liés aux appels.

Pour vérifier si la vidéo locale est activée ou désactivée, vous pouvez utiliser la méthode Call isLocalVideoStarted, qui retourne true ou false :

// Check if local video is on or off
call.isLocalVideoStarted;

Pour écouter les modifications apportées à la vidéo locale, vous pouvez vous abonner et vous désabonner à l’événement isLocalVideoStartedChanged :

// Subscribe to local video event
call.on('isLocalVideoStartedChanged', () => {
    // Callback();
});
// Unsubscribe from local video event
call.off('isLocalVideoStartedChanged', () => {
    // Callback();
});

Démarrer et arrêter le partage d’écran pendant un appel

Pour démarrer le partage d’écran pendant un appel, vous pouvez utiliser la méthode asynchrone startScreenSharing() sur un objet Call :

Démarrer le partage d’écran

// Start screen sharing
await call.startScreenSharing();

Remarque : L’envoi de partages d’écran est pris en charge uniquement par un navigateur de bureau.

Rechercher le partage d’écran dans la collection de LocalVideoStream

Après avoir lancé avec succès l’envoi du partage d’écran, une instance LocalVideoStream de type ScreenSharing, est ajoutée à la collection localVideoStreams de l’instance d’appel.

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing'} );

Arrêter le partage d’écran

Pour arrêter le partage d’écran pendant un appel, vous pouvez utiliser l’API asynchrone stopScreenSharing :

// Stop screen sharing
await call.stopScreenSharing();

Vérifier l’état du partage d’écran

Pour vérifier si le partage d’écran est activé ou désactivé, vous pouvez utiliser l’API isScreenSharingOn qui retourne true ou false :

// Check if screen sharing is on or off
call.isScreenSharingOn;

Pour écouter les modifications apportées au partage d’écran, vous pouvez vous abonner et vous désabonner à l’événement isScreenSharingOnChanged :

// Subscribe to screen share event
call.on('isScreenSharingOnChanged', () => {
    // Callback();
});
// Unsubscribe from screen share event
call.off('isScreenSharingOnChanged', () => {
    // Callback();
});

Important

Cette fonctionnalité d’Azure Communication Services est actuellement en préversion.

Ces interfaces de programmation d’applications et kits de développement logiciel (SDK) en préversion sont fournis sans contrat au niveau du service. Nous vous recommandons de ne pas les utiliser pour les charges de travail de production. Certaines fonctionnalités peuvent être limitées ou non prises en charge.

Pour plus d’informations, consultez Conditions d’utilisation supplémentaires relatives aux préversions de Microsoft Azure.

La préversion de partage d’écran local est en préversion publique et disponible dans le cadre de la version 1.15.1-beta.1+.

Préversion du partage d’écran local

Vous pouvez utiliser un VideoStreamRenderer pour commencer le rendu de flux à partir de votre partage d’écran local afin de voir ce que vous envoyez comme flux de partage d’écran.

// To start viewing local screen share preview
await call.startScreenSharing();
const localScreenSharingStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing' });
const videoStreamRenderer = new VideoStreamRenderer(localScreenSharingStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

// To stop viewing local screen share preview.
await call.stopScreenSharing();
view.dispose();
htmlElement.removeChild(view.target);

// Screen sharing can also be stoped by clicking on the native browser's "Stop sharing" button.
// The isScreenSharingOnChanged event will be triggered where you can check the value of call.isScreenSharingOn.
// If the value is false, then that means screen sharing is turned off and so we can go ahead and dispose the screen share preview.
// This event is also triggered for the case when stopping screen sharing via Call.stopScreenSharing() API.
call.on('isScreenSharingOnChanged', () => {
    if (!call.isScreenSharingOn) {
        view.dispose();
        htmlElement.removeChild(view.target);
    }
});

Afficher les flux de partage de partage d’écran ou de vidéo de participants distants

Pour afficher une vidéo ou un partage d’écran d’un participant distant, la première chose à faire consiste à obtenir une référence sur le RemoteVideoStream que vous souhaitez afficher. Pour cela, vous pouvez parcourir le tableau ou le flux vidéo (videoStreams) du RemoteParticipant. La collection de participants distants est accessible via l’objet Call.

const remoteVideoStream = call.remoteParticipants[0].videoStreams[0];
const streamType = remoteVideoStream.mediaStreamType;

Pour afficher RemoteVideoStream, vous devez vous abonner à son événement isAvailableChanged. Si la propriété isAvailable passe à true, un participant distant envoie un flux vidéo. Une fois que cela se produit, créez une instance de VideoStreamRenderer, puis une instance VideoStreamRendererView en utilisant la méthode createView asynchrone.
Vous pouvez ensuite attacher view.target à un élément d’interface utilisateur quelconque.

Chaque fois que la disponibilité d’un flux distant change, vous pouvez détruire la totalité de VideoStreamRenderer ou un VideoStreamRendererView spécifique. Si vous décidez de les conserver, la vue affiche un cadre vidéo vide.

// Reference to the html's div where we would display a grid of all remote video stream from all participants.
let remoteVideosGallery = document.getElementById('remoteVideosGallery');

subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    let renderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    let remoteVideoContainer = document.createElement('div');
    remoteVideoContainer.className = 'remote-video-container';

    let loadingSpinner = document.createElement('div');
    // See the css example below for styling the loading spinner.
    loadingSpinner.className = 'loading-spinner';
    remoteVideoStream.on('isReceivingChanged', () => {
        try {
            if (remoteVideoStream.isAvailable) {
                const isReceiving = remoteVideoStream.isReceiving;
                const isLoadingSpinnerActive = remoteVideoContainer.contains(loadingSpinner);
                if (!isReceiving && !isLoadingSpinnerActive) {
                    remoteVideoContainer.appendChild(loadingSpinner);
                } else if (isReceiving && isLoadingSpinnerActive) {
                    remoteVideoContainer.removeChild(loadingSpinner);
                }
            }
        } catch (e) {
            console.error(e);
        }
    });

    const createView = async () => {
        // Create a renderer view for the remote video stream.
        view = await renderer.createView();
        // Attach the renderer view to the UI.
        remoteVideoContainer.appendChild(view.target);
        remoteVideosGallery.appendChild(remoteVideoContainer);
    }

    // Remote participant has switched video on/off
    remoteVideoStream.on('isAvailableChanged', async () => {
        try {
            if (remoteVideoStream.isAvailable) {
                await createView();
            } else {
                view.dispose();
                remoteVideosGallery.removeChild(remoteVideoContainer);
            }
        } catch (e) {
            console.error(e);
        }
    });

    // Remote participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        try {
            await createView();
        } catch (e) {
            console.error(e);
        }
    }
    
    console.log(`Initial stream size: height: ${remoteVideoStream.size.height}, width: ${remoteVideoStream.size.width}`);
    remoteVideoStream.on('sizeChanged', () => {
        console.log(`Remote video stream size changed: new height: ${remoteVideoStream.size.height}, new width: ${remoteVideoStream.size.width}`);
    });
}

CSS pour styliser la boucle de chargement sur le flux vidéo distant.

.remote-video-container {
   position: relative;
}
.loading-spinner {
   border: 12px solid #f3f3f3;
   border-radius: 50%;
   border-top: 12px solid #ca5010;
   width: 100px;
   height: 100px;
   -webkit-animation: spin 2s linear infinite; /* Safari */
   animation: spin 2s linear infinite;
   position: absolute;
   margin: auto;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   transform: translate(-50%, -50%);
}
@keyframes spin {
   0% { transform: rotate(0deg); }
   100% { transform: rotate(360deg); }
}
/* Safari */
@-webkit-keyframes spin {
   0% { -webkit-transform: rotate(0deg); }
   100% { -webkit-transform: rotate(360deg); }
}

Vidéo de qualité distante

Le kit SDK WebJS Azure Communication Services fournit, à compter de la version 1.15.1, une fonctionnalité appelée OVC (Optimal Video Count). Vous pouvez utiliser cette fonction pour informer les applications, au moment de l’exécution, du nombre de vidéos entrantes provenant de différents participants qui peuvent être affichées de manière optimale à un moment donné lors d’un appel de groupe (2 participants ou plus). Cette fonction expose une propriété optimalVideoCount qui change dynamiquement au cours de l’appel en fonction du réseau et des capacités matérielles d’un point d’extrémité local. La valeur de optimalVideoCount précise le nombre de vidéos provenant de différents participants que l’application doit afficher à un moment donné. Les applications doivent gérer ces changements et mettre à jour le nombre de vidéos affichées en fonction de la recommandation. Une période anti-rebond (« debounce ») d’environ 10 s existe entre chaque mise à jour.

Utilisation La fonctionnalité optimalVideoCount est une fonctionnalité d’appel. Vous devez référencer la fonctionnalité OptimalVideoCount via la méthode feature de l’objet Call. Vous pouvez ensuite définir un écouteur par le biais de la méthode on de OptimalVideoCountCallFeature pour être averti quand optimalVideoCount change. Pour vous désabonner des changements, vous pouvez appeler la méthode off. Le nombre maximal de vidéos entrantes pouvant être restituées est de 16. Pour prendre en charge 16 vidéos entrantes, l’ordinateur devrait disposer d’au moins 16 Go de RAM et d’un processeur à 4 cœurs ou plus qui n’a pas plus de 3 ans.

const optimalVideoCountFeature = call.feature(Features.OptimalVideoCount);
optimalVideoCountFeature.on('optimalVideoCountChanged', () => {
    const localOptimalVideoCountVariable = optimalVideoCountFeature.optimalVideoCount;
})

Exemple d’utilisation : l’application doit s’abonner aux changements d’Optimal Video Count dans des appels de groupe. Vous pouvez gérer un changement d’Optimal Video Count en créant un renderer (méthode createView) ou en supprimant des vues (dispose) et en mettant à jour la disposition de l’application en conséquence.

Propriétés du flux vidéo distant

Les flux vidéo distants ont les propriétés suivantes :

const id: number = remoteVideoStream.id;
  • id : ID d’un flux vidéo distant.
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
  • mediaStreamType : Peut être Video ou ScreenSharing.
const isAvailable: boolean = remoteVideoStream.isAvailable;
  • isAvailable : Définir si le point de terminaison du ou de la participant(e) distant(e) envoie activement un flux.
const isReceiving: boolean = remoteVideoStream.isReceiving;
  • isReceiving :
    • Informe l’application si des données de flux vidéo distant sont reçues ou non.

    • L’indicateur passe à false dans les scénarios suivants :

      • Un participant distant utilisant un navigateur mobile fait passer l’application du navigateur à l’arrière-plan.
      • Un participant distant ou l’utilisateur recevant la vidéo fait face à un problème réseau qui affecte considérablement la qualité de la vidéo.
      • Un participant distant utilisant Safari sur macOS/iOS sélectionne « Pause » (Suspendre) dans sa barre d’adresses.
      • Un participant distant est déconnecté du réseau.
      • Un participant distant sur un appareil mobile tue ou termine le navigateur.
      • Un participant distant sur un appareil mobile ou un ordinateur de bureau verrouille son appareil. Ce scénario s’applique également si le participant distant se trouve sur un ordinateur de bureau qui se met en veille.
    • L’indicateur passe à true dans les scénarios suivants :

      • Un participant distant utilisant un navigateur mobile fait repasser son navigateur au premier plan.
      • Un participant distant utilisant Safari sur macOS/iOS sélectionne « Resume » (Reprendre) dans sa barre d’adresses après avoir interrompu sa vidéo.
      • Un participant distant se reconnecte au réseau après une déconnexion temporaire.
      • Un participant distant sur un appareil mobile déverrouille son appareil et retourne à l’appel sur son navigateur mobile.
    • Cette fonctionnalité améliore l’expérience utilisateur pour le rendu de flux vidéo distants.

    • Vous pouvez afficher une boucle de chargement sur le flux vidéo distant lorsque l’indicateur de récupération change en « false ». Vous n’êtes pas obligé d’implémenter une boucle de progression du chargement, mais il est de coutume d’en inclure une pour offrir une meilleure expérience utilisateur.

const size: StreamSize = remoteVideoStream.size;
  • size : taille du flux avec des informations sur la largeur et la hauteur de la vidéo.

Méthodes et propriétés de VideoStreamRenderer

await videoStreamRenderer.createView();

Créez une instance de VideoStreamRendererView qui peut être jointe dans l’interface utilisateur de l’application pour afficher le flux vidéo distant. Utilisez la méthode createView() asynchrone ; elle est résolue quand le flux est prêt à être affiché et retourne un objet avec une propriété target qui représente l’élément video pouvant être inséré n’importe où dans l’arborescence DOM.

videoStreamRenderer.dispose();

Supprimez videoStreamRenderer et toutes les instances de VideoStreamRendererView associées.

Méthodes et propriétés de VideoStreamRendererView

Quand vous créez un VideoStreamRendererView, vous pouvez spécifier les propriétés scalingMode et isMirrored. scalingMode Peut être Stretch, Crop ou Fit. Si isMirrored est spécifié, le flux affiché est retourné verticalement.

const videoStreamRendererView: VideoStreamRendererView = await videoStreamRenderer.createView({ scalingMode, isMirrored });

Chaque instance VideoStreamRendererView a une propriété target qui représente la surface du rendu. Attachez cette propriété dans l’interface utilisateur de l’application :

htmlElement.appendChild(view.target);

Vous pouvez mettre à jour scalingMode en appelant la méthode updateScalingMode :

view.updateScalingMode('Crop');

Envoyez des flux vidéo provenant de deux caméras différentes, lors d’un même appel, à partir du même appareil de bureau.

Important

Cette fonctionnalité d’Azure Communication Services est actuellement en préversion.

Ces interfaces de programmation d’applications et kits de développement logiciel (SDK) en préversion sont fournis sans contrat au niveau du service. Nous vous recommandons de ne pas les utiliser pour les charges de travail de production. Certaines fonctionnalités peuvent être limitées ou non prises en charge.

Pour plus d’informations, consultez Conditions d’utilisation supplémentaires relatives aux préversions de Microsoft Azure.

L’envoi de flux vidéo à partir de deux appareils photo différents dans le même appel est pris en charge dans le cadre de la version 1.17.1-beta.1+ sur les navigateurs de bureau pris en charge.

  • Vous pouvez envoyer des flux vidéo provenant de deux caméras différentes à partir d’un seul onglet/application de navigateur de bureau, dans le même appel, avec l’extrait de code suivant :
// Create your first CallAgent with identity A
const callClient1 = new CallClient();
const callAgent1 = await callClient1.createCallAgent(tokenCredentialA);
const deviceManager1 = await callClient1.getDeviceManager();

// Create your second CallAgent with identity B
const callClient2 = new CallClient();
const callAgent2 = await callClient2.createCallAgent(tokenCredentialB);
const deviceManager2 = await callClient2.getDeviceManager();

// Join the call with your first CallAgent
const camera1 = await deviceManager1.getCameras()[0];
const callObj1 = callAgent1.join({ groupId: ‘123’}, { videoOptions: { localVideoStreams: [new LocalVideoStream(camera1)] } });

// Join the same call with your second CallAgent and make it use a different camera
const camera2 = (await deviceManager2.getCameras()).filter((camera) => { return camera !== camera1 })[0];
const callObj2 = callAgent2.join({ groupId: '123' }, { videoOptions: { localVideoStreams: [new LocalVideoStream(camera2)] } });

//Mute the microphone and speakers of your second CallAgent’s Call, so that there is no echos/noises.
await callObj2.muteIncomingAudio();
await callObj2.mute();

Limites :

  • Pour cela, vous devez utiliser deux instances différentes de CallAgent avec des identités différentes. L’extrait de code montre deux agents d’appel utilisés, chacun avec son propre objet Call.
  • Dans l’exemple de code, les deux CallAgents rejoignent le même appel (même ID d’appel). Vous pouvez également joindre différents appels avec chaque agent et envoyer une vidéo sur un appel et une autre vidéo sur l’autre appel.
  • L’envoi de la même caméra dans les deux CallAgent n’est pas possible. Ils doivent être deux caméras différentes.
  • L’envoi de deux caméras différentes avec un CallAgent n’est actuellement pas pris en charge.
  • Sur macOS Safari, les effets vidéo flous d’arrière-plan (de @azure/communication-effects), ne peuvent être appliqués qu’à une seule caméra, et pas les deux en même temps.

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

Gestion des appareils

Pour commencer à utiliser de la vidéo avec les appels, vous devez savoir comment gérer les appareils. Les appareils vous permettent de contrôler ce que transmet l’audio et la vidéo à l’appel.

DeviceManager vous permet d’énumérer les appareils locaux utilisables dans un appel pour transmettre vos flux audio/vidéo. Il vous permet également de demander à un utilisateur l’autorisation d’accéder à son microphone et à sa caméra à l’aide de l’API de navigateur natif.

Vous pouvez accéder au deviceManager en appelant la méthode callClient.getDeviceManager().

Context appContext = this.getApplicationContext();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

Énumérer les appareils locaux

Pour accéder aux appareils locaux, vous pouvez utiliser les méthodes d’énumération sur le Gestionnaire d’appareils. L’énumération est une action synchrone.

//  Get a list of available video devices for use.
List<VideoDeviceInfo> localCameras = deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

Aperçu de la caméra locale

Vous pouvez utiliser DeviceManager et Renderer pour commencer à afficher le flux de votre caméra locale. Ce flux n’est pas envoyé à d’autres participants ; il s’agit d’un flux d’aperçu local. Cette action est asynchrone.

VideoDeviceInfo videoDevice = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentVideoStream = new LocalVideoStream(videoDevice, appContext);

LocalVideoStream[] localVideoStreams = new LocalVideoStream[1];
localVideoStreams[0] = currentVideoStream;

VideoOptions videoOptions = new VideoOptions(localVideoStreams);

RenderingOptions renderingOptions = new RenderingOptions(ScalingMode.Fit);
VideoStreamRenderer previewRenderer = new VideoStreamRenderer(currentVideoStream, appContext);

VideoStreamRendererView uiView = previewRenderer.createView(renderingOptions);

// Attach the uiView to a viewable location on the app at this point
layout.addView(uiView);

Passer un appel 1:1 avec une caméra vidéo

Avertissement

Actuellement, un seul flux vidéo local sortant est pris en charge. Pour passer un appel avec vidéo, vous devez énumérer les caméras locales à l’aide de l’API deviceManager getCameras. Une fois la caméra sélectionnée, utilisez-la pour créer une instance LocalVideoStream et la transmettre à videoOptions en tant qu’élément dans le tableau localVideoStream vers une méthode call. Une fois l’appel connecté, il commence automatiquement à envoyer un flux vidéo aux autres participants à partir de la caméra sélectionnée.

Remarque

Pour des raisons de confidentialité, la vidéo ne sera pas partagée lors de l’appel si elle n’est pas visionnée localement. Pour plus d’informations, consultez Aperçu de la caméra locale.

VideoDeviceInfo desiredCamera = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentVideoStream = new LocalVideoStream(desiredCamera, appContext);

LocalVideoStream[] localVideoStreams = new LocalVideoStream[1];
localVideoStreams[0] = currentVideoStream;

VideoOptions videoOptions = new VideoOptions(localVideoStreams);

// Render a local preview of video so the user knows that their video is being shared
Renderer previewRenderer = new VideoStreamRenderer(currentVideoStream, appContext);
View uiView = previewRenderer.createView(new CreateViewOptions(ScalingMode.FIT));

// Attach the uiView to a viewable location on the app at this point
layout.addView(uiView);

CommunicationUserIdentifier[] participants = new CommunicationUserIdentifier[]{ new CommunicationUserIdentifier("<acs user id>") };

StartCallOptions startCallOptions = new StartCallOptions();
startCallOptions.setVideoOptions(videoOptions);

Call call = callAgent.startCall(context, participants, startCallOptions);

Démarrer et arrêter l’envoi d’une vidéo locale

Pour démarrer une vidéo, vous devez énumérer les caméras à l’aide de l’API getCameraList sur l’objet deviceManager. Cela crée une nouvelle instance de LocalVideoStream transmettant la caméra souhaitée, et en la transmettant dans l’API startVideo comme argument :

VideoDeviceInfo desiredCamera = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentLocalVideoStream = new LocalVideoStream(desiredCamera, appContext);

VideoOptions videoOptions = new VideoOptions(currentLocalVideoStream);

Future startVideoFuture = call.startVideo(appContext, currentLocalVideoStream);
startVideoFuture.get();

Une fois que vous avez commencé à envoyer une vidéo, une instance LocalVideoStream est ajoutée à la collection localVideoStreams sur l’instance d’appel.

List<LocalVideoStream> videoStreams = call.getLocalVideoStreams();
LocalVideoStream currentLocalVideoStream = videoStreams.get(0); // Please make sure there are VideoStreams in the list before calling get(0).

Pour arrêter la vidéo locale, transmettez l’instance LocalVideoStream disponible dans la collection localVideoStreams :

call.stopVideo(appContext, currentLocalVideoStream).get();

Vous pouvez basculer vers une autre caméra pendant l’envoi d’une vidéo en appelant switchSource sur une instance LocalVideoStream :

currentLocalVideoStream.switchSource(source).get();

Afficher les flux vidéo des participants distants

Pour répertorier les flux vidéo et les flux de partage d’écran des participants distants, inspectez les collections videoStreams :

List<RemoteParticipant> remoteParticipants = call.getRemoteParticipants();
RemoteParticipant remoteParticipant = remoteParticipants.get(0); // Please make sure there are remote participants in the list before calling get(0).

List<RemoteVideoStream> remoteStreams = remoteParticipant.getVideoStreams();
RemoteVideoStream remoteParticipantStream = remoteStreams.get(0); // Please make sure there are video streams in the list before calling get(0).

MediaStreamType streamType = remoteParticipantStream.getType(); // of type MediaStreamType.Video or MediaStreamType.ScreenSharing

Pour afficher le RemoteVideoStream d’un participant distant, vous devez vous abonner à un événement OnVideoStreamsUpdated.

Dans l’événement, la modification de la propriété isAvailable sur true indique que le participant distant envoie actuellement un flux. Une fois cette opération effectuée, créez une nouvelle instance d’un(e) Renderer, puis créez un nouveau/une nouvelle RendererView à l’aide de l’API createView asynchrone et joignez view.target n’importe où dans l’interface utilisateur de votre application.

Chaque fois que la disponibilité d’un flux distant change, vous pouvez choisir de détruire le convertisseur tout entier ou un RendererView spécifique, ou de les conserver, mais cela entraîne l’affichage d’une image vidéo vide.

VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteParticipantStream, appContext);
VideoStreamRendererView uiView = remoteVideoRenderer.createView(new RenderingOptions(ScalingMode.FIT));
layout.addView(uiView);

remoteParticipant.addOnVideoStreamsUpdatedListener(e -> onRemoteParticipantVideoStreamsUpdated(p, e));

void onRemoteParticipantVideoStreamsUpdated(RemoteParticipant participant, RemoteVideoStreamsEvent args) {
    for(RemoteVideoStream stream : args.getAddedRemoteVideoStreams()) {
        if(stream.getIsAvailable()) {
            startRenderingVideo();
        } else {
            renderer.dispose();
        }
    }
}

Propriétés du flux vidéo distant

Un flux vidéo distant possède plusieurs propriétés

  • Id - ID d’un flux vidéo distant
int id = remoteVideoStream.getId();
  • MediaStreamType – peut être « Video » ou « ScreenSharing »
MediaStreamType type = remoteVideoStream.getMediaStreamType();
  • isAvailable – indique si le point de terminaison du participant distant envoie activement un flux
boolean availability = remoteVideoStream.isAvailable();

Méthodes et propriétés de convertisseur

Objet du convertisseur suivant les API

  • Créez une instance VideoStreamRendererView qui peut être jointe ultérieurement dans l’interface utilisateur de l’application pour afficher le flux vidéo distant.
// Create a view for a video stream
VideoStreamRendererView.createView()
  • Supprimez le convertisseur et tous les VideoStreamRendererView associés à ce convertisseur. À appeler lorsque vous avez supprimé toutes les vues associées de l’interface utilisateur.
VideoStreamRenderer.dispose()
  • StreamSize - taille (largeur/hauteur) d’un flux vidéo distant
StreamSize renderStreamSize = VideoStreamRenderer.getSize();
int width = renderStreamSize.getWidth();
int height = renderStreamSize.getHeight();

Méthodes et propriétés de RendererView

Lors de la création d’un VideoStreamRendererView, vous pouvez spécifier les propriétés ScalingMode et mirrored qui s’appliquent à cette vue : le mode de mise à l’échelle peut être « CROP » ou « FIT ».

VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteVideoStream, appContext);
VideoStreamRendererView rendererView = remoteVideoRenderer.createView(new CreateViewOptions(ScalingMode.Fit));

Le RendererView créé peut alors être attaché à l’interface utilisateur de l’application à l’aide de l’extrait de code suivant :

layout.addView(rendererView);

Vous pouvez modifier le mode de mise à l’échelle ultérieurement en appelant l’API updateScalingMode sur l’objet RendererView avec l’un des arguments suivants : « ScalingMode.CROP » ou « ScalingMode.FIT ».

// Update the scale mode for this view.
rendererView.updateScalingMode(ScalingMode.CROP)

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 le langage sur Swift et l’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. Affectez la valeur associée à une chaîne qui sera incluse dans la boîte de dialogue affiché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")
        }
})

Gérer des unités

Pour commencer à utiliser de la vidéo avec les appels, vous devez savoir comment gérer les appareils. Les appareils vous permettent de contrôler ce que transmet l’audio et la vidéo à l’appel.

DeviceManager permet d’énumérer les appareils locaux qui peuvent être utilisés dans un appel pour transmettre des flux audio ou vidéo. Il vous permet également de demander l’autorisation à un utilisateur d’accéder à un microphone ou à une caméra. Vous pouvez accéder à deviceManager sur l’objet callClient.

self.callClient!.getDeviceManager { (deviceManager, error) in
        if (error == nil) {
            print("Got device manager instance")
            self.deviceManager = deviceManager
        } else {
            print("Failed to get device manager instance")
        }
    }

Énumérer les appareils locaux

Pour accéder aux appareils locaux, vous pouvez utiliser les méthodes d’énumération sur le gestionnaire d’appareils. L’énumération est une action synchrone.

// enumerate local cameras
var localCameras = deviceManager.cameras // [VideoDeviceInfo, VideoDeviceInfo...]

Obtenir l’aperçu de la caméra locale

Vous pouvez utiliser Renderer pour commencer à afficher un flux à partir de votre caméra locale. Ce flux n’est pas envoyé à d’autres participants ; il s’agit d’un flux d’aperçu local. Cette action est asynchrone.

let camera: VideoDeviceInfo = self.deviceManager!.cameras.first!
let localVideoStream = LocalVideoStream(camera: camera)
let localRenderer = try! VideoStreamRenderer(localVideoStream: localVideoStream)
self.view = try! localRenderer.createView()

Obtenir les propriétés de l’aperçu de la caméra locale

Le renderer a un ensemble de propriétés et de méthodes qui vous permettent de contrôler le rendu.

// Constructor can take in LocalVideoStream or RemoteVideoStream
let localRenderer = VideoStreamRenderer(localVideoStream:localVideoStream)
let remoteRenderer = VideoStreamRenderer(remoteVideoStream:remoteVideoStream)

// [StreamSize] size of the rendering view
localRenderer.size

// [VideoStreamRendererDelegate] an object you provide to receive events from this Renderer instance
localRenderer.delegate

// [Synchronous] create view
try! localRenderer.createView()

// [Synchronous] create view with rendering options
try! localRenderer!.createView(withOptions: CreateViewOptions(scalingMode: ScalingMode.fit))

// [Synchronous] dispose rendering view
localRenderer.dispose()

Passer un appel 1:1 avec vidéo

Pour obtenir une instance du gestionnaire d’appareils, consultez la section relative à la gestion des appareils.

let firstCamera = self.deviceManager!.cameras.first
self.localVideoStreams = [LocalVideoStream]()
self.localVideoStreams!.append(LocalVideoStream(camera: firstCamera!))
let videoOptions = VideoOptions(localVideoStreams: self.localVideoStreams!)

let startCallOptions = StartCallOptions()
startCallOptions.videoOptions = videoOptions

let callee = CommunicationUserIdentifier('UserId')
self.callAgent?.startCall(participants: [callee], options: startCallOptions) { (call, error) in
    if error == nil {
        print("Successfully started outgoing video call")
        self.call = call
    } else {
        print("Failed to start outgoing video call")
    }
}

Afficher les flux vidéo des participants distants

Les participants distants peuvent lancer un partage vidéo ou d’écran pendant un appel.

Gérer les flux de partage vidéo ou de partage d’écran de participants distants

Pour lister les flux de participants distants, examinez les collections videoStreams.

var remoteParticipantVideoStream = call.remoteParticipants[0].videoStreams[0]

Obtenir les propriétés du flux vidéo distant

var type: MediaStreamType = remoteParticipantVideoStream.type // 'MediaStreamTypeVideo'
var isAvailable: Bool = remoteParticipantVideoStream.isAvailable // indicates if remote stream is available
var id: Int = remoteParticipantVideoStream.id // id of remoteParticipantStream

Afficher les flux des participants distants

Pour commencer à afficher les flux des participants distants, utilisez le code suivant.

let renderer = VideoStreamRenderer(remoteVideoStream: remoteParticipantVideoStream)
let targetRemoteParticipantView = renderer?.createView(withOptions: CreateViewOptions(scalingMode: ScalingMode.crop))
// To update the scaling mode later
targetRemoteParticipantView.update(scalingMode: ScalingMode.fit)

Obtenir les méthodes et propriétés du renderer vidéo à distance

// [Synchronous] dispose() - dispose renderer and all `RendererView` associated with this renderer. To be called when you have removed all associated views from the UI.
remoteVideoRenderer.dispose()

Configurer votre système

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

Créer le projet Visual Studio

Pour une application de plateforme Windows universelle, dans Visual Studio 2022, créez un projet Application vide (Windows universel). Après avoir entré le nom du projet, n’hésitez pas à choisir un kit de développement logiciel (SDK) Windows d’une version ultérieure à 10.0.17763.0.

Pour une application WinUI 3, créez un projet avec le modèle Application vide, Empaquetée (WinUI 3 dans Desktop) pour configurer une application WinUI 3 monopage. Le SDK d’application Windows version 1.3 ou ultérieure est nécessaire.

Installer le package et les dépendances à l’aide du Gestionnaire de package NuGet

Les API et les bibliothèques du Kit de développement logiciel (SDK) Appel sont accessibles au public via un package NuGet.

Pour rechercher, télécharger et installer le package NuGet du SDK Appel :

  1. Ouvrez le Gestionnaire de package NuGet en sélectionnant Outils>Gestionnaire de package NuGet>Gérer les packages NuGet pour la solution.
  2. Sélectionnez Parcourir, puis entrez Azure.Communication.Calling.WindowsClient dans la zone de recherche.
  3. Vérifiez que la case Inclure la préversion est cochée.
  4. Sélectionnez le package Azure.Communication.Calling.WindowsClient, puis Azure.Communication.Calling.WindowsClient 1.4.0-beta.1 ou une version plus récente.
  5. Cochez la case qui correspond au projet Azure Communication Services dans le volet de droite.
  6. Sélectionnez Installer.

Demander l’accès au microphone

L’application nécessite un accès à la caméra pour s’exécuter correctement. Dans les applications UWP, la capacité de la caméra doit être déclarée dans le fichier manifeste de l’application.

Les étapes suivantes illustrent comment y parvenir.

  1. Dans le panneau Solution Explorer, double-cliquez sur le fichier avec l’extension .appxmanifest.
  2. Cliquez sur l’onglet Capabilities.
  3. Cochez la case Camera dans la liste des capacités.

Créer des boutons d’interface utilisateur pour passer et raccrocher l’appel

Cet exemple d’application simple contient deux boutons. Un pour passer l’appel et une autre pour raccrocher un appel passé. Les étapes suivantes illustrent comment ajouter ces boutons à l’application.

  1. Dans le panneau Solution Explorer, double-cliquez sur le fichier nommé MainPage.xaml pour UWP, ou MainWindows.xaml pour WinUI 3.
  2. Dans le panneau central, recherchez le code XAML sous l’aperçu de l’interface utilisateur.
  3. Modifiez le code XAML en le remplaçant par l’extrait suivant :
<TextBox x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" />
<StackPanel>
    <Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" />
    <Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" />
</StackPanel>

Configuration de l’application avec les API du Kit de développement logiciel (SDK) Appel

Les API du Kit de développement logiciel (SDK) Appel se trouvent dans deux espaces de noms différents. Les étapes suivantes informent le compilateur C# de ces espaces de noms, ce qui permet à IntelliSense de Visual Studio de faciliter le développement du code.

  1. Dans le panneau Solution Explorer, cliquez sur la flèche située sur le côté gauche du fichier nommé MainPage.xaml pour UWP, ou MainWindows.xaml pour WinUI 3.
  2. Double-cliquez sur le fichier nommé MainPage.xaml.cs ou MainWindows.xaml.cs.
  3. Ajoutez les commandes suivantes au bas des instructions using actuelles.
using Azure.Communication.Calling.WindowsClient;

Conservez MainPage.xaml.cs ou MainWindows.xaml.cs ouvert. Les étapes suivantes y ajouteront du code.

Autoriser les interactions avec l’application

Les boutons d’interface utilisateur précédemment ajoutés doivent fonctionner en même temps qu’un CommunicationCall passé. Cela signifie qu’un membre de données CommunicationCall doit être ajouté à la classe MainPage ou MainWindow. En outre, pour permettre à l’opération asynchrone qui crée CallAgent de réussir, un membre de données CallAgent doit également être ajouté à la même classe.

Ajoutez les membres de données suivants à la classe MainPage ou MainWindow :

CallAgent callAgent;
CommunicationCall call;

Créer des gestionnaires de bouton

Précédemment, deux boutons d’interface utilisateur ont été ajoutés au code XAML. Le code suivant ajoute les gestionnaires à exécuter lorsqu’un utilisateur sélectionne le bouton. Le code suivant doit être ajouté après les membres de données de la section précédente.

private async void CallButton_Click(object sender, RoutedEventArgs e)
{
    // Start call
}

private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
    // End the current call
}

Modèle objet

Les classes et les interfaces suivantes gèrent certaines des principales fonctionnalités de la bibliothèque de client Appel Azure Communication Services pour UWP.

Nom Description
CallClient CallClient est le point d’entrée principal de la bibliothèque d’appels de client.
CallAgent CallAgent sert à lancer et à joindre des appels.
CommunicationCall CommunicationCall sert à gérer les appels passés ou rejoints.
CommunicationTokenCredential CommunicationTokenCredential sert de jeton d’informations d'identification pour initier le CallAgent.
CallAgentOptions Le CallAgentOptions contient des informations pour identifier l’appelant.
HangupOptions Le HangupOptions informe si un appel doit être arrêté à tous ses participants.

Enregistrer le gestionnaire de schéma vidéo

Un composant d’interface utilisateur, comme MediaElement ou MediaPlayerElement de XAML, vous avez besoin que l’application enregistre une configuration pour le rendu des flux vidéo locaux et distants. Ajoutez le contenu suivant entre les balises Package du Package.appxmanifest :

<Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
        <InProcessServer>
            <Path>RtmMvrUap.dll</Path>
            <ActivatableClass ActivatableClassId="VideoN.VideoSchemeHandler" ThreadingModel="both" />
        </InProcessServer>
    </Extension>
</Extensions>

Initialiser CallAgent

Pour créer une instance CallAgent à partir de CallClient, vous devez utiliser la méthode CallClient.CreateCallAgentAsync qui retourne de façon asynchrone un objet CallAgent une fois qu’il est initialisé.

Pour créer CallAgent, vous devez transmettre un objet CallTokenCredential et un objet CallAgentOptions. Gardez à l’esprit que CallTokenCredential lève une exception si un jeton malformé est transmis.

Vous devez ajouter le code suivant dans une fonction d’assistance à appeler au cours de l’initialisation de l’application.

var callClient = new CallClient();
this.deviceManager = await callClient.GetDeviceManagerAsync();

var tokenCredential = new CallTokenCredential("<AUTHENTICATION_TOKEN>");
var callAgentOptions = new CallAgentOptions()
{
    DisplayName = "<DISPLAY_NAME>"
};

this.callAgent = await callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);
this.callAgent.CallsUpdated += Agent_OnCallsUpdatedAsync;
this.callAgent.IncomingCallReceived += Agent_OnIncomingCallAsync;

Modifiez le <AUTHENTICATION_TOKEN> avec un jeton d’informations d’identification valide pour votre ressource. Référez-vous à la documentation relative aux jetons d’accès utilisateur si un jeton d’informations d’identification doit être sourcé.

Passer un appel 1:1 avec une caméra vidéo

Les objets nécessaires à la création d’un objet CallAgent sont maintenant prêts. Il est temps de créer CallAgent de manière asynchrone et de passer un appel vidéo.

private async void CallButton_Click(object sender, RoutedEventArgs e)
{
    var callString = CalleeTextBox.Text.Trim();

    if (!string.IsNullOrEmpty(callString))
    {
        if (callString.StartsWith("8:")) // 1:1 Azure Communication Services call
        {
            this.call = await StartAcsCallAsync(callString);
        }
    }

    if (this.call != null)
    {
        this.call.RemoteParticipantsUpdated += OnRemoteParticipantsUpdatedAsync;
        this.call.StateChanged += OnStateChangedAsync;
    }
}

private async Task<CommunicationCall> StartAcsCallAsync(string acsCallee)
{
    var options = await GetStartCallOptionsAsynnc();
    var call = await this.callAgent.StartCallAsync( new [] { new UserCallIdentifier(acsCallee) }, options);
    return call;
}

var micStream = new LocalOutgoingAudioStream(); // Create a default local audio stream
var cameraStream = new LocalOutgoingVideoStreamde(this.viceManager.Cameras.FirstOrDefault() as VideoDeviceDetails); // Create a default video stream

private async Task<StartCallOptions> GetStartCallOptionsAsynnc()
{
    return new StartCallOptions() {
        OutgoingAudioOptions = new OutgoingAudioOptions() { IsMuted = true, Stream = micStream  },
        OutgoingVideoOptions = new OutgoingVideoOptions() { Streams = new OutgoingVideoStream[] { cameraStream } }
    };
}

Aperçu de la caméra locale

Nous pouvons éventuellement configurer l’aperçu de la caméra locale. La vidéo peut être affichée par le biais du composant MediaPlayerElement :

<Grid>
    <MediaPlayerElement x:Name="LocalVideo" AutoPlay="True" />
    <MediaPlayerElement x:Name="RemoteVideo" AutoPlay="True" />
</Grid>

Pour initialiser le composant MediaPlayerElement de l’aperçu local :

private async void CameraList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (cameraStream != null)
    {
        await cameraStream?.StopPreviewAsync();
        if (this.call != null)
        {
            await this.call?.StopVideoAsync(cameraStream);
        }
    }
    var selectedCamerea = CameraList.SelectedItem as VideoDeviceDetails;
    cameraStream = new LocalOutgoingVideoStream(selectedCamerea);

    var localUri = await cameraStream.StartPreviewAsync();
    LocalVideo.Source = MediaSource.CreateFromUri(localUri);

    if (this.call != null) {
        await this.call?.StartVideoAsync(cameraStream);
    }
}

Afficher le flux de la caméra à distance

Configurez le gestionnaire d’événements en réponse à l’événement OnCallsUpdated :

private async void OnCallsUpdatedAsync(object sender, CallsUpdatedEventArgs args)
{
    var removedParticipants = new List<RemoteParticipant>();
    var addedParticipants = new List<RemoteParticipant>();

    foreach(var call in args.RemovedCalls)
    {
        removedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    foreach (var call in args.AddedCalls)
    {
        addedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    await OnParticipantChangedAsync(removedParticipants, addedParticipants);
}

private async void OnRemoteParticipantsUpdatedAsync(object sender, ParticipantsUpdatedEventArgs args)
{
    await OnParticipantChangedAsync(
        args.RemovedParticipants.ToList<RemoteParticipant>(),
        args.AddedParticipants.ToList<RemoteParticipant>());
}

private async Task OnParticipantChangedAsync(IEnumerable<RemoteParticipant> removedParticipants, IEnumerable<RemoteParticipant> addedParticipants)
{
    foreach (var participant in removedParticipants)
    {
        foreach(var incomingVideoStream in  participant.IncomingVideoStreams)
        {
            var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
            if (remoteVideoStream != null)
            {
                await remoteVideoStream.StopPreviewAsync();
            }
        }
        participant.VideoStreamStateChanged -= OnVideoStreamStateChanged;
    }

    foreach (var participant in addedParticipants)
    {
        participant.VideoStreamStateChanged += OnVideoStreamStateChanged;
    }
}

private void OnVideoStreamStateChanged(object sender, VideoStreamStateChangedEventArgs e)
{
    CallVideoStream callVideoStream = e.CallVideoStream;

    switch (callVideoStream.StreamDirection)
    {
        case StreamDirection.Outgoing:
            OnOutgoingVideoStreamStateChanged(callVideoStream as OutgoingVideoStream);
            break;
        case StreamDirection.Incoming:
            OnIncomingVideoStreamStateChanged(callVideoStream as IncomingVideoStream);
            break;
    }
}

Démarrez l’affichage du flux vidéo distant sur MediaPlayerElement :

private async void OnIncomingVideoStreamStateChanged(IncomingVideoStream incomingVideoStream)
{
    switch (incomingVideoStream.State)
    {
        case VideoStreamState.Available:
            {
                switch (incomingVideoStream.Kind)
                {
                    case VideoStreamKind.RemoteIncoming:
                        var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                        var uri = await remoteVideoStream.StartPreviewAsync();

                        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                        {
                            RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                        });

                        /* Or WinUI 3
                        this.DispatcherQueue.TryEnqueue(() => {
                            RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                            RemoteVideo.MediaPlayer.Play();
                        });
                        */

                        break;

                    case VideoStreamKind.RawIncoming:
                        break;
                }

                break;
            }
        case VideoStreamState.Started:
            break;
        case VideoStreamState.Stopping:
            break;
        case VideoStreamState.Stopped:
            if (incomingVideoStream.Kind == VideoStreamKind.RemoteIncoming)
            {
                var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                await remoteVideoStream.StopPreviewAsync();
            }
            break;
        case VideoStreamState.NotAvailable:
            break;
    }
}

Terminer un appel

Une fois qu’un appel est passé, la méthode HangupAsync de l’objet CommunicationCall doit être utilisée pour raccrocher l’appel.

Une instance HangupOptions doit également être utilisée pour indiquer à tous les participants si l’appel doit être interrompu.

Le code suivant doit être ajouté dans HangupButton_Click.

var call = this.callAgent?.Calls?.FirstOrDefault();
if (call != null)
{
    var call = this.callAgent?.Calls?.FirstOrDefault();
    if (call != null)
    {
        foreach (var localVideoStream in call.OutgoingVideoStreams)
        {
            await call.StopVideoAsync(localVideoStream);
        }

        try
        {
            if (cameraStream != null)
            {
                await cameraStream.StopPreviewAsync();
            }

            await call.HangUpAsync(new HangUpOptions() { ForEveryone = false });
        }
        catch(Exception ex) 
        { 
            var errorCode = unchecked((int)(0x0000FFFFU & ex.HResult));
            if (errorCode != 98) // Sample error code, sam_status_failed_to_hangup_for_everyone (98)
            {
                throw;
            }
        }
    }
}

Exécuter le code

Vérifiez que Visual Studio génère l’application pour x64, x86 ou ARM64, puis appuyez sur F5 pour commencer à exécuter l’application. Après cela, cliquez sur le bouton CommunicationCall pour passer un appel à l’appelé défini.

N’oubliez pas que la première fois que l’application s’exécute, le système invite l’utilisateur à accorder l’accès au microphone.

Étapes suivantes