Condividi tramite


Gestire i video durante le chiamate

Informazioni su come gestire le videochiamate con gli SDK di Servizi di comunicazione di Azure. Saranno fornite informazioni su come gestire la ricezione e l'invio di video nell'ambito di una chiamata.

Prerequisiti

Installazione dell'SDK

Usare il comando npm install per installare SDK comuni e di chiamata di Servizi di comunicazione di Azure per JavaScript:

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

Inizializzare gli oggetti necessari

Per la maggior parte delle operazioni di chiamata è necessaria un'istanza di CallClient. Quando si crea una nuova istanza di CallClient, è possibile configurarla con opzioni personalizzate come un'istanza di Logger.

Con l'istanza di CallClient è possibile creare un'istanza di CallAgent chiamando il createCallAgent. Questo metodo restituisce in modo asincrono un oggetto istanza CallAgent.

Il metodo createCallAgent usa CommunicationTokenCredential come argomento. Accetta un token di accesso utente.

È possibile usare il metodo getDeviceManager nell'istanza di CallClient per accedere a 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()

Come gestire al meglio la connettività dell'SDK all'infrastruttura Microsoft

L'istanza Call Agent consente di gestire le chiamate (per partecipare o avviare le chiamate). Per lavorare con l'SDK per chiamate, è necessario connettersi all'infrastruttura Microsoft per ricevere notifiche delle chiamate in arrivo e coordinare altri dettagli delle chiamate. Call Agent ha due possibili stati:

Connesso : un valore connectionStatue Call Agent di Connected indica che l'SDK client è connesso e in grado di ricevere notifiche dall'infrastruttura Microsoft.

Disconnesso : un valore connectionStatue Call Agent di Disconnected indica un problema che impedisce all'SDK di connettersi correttamente. Call Agent deve essere ricreato.

  • invalidToken: se un token è scaduto o non è valido l'istanza Call Agent si disconnette con questo errore.
  • connectionIssue: se si verifica un problema con il client che si connette a Microsoft infrascture, dopo molti tentativi Call Agent espone l'errore connectionIssue.

È possibile verificare se l'infrastruttura locale Call Agent è connessa all'infrastruttura Microsoft controllando il valore corrente della proprietà connectionState. Durante una chiamata attiva è possibile restare in ascolto dell'evento connectionStateChanged per determinare se Call Agent cambia dallo stato Connesso a Disconnesso.

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

Gestione dispositivi

Per iniziare a usare i video con l'SDK per chiamate, è necessario essere in grado di gestire i dispositivi. I dispositivi permettono di controllare cosa trasmette contenuti audio e video alla chiamata.

Grazie a deviceManager è possibile enumerare i dispositivi locali in grado di trasmettere i flussi audio e video in una chiamata. Inoltre, è possibile usare deviceManager per richiedere l'autorizzazione di accesso ai microfoni e alle fotocamere del dispositivo locale.

È possibile accedere a deviceManager chiamando il metodo callClient.getDeviceManager():

const deviceManager = await callClient.getDeviceManager();

Ottenere i dispositivi locali

Per accedere ai dispositivi locali, è possibile usare i metodi di enumerazione deviceManager getCameras() e getMicrophones. Questi metodi sono azioni asincrone.

//  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...]

Impostare i dispositivi predefiniti

Dopo aver compreso quali dispositivi sono disponibili per l'uso, è possibile impostare i dispositivi predefiniti per il microfono, l'altoparlante e la fotocamera. Se non sono presenti le impostazioni predefinite del client, l'SDK di Servizi di comunicazione di Azure applicherà le impostazioni predefinite del sistema operativo.

Microphone

Accedere al dispositivo utilizzato

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

Impostazione del dispositivo da utilizzare

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

Relatore

Accedere al dispositivo utilizzato

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

Impostazione del dispositivo da utilizzare

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

Fotocamera

Accedere al dispositivo utilizzato

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

Impostazione del dispositivo da utilizzare

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

Ogni CallAgent può scegliere il proprio microfono e i propri altoparlanti sul DeviceManager associato. È consigliabile che i CallAgents utilizzino microfoni e altoparlanti diversi. Essi non devono condividere gli stessi microfoni e altoparlanti. In caso di condivisione, potrebbe essere attivata la diagnostica rivolta all'utente del microfono e, di conseguenza, il microfono smetterà di funzionare, a seconda del browser o del sistema operativo.

Flusso video locale

Per poter inviare video in una chiamata, è necessario creare un oggetto LocalVideoStream.

const localVideoStream = new LocalVideoStream(camera);

La fotocamera passata come parametro è uno degli oggetti VideoDeviceInfo restituiti dal metodo deviceManager.getCameras().

Un oggetto LocalVideoStream ha le proprietà seguenti:

  • source: informazioni sul dispositivo.
const source = localVideoStream.source;
  • mediaStreamType: può essere Video, ScreenSharing o RawMedia.
const type: MediaStreamType = localVideoStream.mediaStreamType;

Anteprima della fotocamera locale

È possibile usare deviceManager e VideoStreamRenderer per avviare il rendering dei flussi dalla fotocamera locale. Dopo aver creato un oggetto LocalVideoStream, usarlo per configurare VideoStreamRenderer. Dopo aver creato VideoStreamRenderer chiamare il relativo metodo createView() per ottenere una visualizzazione che si potrà aggiungere alla pagina come elemento figlio.

Questo flusso non viene inviato ad altri partecipanti; si tratta di un feed di anteprima locale.

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

Interrompere l'anteprima locale

Per interrompere la chiamata di anteprima locale, eliminare la visualizzazione derivata da VideoStreamRenderer. Dopo aver eliminato VideoStreamRenderer, rimuovere la visualizzazione dall'albero html chiamando il metodo removeChild() dal nodo DOM contenente l'anteprima.

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

Richiedere l'autorizzazione per la fotocamera e il microfono

Un'applicazione non può usare la fotocamera o il microfono senza autorizzazioni. È possibile usare deviceManager per richiedere a un utente di concedere le autorizzazioni per la fotocamera e/o il microfono:

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

Una volta risolto l'oggetto Promise, il metodo restituisce un oggetto DeviceAccess che indica se sono state concesse le autorizzazioni audio e video:

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

Note

  • L'evento videoDevicesUpdated viene generato quando i dispositivi video si collegano/vengono scollegati.
  • L'evento audioDevicesUpdated viene generato quando i dispositivi audio sono collegati.
  • Quando si crea DeviceManager, inizialmente non riconosce alcun dispositivo se le autorizzazioni non sono ancora state concesse, pertanto, inizialmente il nome del dispositivo è vuoto e non contiene informazioni dettagliate sul dispositivo. Se poi si chiama l'API DeviceManager.askPermission(), all'utente viene richiesto l'accesso al dispositivo. Quando l'utente seleziona "consenti" per concedere l'accesso, Gestione dispositivi acquisisce informazioni sui dispositivi nel sistema, aggiorna gli elenchi dei dispositivi e genera gli eventi "audioDevicesUpdated" e "videoDevicesUpdated". Se un utente aggiorna la pagina e crea una Gestione dispositivi, questa sarà in grado di ottenere informazioni sui dispositivi perché l'utente aveva concesso l'accesso in precedenza. Essa ha degli elenchi di dispositivi compilati inizialmente e non genera gli eventi 'audioDevicesUpdated' e 'videoDevicesUpdated'.
  • L'enumerazione/selezione dell'altoparlante non è supportata in Android Chrome, iOS Safari o macOS Safari.

Effettuare una chiamata con videocamera

Importante

Attualmente è supportato un solo flusso video locale in uscita.

Per effettuare una videochiamata, è necessario enumerare le fotocamere locali tramite il metodo getCameras() in deviceManager.

Dopo avere selezionato una fotocamera, utilizzarla per creare un'istanza LocalVideoStream. Passarla in videoOptions come elemento all’interno della matrice localVideoStream al metodo 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);
  • È anche possibile partecipare a una chiamata con video con l'API CallAgent.join() ed effettuare e accettare una chiamata con video con l'API Call.Accept().
  • Quando la chiamata si connette, avvia automaticamente l'invio di un flusso video dalla fotocamera selezionata all'altro partecipante.

Avviare e interrompere l'invio di un video locale durante una chiamata

Avvia video

Per avviare un video durante una chiamata, è necessario enumerare le fotocamere usando il metodo getCameras sull'oggetto deviceManager. Quindi si dovrà creare una nuova istanza di LocalVideoStream con la fotocamera desiderata e passare l'oggetto LocalVideoStream al metodo startVideo di un oggetto esistente della chiamata:

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

Interrompere un video

Dopo aver avviato correttamente l'invio del video, viene aggiunta un'istanza LocalVideoStream di tipo Video alla raccolta localVideoStreams in un'istanza di chiamata.

Trovare il flusso video nell'oggetto Call

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

Interrompere il video locale Per interrompere il video locale durante una chiamata, passare l'istanza localVideoStream usata per il video al metodo stopVideo di Call:

await call.stopVideo(localVideoStream);

È possibile passare a un dispositivo fotocamera diverso quando si ha un oggetto LocalVideoStream attivo richiamando switchSource su tale istanza LocalVideoStream:

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

Se il dispositivo video specificato non è disponibile:

  • Durante una chiamata, se il video non è attivo e si avvia il video usando call.startVideo(), questo metodo genera SourceUnavailableError e cameraStartFailed, quindi la diagnostica rivolta all'utente viene impostata su true.
  • Una chiamata al metodo localVideoStream.switchSource() determina l'impostazione di cameraStartFailed su true. La guida alla Diagnostica delle chiamate fornisce informazioni aggiuntive su come eseguire la diagnostica dei problemi correlati alle chiamate.

Per verificare se il video locale è attivo o non attivo, è possibile usare il metodo Call isLocalVideoStarted, che restituisce true o false:

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

Per ascoltare le modifiche apportate al video locale, è possibile sottoscrivere e annullare la sottoscrizione all'evento isLocalVideoStartedChanged:

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

Avviare e interrompere la condivisione dello schermo durante una chiamata

Per avviare la condivisione dello schermo durante una chiamata, è possibile usare il metodo asincrono startScreenSharing() in un oggetto Call:

Avviare la condivisione dello schermo

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

Nota: l'invio di screenshare è supportato soltanto nel browser desktop.

Trovare la condivisione dello schermo nella raccolta di LocalVideoStream

Dopo aver avviato correttamente l'invio della condivisione dello schermo, viene aggiunta un'istanza LocalVideoStream di tipo ScreenSharing alla raccolta localVideoStreams nell'istanza di chiamata.

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

Interrompere la condivisione dello schermo

Per interrompere la condivisione dello schermo durante una chiamata, è possibile usare stoptScreenSharing dell'API asincrona:

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

Controllare lo stato della condivisione dello schermo

Per verificare se la condivisione dello schermo è attivata o disattivata, è possibile usare l'API isScreenSharingOn, che restituisce true o false:

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

Per ascoltare le modifiche apportate alla condivisione dello schermo, è possibile sottoscrivere e annullare la sottoscrizione all'evento isScreenSharingOnChanged:

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

Importante

Questa funzionalità di Servizi di comunicazione di Azure è attualmente in anteprima.

Le anteprime di API e SDK vengono fornite senza un contratto di servizio. È consigliabile non usarle per carichi di lavoro di produzione. Alcune funzionalità potrebbero non essere supportate o risultare limitate.

Per altre informazioni, vedere le Condizioni per l'utilizzo supplementari delle anteprime di Microsoft Azure.

L'anteprima locale della condivisione dello schermo è disponibile in anteprima pubblica e disponibile come parte della versione 1.15.1-beta.1+.

Anteprima locale della condivisione dello schermo

È possibile usare un oggetto VideoStreamRenderer per avviare il rendering dei flussi dalla condivisione dello schermo locale, affinché sia possibile visualizzare gli elementi inviati come flusso di condivisione dello schermo.

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

Eseguire il rendering dei flussi video/di condivisione dello schermo dei partecipanti remoti

Per eseguire il rendering di un video o una condivisione dello schermo di un partecipante remoto, il primo passaggio consiste nell'ottenere un riferimento del RemoteVideoStream di cui si desidera eseguire il rendering. A tale scopo, è possibile passare attraverso la matrice o il flusso video (videoStreams) di RemoteParticipant. L'insieme di dati dei partecipanti remoti è accessibile tramite l'oggetto Call.

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

Per eseguire il rendering di RemoteVideoStream, è necessario effettuare la sottoscrizione all'evento isAvailableChanged. Se la proprietà isAvailable viene modificata in true, un partecipante remoto sta inviando un flusso video. Successivamente, creare una nuova istanza di VideoStreamRenderer, quindi creare una nuova istanza VideoStreamRendererView usando il metodo asincrono createView.
A questo punto è possibile rendere visibile view.target a qualsiasi elemento dell'interfaccia utente.

Ogni volta che cambia la disponibilità di un flusso remoto, è possibile eliminare l'intero oggetto VideoStreamRenderer o un oggetto specifico VideoStreamRendererView. Se si decide di mantenerli, la visualizzazione mostra un fotogramma video vuoto.

// 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 per applicare uno stile alla rotellina di caricamento sul flusso video remoto.

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

Qualità del video remoto

L'SDK WebJS di Servizi di comunicazione di Azure offre una funzionalità denominata OVC (Optimal Video Count), a partire dalla versione 1.15.1. Questa funzionalità può essere usata per informare le applicazioni in fase di esecuzione sul numero di video in ingresso provenienti da partecipanti diversi, per consentire il rendering ottimale in un determinato momento durante una chiamata di gruppo (2 o più partecipanti). Questa funzionalità espone una proprietà optimalVideoCount che cambia in modo dinamico durante la chiamata, in base alle capacità di rete e hardware di un endpoint locale. Il valore di optimalVideoCount fornisce i dettagli sul numero di video provenienti da un'applicazione partecipante diversa dovrebbe essere oggetto di rendering in un determinato momento. Le applicazioni devono gestire queste modifiche e aggiornare il numero di video di cui è stato eseguito il rendering, come conseguenza di tale raccomandazione. Tra gli aggiornamenti è previsto un periodo di debounce (circa 10 s).

Utilizzo La funzionalità optimalVideoCount è una funzione di chiamata. È necessario fare riferimento alla funzionalità OptimalVideoCount tramite il metodo feature dell'oggetto Call. È quindi possibile impostare un listener tramite il metodo on di OptimalVideoCountCallFeature per ricevere una notifica quando si verifica un cambiamento in optimalVideoCount. Per annullare la sottoscrizione alle modifiche, è possibile chiamare il metodo off. Attualmente il numero massimo di video in ingresso di cui è possibile eseguire il rendering è 16. Per supportare correttamente 16 video in ingresso, il computer deve avere almeno 16 GB di RAM e una CPU 4 core o superiore, che non abbia più di 3 anni.

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

Utilizzo di esempio: l'applicazione deve effettuare la sottoscrizione alle modifiche di OVC (Optimal Video Count) nelle chiamate di gruppo. Una modifica del numero ottimale di video può essere gestita creando un nuovo renderer (metodo createView) o eliminando le visualizzazioni (dispose), quindi aggiornando di conseguenza il layout dell'applicazione.

Proprietà del flusso video remoto

I flussi video remoti hanno le proprietà seguenti:

const id: number = remoteVideoStream.id;
  • id: ID di un flusso video remoto.
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
  • mediaStreamType: può essere Video o ScreenSharing.
const isAvailable: boolean = remoteVideoStream.isAvailable;
  • isAvailable: definisce se l'endpoint di un partecipante remoto sta inviando attivamente un flusso.
const isReceiving: boolean = remoteVideoStream.isReceiving;
  • isReceiving:
    • informa l'applicazione sull'effettiva ricezione dei dati del flusso video remoto.

    • Il flag diventa false negli scenari seguenti:

      • Un partecipante remoto nel browser per dispositivi mobili porta l'app del browser in background.
      • Un partecipante remoto o l'utente che riceve il video ha problemi di rete che influiscono drasticamente sulla qualità video.
      • Un partecipante remoto su macOS/iOS Safari seleziona "Sospendi" dalla barra di navigazione.
      • Un partecipante remoto subisce una disconnessione dalla rete.
      • Un partecipante remoto su un dispositivo mobile termina o arresta il browser.
      • Un partecipante remoto su un dispositive mobile o su desktop blocca il dispositivo. Questo scenario è applicabile anche se il partecipante remoto opera su un computer desktop che va in modalità sospensione.
    • Il flag diventa true negli scenari seguenti:

      • Un partecipante remoto nel browser per dispositivi mobili che ha il browser in background lo riporta in primo piano.
      • Un partecipante remoto su macOS/iOS Safari seleziona "Riprendi" dalla barra di navigazione dopo aver messo in pausa il video.
      • Un partecipante remoto si ricollega alla rete dopo una disconnessione temporanea.
      • Un partecipante remoto su un dispositivo mobile sblocca il dispositivo e torna alla chiamata nel browser per dispositivi mobili.
    • Questa funzionalità migliora l'esperienza utente per il rendering di flussi video remoti.

    • È possibile visualizzare la rotellina di caricamento sul flusso video remoto quando il flag isReceiving diventa false. Non è necessario implementare la rotellina di caricamento, tuttavia è il metodo più utilizzato per ottenere una migliore esperienza utente.

const size: StreamSize = remoteVideoStream.size;
  • size: dimensioni del flusso con informazioni su larghezza e altezza del video.

Metodi e proprietà di VideoStreamRenderer

await videoStreamRenderer.createView();

Creare un'istanza VideoStreamRendererView che può essere collegata nell'interfaccia utente dell'applicazione per eseguire il rendering del flusso video remoto, usare il metodo asincrono createView(), esso viene risolto quando il flusso è pronto per il rendering e restituisce un oggetto con la proprietà target che rappresenta l'elemento video che può essere inserito ovunque nell'albero DOM.

videoStreamRenderer.dispose();

Eliminare videoStreamRenderer e tutte le istanze VideoStreamRendererView associate.

Metodi e proprietà di VideoStreamRendererView

Quando si crea un oggetto VideoStreamRendererView, è possibile specificare le proprietà scalingMode e isMirrored. scalingMode può essere Stretch, Cropo Fit. Se viene specificato isMirrored, il flusso sottoposto a rendering viene capovolto verticalmente.

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

Ogni istanza VideoStreamRendererView ha una proprietà target che rappresenta la superficie di rendering. Rendere visibile questa proprietà nell'interfaccia utente dell'applicazione:

htmlElement.appendChild(view.target);

È possibile eseguire l'aggiornamento di scalingMode richiamando il metodo updateScalingMode:

view.updateScalingMode('Crop');

L'invio di flussi video da due fotocamere diverse, nella stessa chiamata dallo stesso dispositivo desktop.

Importante

Questa funzionalità di Servizi di comunicazione di Azure è attualmente in anteprima.

Le anteprime di API e SDK vengono fornite senza un contratto di servizio. È consigliabile non usarle per carichi di lavoro di produzione. Alcune funzionalità potrebbero non essere supportate o risultare limitate.

Per altre informazioni, vedere le Condizioni per l'utilizzo supplementari delle anteprime di Microsoft Azure.

L'invio di flussi video da due fotocamere diverse nella stessa chiamata è supportato come parte della versione 1.17.1-beta.1+ nei browser desktop supportati.

  • È possibile inviare flussi video da due fotocamere diverse da una singola scheda/app del browser desktop, nella stessa chiamata, con il frammento di codice seguente:
// 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();

Limitazioni:

  • Questa operazione deve essere eseguita con due istanze CallAgent diverse usando identità diverse. Il frammento di codice mostra i due operatori utilizzati, ciascuno con il proprio oggetto Call.
  • Nell'esempio di codice, i CallAgents partecipano entrambi alla stessa chiamata (stessi ID chiamata). È anche possibile partecipare a chiamate diverse con ogni agente e inviare un video su una chiamata e un video diverso sull'altra chiamata.
  • L'invio della stessa fotocamera in entrambi i CallAgent non è supportato. Devono essere due fotocamere diverse.
  • L'invio di due fotocamere diverse con un solo CallAgent non è attualmente supportato.
  • In Safari macOS, effetti video di sfocatura dello sfondo (da @azure/communication-effects), possono essere applicati solo a una fotocamera e non a entrambe contemporaneamente).

Installazione dell'SDK

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

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

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

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

Inizializzare gli oggetti necessari

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

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

Per accedere a DeviceManager, è prima necessario creare un'istanza callAgent. Quindi è possibile usare il metodo CallClient.getDeviceManager per ottenere DeviceManager.

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

Per impostare nome visualizzato per il chiamante, usare questo metodo alternativo:

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

Gestione dispositivi

Per iniziare a usare video con la chiamata, è necessario sapere come gestire i dispositivi. I dispositivi permettono di controllare cosa trasmette contenuti audio e video alla chiamata.

DeviceManager consente di enumerare i dispositivi locali che possono essere usati in una chiamata per trasmettere flussi audio/video. Inoltre, consente di richiedere l'autorizzazione da un utente per accedere al microfono e alla fotocamera, usando l'API del browser nativo.

È possibile accedere a deviceManager chiamando il metodo callClient.getDeviceManager().

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

Enumerare i dispositivi locali

Per accedere ai dispositivi locali, è possibile usare i metodi di enumerazione in Gestione dispositivi. L'enumerazione è un'azione sincrona.

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

Anteprima della fotocamera locale

È possibile usare DeviceManager e Renderer per avviare il rendering dei flussi dalla fotocamera locale. Questo flusso non sarà inviato ad altri partecipanti; è un feed di anteprima locale. Questa è un'azione asincrona.

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

Effettuare una chiamata 1:1 con la videocamera

Avviso

Attualmente è supportato un solo flusso video locale in uscita Per effettuare una chiamata video è necessario enumerare le fotocamere locali usando l'API deviceManager getCameras. Dopo aver selezionato una fotocamera desiderata, usarla per creare un'istanza LocalVideoStream e passarla in videoOptions come elemento nella matrice localVideoStream per un metodo call. Quando la chiamata si connette, avvia automaticamente l'invio di un flusso video dalla fotocamera selezionata all'altro partecipante.

Nota

A causa di problemi di privacy, il video non verrà condiviso durante la chiamata se non viene visualizzato in anteprima in locale. Per altri dettagli, vedere Anteprima di Fotocamera 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);

Avviare e interrompere l'invio di un video locale

Per avviare un video, è necessario enumerare le fotocamere usando l'API getCameraList sull'oggetto deviceManager. Quindi, creare una nuova istanza di LocalVideoStream passando la fotocamera desiderata, poi passarla nell'API startVideo come argomento:

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

Dopo aver avviato correttamente l'invio del video, un'istanza LocalVideoStream verrà aggiunta alla raccolta localVideoStreams nell'istanza di chiamata.

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

Per interrompere il video locale, passare l'istanza LocalVideoStream disponibile nella raccolta localVideoStreams:

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

È possibile passare a un dispositivo fotocamera diverso durante l'invio del video richiamando switchSource su un'istanza LocalVideoStream:

currentLocalVideoStream.switchSource(source).get();

Eseguire il rendering dei flussi video dei partecipanti remoti

Per elencare i flussi video e i flussi di condivisione dello schermo dei partecipanti remoti, esaminare le raccolte 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

Per eseguire il rendering di un oggetto RemoteVideoStream di un partecipante remoto, è necessario effettuare la sottoscrizione a un evento OnVideoStreamsUpdated.

All'interno dell'evento, il cambiamento della proprietà isAvailable in true indica che il partecipante remoto sta attualmente inviando un flusso. In questo caso, creare una nuova istanza di un oggetto Renderer, quindi creare un nuovo oggetto RendererView usando l'API asincrona createView e rendere visibile view.target ovunque nell'interfaccia utente dell'applicazione.

Ogni volta che la disponibilità di un flusso remoto cambia, è possibile scegliere di eliminare definitivamente l'intero renderer, un elemento RendererView specifico, oppure di mantenerli, tuttavia ciò comporterà la visualizzazione di un fotogramma video vuoto.

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

Proprietà del flusso video remoto

Il flusso video remoto possiede alcune proprietà

  • Id - ID di un flusso video remoto
int id = remoteVideoStream.getId();
  • MediaStreamType - Può essere 'Video' o 'ScreenSharing'
MediaStreamType type = remoteVideoStream.getMediaStreamType();
  • isAvailable - Indica se l'endpoint del partecipante remoto sta inviando attivamente un flusso
boolean availability = remoteVideoStream.isAvailable();

Metodi e proprietà del renderer

Oggetto renderer che segue le API

  • Creare un'istanza VideoStreamRendererView che può essere resa visibile in un secondo momento nell'interfaccia utente dell'applicazione per eseguire il rendering del flusso video remoto.
// Create a view for a video stream
VideoStreamRendererView.createView()
  • Eliminare il renderer e ogni VideoStreamRendererView associata a questo renderer. Essere chiamati quando sono state rimosse tutte le visualizzazioni associate dall'interfaccia utente.
VideoStreamRenderer.dispose()
  • StreamSize - dimensioni (larghezza/altezza) di un flusso video remoto
StreamSize renderStreamSize = VideoStreamRenderer.getSize();
int width = renderStreamSize.getWidth();
int height = renderStreamSize.getHeight();

Metodi e proprietà di RendererView

Quando si crea un oggetto VideoStreamRendererView è possibile specificare le proprietà ScalingMode e mirrored che saranno applicate a questa visualizzazione. La modalità di ridimensionamento può essere una delle proprietà 'CROP' | 'FIT'

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

L'oggetto RendererView creato può essere reso visibile per l'applicazione dell'interfaccia utente utilizzando il seguente frammento di codice:

layout.addView(rendererView);

In seguito sarà possibile aggiornare la modalità di ridimensionamento richiamando l'API updateScalingMode nell'oggetto RendererView con ScalingMode.CROP | ScalingMode.FIT come argomento.

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

Configurare il sistema

Seguire questa procedura per configurare il sistema.

Creare il progetto Xcode

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

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

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

Installare il pacchetto e le dipendenze usando CocoaPods

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

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

  3. Aprire .xcworkspace con Xcode.

Richiedere l'accesso al microfono

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

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

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

Configurare il framework dell'app

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

import AzureCommunicationCalling
import AVFoundation

Inizializzare CallAgent

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

Per creare un client di chiamata, passare un oggetto CommunicationTokenCredential:

import AzureCommunication

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

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

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

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

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

Gestire i dispositivi

Per iniziare a usare video con la chiamata, è necessario sapere come gestire i dispositivi. I dispositivi permettono di controllare cosa trasmette contenuti audio e video alla chiamata.

DeviceManager consente di enumerare i dispositivi locali che possono essere usati in una chiamata per trasmettere flussi audio o video. Consente inoltre di richiedere l'autorizzazione da un utente per accedere al microfono o alla fotocamera. È possibile accedere a deviceManager nell'oggetto 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")
        }
    }

Enumerare i dispositivi locali

Per accedere ai dispositivi locali, è possibile usare i metodi di enumerazione in Gestione dispositivi. L'enumerazione è un'azione sincrona.

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

Ottenere un'anteprima della fotocamera locale

È possibile usare Renderer per iniziare a eseguire il rendering di un flusso dalla fotocamera locale. Questo flusso non sarà inviato ad altri partecipanti; è un feed di anteprima locale. Questa è un'azione asincrona.

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

Ottenere le proprietà di anteprima della fotocamera locale

Il renderer include un set di proprietà e metodi che consentono di controllare il rendering.

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

Effettuare una chiamata 1:1 con video

Per ottenere un'istanza di Gestione dispositivi, vedere la sezione relativa alla Gestione dispositivi.

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

Eseguire il rendering dei flussi video dei partecipanti remoti

I partecipanti remoti possono avviare la condivisione di un video o dello schermo durante una chiamata.

Gestire flussi di condivisione video o condivisione dello schermo di partecipanti remoti

Per elencare i flussi dei partecipanti remoti, esaminare le raccolte videoStreams.

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

Ottenere le proprietà del flusso video remoto

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

Eseguire il rendering dei flussi dei partecipanti remoti

Per avviare il rendering dei flussi dei partecipanti remoti, usare il codice riportato di seguito.

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)

Ottenere metodi e proprietà del renderer del video remoto

// [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()

Configurare il sistema

Seguire questa procedura per configurare il sistema.

Creare il progetto di Visual Studio

Per un'app piattaforma UWP (Universal Windows Platform), in Visual Studio 2022 creare un nuovo progetto App vuota (Windows universale). Dopo aver immesso il nome del progetto, è possibile scegliere qualsiasi Windows SDK successivo alla versione 10.0.17763.0.

Per un'app WinUI 3, creare un nuovo progetto con il modello App vuota, Incluso nel pacchetto (WinUI 3 in Desktop) per configurare un'app WinUI 3 a pagina singola. È necessario SDK per app di Windows in versione 1.3 o successiva.

Installare il pacchetto e le dipendenze usando Gestione pacchetti NuGet

Le API e le librerie di SDK Chiamata sono disponibili pubblicamente tramite un pacchetto NuGet.

Per trovare, scaricare e installare il pacchetto NuGet Calling SDK:

  1. Aprire Gestione pacchetti NuGet selezionando Strumenti>Gestione pacchetti NuGet> Gestisci pacchetti NuGet per la soluzione.
  2. Selezionare Sfoglia e quindi immettere Azure.Communication.Calling.WindowsClient nella casella di ricerca.
  3. Assicurarsi che la casella di controllo Includi versione preliminare sia selezionata.
  4. Selezionare il pacchetto Azure.Communication.Calling.WindowsClient e quindi selezionare Azure.Communication.Calling.WindowsClient 1.4.0-beta.1 o una versione più recente.
  5. Selezionare la casella di controllo corrispondente al progetto Servizi di comunicazione di Azure nel riquadro destro.
  6. Selezionare Installa.

Richiedere l'accesso al microfono

L'app richiede l'accesso alla fotocamera affinché possa essere eseguita correttamente. Nelle app della piattaforma UWP (Universal Windows Platform), la funzionalità fotocamera deve essere dichiarata nel file manifesto dell'app.

I passaggi seguenti esemplificano come ottenere questo risultato.

  1. Nel pannello Solution Explorer, fare doppio clic sul file con estensione .appxmanifest.
  2. Fare clic sulla scheda Capabilities.
  3. Selezionare la casella di controllo Camera dall'elenco delle funzionalità.

Creare pulsanti dell'interfaccia utente per effettuare e terminare la chiamata

Questa semplice app di esempio contiene due pulsanti. Uno per effettuare la chiamata e un altro per terminarla. La procedura seguente illustra come aggiungere questi pulsanti all'app.

  1. Nel pannello Solution Explorer, fare doppio clic sul file denominato MainPage.xaml per UWP o MainWindows.xaml per WinUI 3.
  2. Nel pannello centrale, cercare il codice XAML sotto l'anteprima dell'interfaccia utente.
  3. Modificare il codice XAML come dall'estratto seguente:
<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>

Configurazione dell'app con le API dell'SDK per chiamate

Le API dell’SDK per chiamate si trovano in due spazi dei nomi diversi. La procedura seguente informa il compilatore C# in proposito a questi spazi dei nomi, consentendo a IntelliSense di Visual Studio di supportare lo sviluppo di codice.

  1. Nel pannello Solution Explorer, fare clic sulla freccia sul lato sinistro del file denominato MainPage.xaml per UWP o MainWindows.xaml per WinUI 3.
  2. Fare doppio clic sul file denominato MainPage.xaml.cs o MainWindows.xaml.cs.
  3. Aggiungere i comandi seguenti nella parte inferiore delle istruzioni correnti using.
using Azure.Communication.Calling.WindowsClient;

Mantenere MainPage.xaml.cs o MainWindows.xaml.cs aperti. I passaggi successivi aggiungeranno ulteriore codice.

Consenti interazioni con le app

I pulsanti dell'interfaccia utente aggiunti in precedenza devono operare sopra una CommunicationCall effettuata. Significa che un membro dati CommunicationCall deve essere aggiunto alla classe MainPage o MainWindow. Inoltre, per consentire all'operazione asincrona che crea CallAgent di avere esito positivo, è necessario aggiungere anche un membro dati CallAgent alla stessa classe.

Aggiungere i seguenti membri dati alla classe MainPage o MainWindow:

CallAgent callAgent;
CommunicationCall call;

Creare gestori di pulsanti

In precedenza, sono stati aggiunti al codice XAML due pulsanti dell'interfaccia utente. Il codice seguente aggiunge i gestori da eseguire quando un utente seleziona il pulsante. Il codice seguente deve essere aggiunto dopo i membri di dati della sezione precedente.

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

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

Modello a oggetti

Le classi e le interfacce seguenti gestiscono alcune delle principali funzionalità della libreria client Chiamate di Servizi di comunicazione di Azure per UWP.

Nome Descrizione
CallClient CallClient è il principale punto di ingresso alla libreria client Chiamate.
CallAgent Il CallAgent viene usato per avviare e unire chiamate.
CommunicationCall L'oggetto CommunicationCall viene utilizzato per gestire le chiamate inserite o unite in join.
CommunicationTokenCredential CommunicationTokenCredential viene usato come credenziale del token per creare un'istanza di CallAgent.
CallAgentOptions CallAgentOptions Contiene informazioni per identificare il chiamante.
HangupOptions HangupOptions Informa tutti i partecipanti se una chiamata deve essere terminata.

Registrare il gestore dello schema video

Un componente dell'interfaccia utente, ad esempio MediaElement o MediaPlayerElement di XAML, che richiede che l'app registri una configurazione per eseguire il rendering di feed video locali e remoti. Aggiungere il contenuto seguente tra i tag Package di Package.appxmanifest:

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

Inizializzare CallAgent

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

Per creare CallAgent, è necessario passare un oggetto CallTokenCredential e un oggetto CallAgentOptions. Tenere presente che CallTokenCredential genera un'eccezione se viene passato un token in formato non valido.

Il codice seguente deve essere aggiunto all'interno e alla funzione helper da chiamare nell'inizializzazione dell'app.

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;

Modificare <AUTHENTICATION_TOKEN> con un token di credenziali valido per la risorsa. Fare riferimento alla documentazione del token di accesso utente se si necessita di creare un token di credenziali.

Effettuare una chiamata 1:1 con la videocamera

Gli oggetti necessari per la creazione di un CallAgent sono ora pronti. È il momento di creare CallAgent in modo asincrono ed effettuare una chiamata video.

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

Anteprima della fotocamera locale

Facoltativamente, è possibile configurare l'anteprima di fotocamera locale. Il rendering del video può essere eseguito tramite MediaPlayerElement:

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

Per inizializzare l'anteprima locale MediaPlayerElement:

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

Eseguire il rendering del flusso della fotocamera remota

Configurare anche il gestore in risposta all'evento 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;
    }
}

Avviare il rendering del flusso video remoto in 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;
    }
}

Terminare una chiamata

Una volta effettuata una chiamata, utilizzare il metodo HangupAsync dell'oggetto CommunicationCall per terminarla.

È inoltre necessario utilizzare un'istanza di HangupOptions per informare tutti i partecipanti se la chiamata deve essere terminata.

Il codice seguente deve essere aggiunto all'interno di 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;
            }
        }
    }
}

Eseguire il codice

Assicurarsi che Visual Studio compili l'app per x64, x86 o ARM64, quindi premere F5 per avviare l'esecuzione dell'app. Successivamente, fare clic sul pulsante CommunicationCall per inserire una chiamata al chiamato definito.

Tenere presente che alla prima esecuzione dell’app, il sistema chiede all'utente di concedere l'accesso al microfono.

Passaggi successivi