Condividi tramite


Guida introduttiva: Aggiungere videochiamate 1:1 come utente di Teams all'applicazione

Per iniziare a usare Servizi di comunicazione di Azure, usare l'SDK per chiamate di Servizi di comunicazione per aggiungere 1:1 chiamate vocali e videochiamate all'app. Si apprenderà come avviare e rispondere a una chiamata usando Azure Communication Services Calling SDK per JavaScript.

Codice di esempio

Se si vuole passare direttamente alla fine, è possibile scaricare questa guida introduttiva come esempio in GitHub.

Prerequisiti

Configurazione

Creare una nuova applicazione Node.js

Aprire la finestra del terminale o di comando, creare una nuova directory per l'app e passare alla directory.

mkdir calling-quickstart && cd calling-quickstart

Eseguire npm init -y per creare un file package.json con le impostazioni predefinite.

npm init -y

Installare il pacchetto

Usare il comando npm install per installare SDK di chiamata di Servizi di comunicazione di Azure per JavaScript.

Importante

Questa guida introduttiva usa la versione più recente di Azure Communication Services Calling SDK.

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

Configurare il framework dell'app

Questa guida di avvio rapido usa webpack per aggregare gli asset dell'applicazione. Eseguire il comando seguente per installare i pacchetti npm webpack, webpack-cli e webpack-dev-server ed elencarli come dipendenze di sviluppo in package.json:

npm install copy-webpack-plugin@^11.0.0 webpack@^5.88.2 webpack-cli@^5.1.4 webpack-dev-server@^4.15.1 --save-dev

Creare un file index.html nella directory radice del progetto. Questo file verrà usato per configurare un layout di base che consentirà all'utente di effettuare una videochiamata 1:1.

Ecco il codice:

<!-- index.html -->
<!DOCTYPE html>
<html>
    <head>
        <title>Azure Communication Services - Teams Calling Web Application</title>
    </head>
    <body>
        <h4>Azure Communication Services - Teams Calling Web Application</h4>
        <input id="user-access-token"
            type="text"
            placeholder="User access token"
            style="margin-bottom:1em; width: 500px;"/>
        <button id="initialize-teams-call-agent" type="button">Login</button>
        <br>
        <br>
        <input id="callee-teams-user-id"
            type="text"
            placeholder="Microsoft Teams callee's id (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)"
            style="margin-bottom:1em; width: 500px; display: block;"/>
        <button id="start-call-button" type="button" disabled="true">Start Call</button>
        <button id="hangup-call-button" type="button" disabled="true">Hang up Call</button>
        <button id="accept-call-button" type="button" disabled="true">Accept Call</button>
        <button id="start-video-button" type="button" disabled="true">Start Video</button>
        <button id="stop-video-button" type="button" disabled="true">Stop Video</button>
        <br>
        <br>
        <div id="connectedLabel" style="color: #13bb13;" hidden>Call is connected!</div>
        <br>
        <div id="remoteVideoContainer" style="width: 40%;" hidden>Remote participants' video streams:</div>
        <br>
        <div id="localVideoContainer" style="width: 30%;" hidden>Local video stream:</div>
        <!-- points to the bundle generated from client.js -->
        <script src="./main.js"></script>
    </body>
</html>

Modello a oggetti di Servizi di comunicazione di Azure che chiama Web SDK

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

Nome Descrizione
CallClient Punto di ingresso principale di Calling SDK.
AzureCommunicationTokenCredential Implementa l'interfaccia CommunicationTokenCredential usata per creare un'istanza di teamsCallAgent.
TeamsCallAgent Usato per avviare e gestire le chiamate di Teams.
DeviceManager Usato per gestire i dispositivi multimediali.
TeamsCall Usato per rappresentare una chiamata di Teams
LocalVideoStream Usato per creare un flusso video locale per un dispositivo fotocamera nel sistema locale.
RemoteParticipant Utilizzato per rappresentare un partecipante remoto nella chiamata
RemoteVideoStream Utilizzato per rappresentare un flusso video remoto da un partecipante remoto.

Nella directory radice del progetto creare un file denominato index.js in cui contenere la logica dell'applicazione per questa guida di avvio rapido. Aggiungere il codice seguente a index.js:

// Make sure to install the necessary dependencies
const { CallClient, VideoStreamRenderer, LocalVideoStream } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential } = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");
// Set the log level and output
setLogLevel('verbose');
AzureLogger.log = (...args) => {
    console.log(...args);
};
// Calling web sdk objects
let teamsCallAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let calleeTeamsUserId = document.getElementById('callee-teams-user-id');
let initializeCallAgentButton = document.getElementById('initialize-teams-call-agent');
let startCallButton = document.getElementById('start-call-button');
let hangUpCallButton = document.getElementById('hangup-call-button');
let acceptCallButton = document.getElementById('accept-call-button');
let startVideoButton = document.getElementById('start-video-button');
let stopVideoButton = document.getElementById('stop-video-button');
let connectedLabel = document.getElementById('connectedLabel');
let remoteVideoContainer = document.getElementById('remoteVideoContainer');
let localVideoContainer = document.getElementById('localVideoContainer');
/**
 * Create an instance of CallClient. Initialize a TeamsCallAgent instance with a CommunicationUserCredential via created CallClient. TeamsCallAgent enables us to make outgoing calls and receive incoming calls. 
 * You can then use the CallClient.getDeviceManager() API instance to get the DeviceManager.
 */
initializeCallAgentButton.onclick = async () => {
    try {
        const callClient = new CallClient(); 
        tokenCredential = new AzureCommunicationTokenCredential(userAccessToken.value.trim());
        teamsCallAgent = await callClient.createTeamsCallAgent(tokenCredential)
        // Set up a camera device to use.
        deviceManager = await callClient.getDeviceManager();
        await deviceManager.askDevicePermission({ video: true });
        await deviceManager.askDevicePermission({ audio: true });
        // Listen for an incoming call to accept.
        teamsCallAgent.on('incomingCall', async (args) => {
            try {
                incomingCall = args.incomingCall;
                acceptCallButton.disabled = false;
                startCallButton.disabled = true;
            } catch (error) {
                console.error(error);
            }
        });
        startCallButton.disabled = false;
        initializeCallAgentButton.disabled = true;
    } catch(error) {
        console.error(error);
    }
}
/**
 * Place a 1:1 outgoing video call to a user
 * Add an event listener to initiate a call when the `startCallButton` is selected.
 * Enumerate local cameras using the deviceManager `getCameraList` API.
 * In this quickstart, we're using the first camera in the collection. Once the desired camera is selected, a
 * LocalVideoStream instance will be constructed and passed within `videoOptions` as an item within the
 * localVideoStream array to the call method. When the call connects, your application will be sending a video stream to the other participant. 
 */
startCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = teamsCallAgent.startCall({ microsoftTeamsUserId: calleeTeamsUserId.value.trim() }, { videoOptions: videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
/**
 * Accepting an incoming call with a video
 * Add an event listener to accept a call when the `acceptCallButton` is selected.
 * You can accept incoming calls after subscribing to the `TeamsCallAgent.on('incomingCall')` event.
 * You can pass the local video stream to accept the call with the following code.
 */
acceptCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = await incomingCall.accept({ videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a call obj.
// Listen for property changes and collection updates.
subscribeToCall = (call) => {
    try {
        // Inspect the initial call.id value.
        console.log(`Call Id: ${call.id}`);
        //Subscribe to call's 'idChanged' event for value changes.
        call.on('idChanged', () => {
            console.log(`Call ID changed: ${call.id}`); 
        });
        // Inspect the initial call.state value.
        console.log(`Call state: ${call.state}`);
        // Subscribe to call's 'stateChanged' event for value changes.
        call.on('stateChanged', async () => {
            console.log(`Call state changed: ${call.state}`);
            if(call.state === 'Connected') {
                connectedLabel.hidden = false;
                acceptCallButton.disabled = true;
                startCallButton.disabled = true;
                hangUpCallButton.disabled = false;
                startVideoButton.disabled = false;
                stopVideoButton.disabled = false;
            } else if (call.state === 'Disconnected') {
                connectedLabel.hidden = true;
                startCallButton.disabled = false;
                hangUpCallButton.disabled = true;
                startVideoButton.disabled = true;
                stopVideoButton.disabled = true;
                console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
            }   
        });
        call.on('isLocalVideoStartedChanged', () => {
            console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
        });
        console.log(`isLocalVideoStarted: ${call.isLocalVideoStarted}`);
        call.localVideoStreams.forEach(async (lvs) => {
            localVideoStream = lvs;
            await displayLocalVideoStream();
        });
        call.on('localVideoStreamsUpdated', e => {
            e.added.forEach(async (lvs) => {
                localVideoStream = lvs;
                await displayLocalVideoStream();
            });
            e.removed.forEach(lvs => {
               removeLocalVideoStream();
            });
        });
        
        // Inspect the call's current remote participants and subscribe to them.
        call.remoteParticipants.forEach(remoteParticipant => {
            subscribeToRemoteParticipant(remoteParticipant);
        });
        // Subscribe to the call's 'remoteParticipantsUpdated' event to be
        // notified when new participants are added to the call or removed from the call.
        call.on('remoteParticipantsUpdated', e => {
            // Subscribe to new remote participants that are added to the call.
            e.added.forEach(remoteParticipant => {
                subscribeToRemoteParticipant(remoteParticipant)
            });
            // Unsubscribe from participants that are removed from the call
            e.removed.forEach(remoteParticipant => {
                console.log('Remote participant removed from the call.');
            });
        });
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a remote participant obj.
// Listen for property changes and collection updates.
subscribeToRemoteParticipant = (remoteParticipant) => {
    try {
        // Inspect the initial remoteParticipant.state value.
        console.log(`Remote participant state: ${remoteParticipant.state}`);
        // Subscribe to remoteParticipant's 'stateChanged' event for value changes.
        remoteParticipant.on('stateChanged', () => {
            console.log(`Remote participant state changed: ${remoteParticipant.state}`);
        });
        // Inspect the remoteParticipants's current videoStreams and subscribe to them.
        remoteParticipant.videoStreams.forEach(remoteVideoStream => {
            subscribeToRemoteVideoStream(remoteVideoStream)
        });
        // Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
        // notified when the remoteParticipant adds new videoStreams and removes video streams.
        remoteParticipant.on('videoStreamsUpdated', e => {
            // Subscribe to newly added remote participant's video streams.
            e.added.forEach(remoteVideoStream => {
                subscribeToRemoteVideoStream(remoteVideoStream)
            });
            // Unsubscribe from newly removed remote participants' video streams.
            e.removed.forEach(remoteVideoStream => {
                console.log('Remote participant video stream was removed.');
            })
        });
    } catch (error) {
        console.error(error);
    }
}
/**
 * Subscribe to a remote participant's remote video stream obj.
 * You have to subscribe to the 'isAvailableChanged' event to render the remoteVideoStream. If the 'isAvailable' property
 * changes to 'true' a remote participant is sending a stream. Whenever the availability of a remote stream changes
 * you can choose to destroy the whole 'Renderer' a specific 'RendererView' or keep them. Displaying RendererView without a video stream will result in a blank video frame. 
 */
subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    // Create a video stream renderer for the remote video stream.
    let videoStreamRenderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    const renderVideo = async () => {
        try {
            // Create a renderer view for the remote video stream.
            view = await videoStreamRenderer.createView();
            // Attach the renderer view to the UI.
            remoteVideoContainer.hidden = false;
            remoteVideoContainer.appendChild(view.target);
        } catch (e) {
            console.warn(`Failed to createView, reason=${e.message}, code=${e.code}`);
        }	
    }
    
    remoteVideoStream.on('isAvailableChanged', async () => {
        // Participant has switched video on.
        if (remoteVideoStream.isAvailable) {
            await renderVideo();
        // Participant has switched video off.
        } else {
            if (view) {
                view.dispose();
                view = undefined;
            }
        }
    });
    // Participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        await renderVideo();
    }
}
// Start your local video stream.
// This will send your local video stream to remote participants so they can view it.
startVideoButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        await call.startVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
// Stop your local video stream.
// This will stop your local video stream from being sent to remote participants.
stopVideoButton.onclick = async () => {
    try {
        await call.stopVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
/**
 * To render a LocalVideoStream, you need to create a new instance of VideoStreamRenderer, and then
 * create a new VideoStreamRendererView instance using the asynchronous createView() method.
 * You may then attach view.target to any UI element. 
 */
// Create a local video stream for your camera device
createLocalVideoStream = async () => {
    const camera = (await deviceManager.getCameras())[0];
    if (camera) {
        return new LocalVideoStream(camera);
    } else {
        console.error(`No camera device found on the system`);
    }
}
// Display your local video stream preview in your UI
displayLocalVideoStream = async () => {
    try {
        localVideoStreamRenderer = new VideoStreamRenderer(localVideoStream);
        const view = await localVideoStreamRenderer.createView();
        localVideoContainer.hidden = false;
        localVideoContainer.appendChild(view.target);
    } catch (error) {
        console.error(error);
    } 
}
// Remove your local video stream preview from your UI
removeLocalVideoStream = async() => {
    try {
        localVideoStreamRenderer.dispose();
        localVideoContainer.hidden = true;
    } catch (error) {
        console.error(error);
    } 
}
// End the current call
hangUpCallButton.addEventListener("click", async () => {
    // end the current call
    await call.hangUp();
});

Aggiungere il codice del server locale webpack

Creare un file nella directory radice del progetto denominato webpack.config.js per contenere la logica del server locale per questa guida introduttiva. Aggiungere il codice seguente a webpack.config.js:

const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
    mode: 'development',
    entry: './index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
    },
    devServer: {
        static: {
            directory: path.join(__dirname, './')
        },
    },
    plugins: [
        new CopyPlugin({
            patterns: [
                './index.html'
            ]
        }),
    ]
};

Eseguire il codice

Usare webpack-dev-server per compilare ed eseguire l'app. Eseguire il comando seguente per aggregare l'host dell'applicazione in un server Web locale:

`npx webpack serve --config webpack.config.js`

Aprire il browser e in due schede passare a http://localhost:8080/. Le schede dovrebbero mostrare il risultato simile all'immagine seguente: Screenshot che mostra due schede nella visualizzazione predefinita. Ogni scheda verrà usata per un utente di Teams diverso.

Nella prima scheda immettere un token di accesso utente valido. Nella seconda scheda immettere un altro token di accesso utente valido diverso. Se non è disponibile un token, vedere la documentazione dei token di accesso utente. In entrambe le schede fare clic sui pulsanti "Inizializza agente di chiamata". Le schede dovrebbero mostrare il risultato simile all'immagine seguente: Screenshot che mostra i passaggi per inizializzare ogni utente di Teams nella scheda del browser.

Nella prima scheda immettere l'identità utente di Servizi di comunicazione di Azure della seconda scheda e selezionare il pulsante "Avvia chiamata". La prima scheda avvierà la chiamata in uscita alla seconda scheda e il pulsante "Accetta chiamata" della seconda scheda diventa abilitato: Screenshot che mostra l'esperienza quando gli utenti di Teams inizializzano l'SDK e illustrano i passaggi per avviare una chiamata al secondo utente e come accettare la chiamata.

Nella seconda scheda selezionare il pulsante "Accetta chiamata". La chiamata verrà risposta e connessa. Le schede dovrebbero mostrare il risultato simile all'immagine seguente: Screenshot che mostra due schede, con una chiamata in corso tra due utenti di Teams, ognuna registrata nella singola scheda.

Entrambe le schede sono ora correttamente in una videochiamata 1:1. Entrambi gli utenti possono ascoltare l'audio dell'altro e vedere l'uno il flusso video dell'altro.

Per iniziare a usare Servizi di comunicazione di Azure, usare l'SDK per chiamate di Servizi di comunicazione per aggiungere 1:1 chiamate vocali e videochiamate all'app. Si apprenderà come avviare e rispondere a una chiamata usando l'SDK per chiamate di Servizi di comunicazione di Azure per Windows.

Codice di esempio

Se si vuole passare direttamente alla fine, è possibile scaricare questa guida introduttiva come esempio in GitHub.

Prerequisiti

Per completare questa esercitazione è necessario soddisfare i prerequisiti seguenti:

Configurazione

Creazione del progetto

In Visual Studio creare un nuovo progetto con il modello App vuota (Windows universale) per configurare un'app UWP (Universal Windows Platform) a pagina singola.

Screenshot che mostra la finestra Nuovo progetto UWP in Visual Studio.

Installare il pacchetto

Selezionare il progetto e passare a Manage Nuget Packages per installare Azure.Communication.Calling.WindowsClient 1.2.0-beta.1 o superiore. Verificare che l'opzione Includi Preleased sia selezionata.

Richiedere l'accesso

Passare a Package.appxmanifest e selezionare Capabilities. Controllare Internet (Client) e Internet (Client & Server) per ottenere l'accesso in ingresso e in uscita a Internet. Controllare Microphone per accedere al feed audio del microfono e Webcam per accedere al feed video della fotocamera.

Screenshot che mostra la richiesta di accesso a Internet e microfono in Visual Studio.

Configurare il framework dell'app

È necessario configurare un layout di base per collegare la logica. Per effettuare una chiamata in uscita, è necessario un TextBox per fornire l'ID utente del chiamato. È anche necessario un pulsante Start/Join call e un pulsante Hang up. In questo esempio sono inclusi anche delle caselle di controllo Mute e BackgroundBlur per illustrare le funzionalità di attivazione/disattivazione degli stati audio e degli effetti video.

Aprire il MainPage.xaml del progetto e aggiungere il nodo Grid al Page:

<Page
    x:Class="CallingQuickstart.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CallingQuickstart"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Width="800" Height="600">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="16*"/>
            <RowDefinition Height="30*"/>
            <RowDefinition Height="200*"/>
            <RowDefinition Height="60*"/>
            <RowDefinition Height="16*"/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="1" x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="10,10,10,10" />

        <Grid x:Name="AppTitleBar" Background="LightSeaGreen">
            <TextBlock x:Name="QuickstartTitle" Text="Calling Quickstart sample title bar" Style="{StaticResource CaptionTextBlockStyle}" Padding="7,7,0,0"/>
        </Grid>

        <Grid Grid.Row="2">
            <Grid.RowDefinitions>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <MediaPlayerElement x:Name="LocalVideo" HorizontalAlignment="Center" Stretch="UniformToFill" Grid.Column="0" VerticalAlignment="Center" AutoPlay="True" />
            <MediaPlayerElement x:Name="RemoteVideo" HorizontalAlignment="Center" Stretch="UniformToFill" Grid.Column="1" VerticalAlignment="Center" AutoPlay="True" />
        </Grid>
        <StackPanel Grid.Row="3" Orientation="Vertical" Grid.RowSpan="2">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" VerticalAlignment="Center" Margin="10,0,0,0" Height="40" Width="123"/>
                <Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" VerticalAlignment="Center" Margin="10,0,0,0" Height="40" Width="123"/>
                <CheckBox x:Name="MuteLocal" Content="Mute" Margin="10,0,0,0" Click="MuteLocal_Click" Width="74"/>
            </StackPanel>
        </StackPanel>
        <TextBox Grid.Row="5" x:Name="Stats" Text="" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="0,2,0,0" BorderThickness="2" IsReadOnly="True" Foreground="LightSlateGray" />
    </Grid>
</Page>

Aprire MainPage.xaml.cs e sostituire il contenuto con l'implementazione seguente:

using Azure.Communication.Calling.WindowsClient;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Core;
using Windows.Media.Core;
using Windows.Networking.PushNotifications;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace CallingQuickstart
{
    public sealed partial class MainPage : Page
    {
        private const string authToken = "<AUTHENTICATION_TOKEN>";

        private CallClient callClient;
        private CallTokenRefreshOptions callTokenRefreshOptions = new CallTokenRefreshOptions(false);
        private TeamsCallAgent teamsCallAgent;
        private TeamsCommunicationCall teamsCall;

        private LocalOutgoingAudioStream micStream;
        private LocalOutgoingVideoStream cameraStream;

        #region Page initialization
        public MainPage()
        {
            this.InitializeComponent();
            // Additional UI customization code goes here
        }

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
        }
        #endregion

        #region UI event handlers
        private async void CallButton_Click(object sender, RoutedEventArgs e)
        {
            // Start a call
        }

        private async void HangupButton_Click(object sender, RoutedEventArgs e)
        {
            // Hang up a call
        }

        private async void MuteLocal_Click(object sender, RoutedEventArgs e)
        {
            // Toggle mute/unmute audio state of a call
        }
        #endregion

        #region API event handlers
        private async void OnIncomingCallAsync(object sender, TeamsIncomingCallReceivedEventArgs args)
        {
            // Handle incoming call event
        }

        private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            // Handle connected and disconnected state change of a call
        }
        #endregion
    }
}

Modello a oggetti

La tabella successiva elenca le classi e le interfacce che gestiscono alcune delle principali funzionalità di Azure Communication Services Calling SDK:

Nome Descrizione
CallClient CallClient è il punto di ingresso principale di Calling SDK.
TeamsCallAgent TeamsCallAgent viene usato per avviare e gestire le chiamate.
TeamsCommunicationCall TeamsCommunicationCall viene utilizzato per gestire una chiamata in corso.
CallTokenCredential CallTokenCredential viene usato come credenziale del token per creare un'istanza di TeamsCallAgent.
CallIdentifier CallIdentifier viene usato per rappresentare l'identità dell'utente, che può essere una delle opzioni seguenti: MicrosoftTeamsUserCallIdentifier, UserCallIdentifier, PhoneNumberCallIdentifier e così via.

Autenticare il client

Inizializzare un'istanza TeamsCallAgent con un token di accesso utente che consente di effettuare e ricevere chiamate e, facoltativamente, ottenere un'istanza di DeviceManager per eseguire query per le configurazioni del dispositivo client.

Nel codice, sostituire <AUTHENTICATION_TOKEN> con un token di accesso utente. Se non è disponibile un token, vedere la documentazione dei token di accesso utente.

Aggiungere la funzione InitCallAgentAndDeviceManagerAsync, che esegue il bootstrap dell'SDK. Questo helper può essere personalizzato per soddisfare i requisiti dell'applicazione.

        private async Task InitCallAgentAndDeviceManagerAsync()
        {
            this.callClient = new CallClient(new CallClientOptions() {
                Diagnostics = new CallDiagnosticsOptions() { 
                    AppName = "CallingQuickstart",
                    AppVersion="1.0",
                    Tags = new[] { "Calling", "CTE", "Windows" }
                    }
                });

            // Set up local video stream using the first camera enumerated
            var deviceManager = await this.callClient.GetDeviceManagerAsync();
            var camera = deviceManager?.Cameras?.FirstOrDefault();
            var mic = deviceManager?.Microphones?.FirstOrDefault();
            micStream = new LocalOutgoingAudioStream();

            var tokenCredential = new CallTokenCredential(authToken, callTokenRefreshOptions);

            this.teamsCallAgent = await this.callClient.CreateTeamsCallAgentAsync(tokenCredential);
            this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;
        }

Avvia una chiamata

Aggiungere l'implementazione a CallButton_Click per avviare vari tipi di chiamate con l'oggetto teamsCallAgent creato e associare i gestori eventi RemoteParticipantsUpdated e StateChanged nell'oggetto TeamsCommunicationCall.

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

            teamsCall = await StartCteCallAsync(callString);
            if (teamsCall != null)
            {
                teamsCall.StateChanged += OnStateChangedAsync;
            }
        }

Terminare una chiamata

Terminare la chiamata corrente quando si fa clic sul pulsante Hang up. Aggiungere l'implementazione all’HangupButton_Click per terminare una chiamata e arrestare i flussi di anteprima e video.

        private async void HangupButton_Click(object sender, RoutedEventArgs e)
        {
            var teamsCall = this.teamsCallAgent?.Calls?.FirstOrDefault();
            if (teamsCall != null)
            {
                await teamsCall.HangUpAsync(new HangUpOptions() { ForEveryone = false });
            }
        }

Alternanza attiva/disattiva audio

Disattivare l'audio in uscita quando si fa clic sul pulsante Mute. Aggiungere l'implementazione al MuteLocal_Click per disattivare la chiamata.

        private async void MuteLocal_Click(object sender, RoutedEventArgs e)
        {
            var muteCheckbox = sender as CheckBox;

            if (muteCheckbox != null)
            {
                var teamsCall = this.teamsCallAgent?.Calls?.FirstOrDefault();
                if (teamsCall != null)
                {
                    if ((bool)muteCheckbox.IsChecked)
                    {
                        await teamsCall.MuteOutgoingAudioAsync();
                    }
                    else
                    {
                        await teamsCall.UnmuteOutgoingAudioAsync();
                    }
                }

                // Update the UI to reflect the state
            }
        }

Avviare la chiamata

Dopo aver ottenuto un oggetto StartTeamsCallOptions, è possibile usare TeamsCallAgent per avviare la chiamata di Teams:

        private async Task<TeamsCommunicationCall> StartCteCallAsync(string cteCallee)
        {
            var options = new StartTeamsCallOptions();
            var teamsCall = await this.teamsCallAgent.StartCallAsync( new MicrosoftTeamsUserCallIdentifier(cteCallee), options);
            return call;
        }

Accettare una chiamata in arrivo

Il sink di eventi TeamsIncomingCallReceived viene configurato nell'helper bootstrap dell'SDK InitCallAgentAndDeviceManagerAsync.

    this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;

L'applicazione ha la possibilità di configurare la modalità di accettazione della chiamata in ingresso, ad esempio i tipi di flusso video e audio.

        private async void OnIncomingCallAsync(object sender, TeamsIncomingCallReceivedEventArgs args)
        {
            var teamsIncomingCall = args.IncomingCall;

            var acceptteamsCallOptions = new AcceptTeamsCallOptions() { };

            teamsCall = await teamsIncomingCall.AcceptAsync(acceptteamsCallOptions);
            teamsCall.StateChanged += OnStateChangedAsync;
        }

Partecipare a una chiamata di Teams

L'utente può anche partecipare a una chiamata esistente passando un collegamento

TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
JoinTeamsCallOptions options = new JoinTeamsCallOptions();
TeamsCall call = await teamsCallAgent.JoinAsync(link, options);

Monitorare e rispondere all'evento di modifica dello stato della chiamata

L’evento StateChanged sull'oggetto TeamsCommunicationCall viene generato quando un oggetto in corso chiama transazioni da uno stato a un altro. L'applicazione offre le opportunità di riflettere le modifiche dello stato nell'interfaccia utente o inserire le logiche di business.

        private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            var teamsCall = sender as TeamsCommunicationCall;

            if (teamsCall != null)
            {
                var state = teamsCall.State;

                // Update the UI

                switch (state)
                {
                    case CallState.Connected:
                        {
                            await teamsCall.StartAudioAsync(micStream);

                            break;
                        }
                    case CallState.Disconnected:
                        {
                            teamsCall.StateChanged -= OnStateChangedAsync;

                            teamsCall.Dispose();

                            break;
                        }
                    default: break;
                }
            }
        }

Eseguire il codice

È possibile compilare ed eseguire il codice in Visual Studio. Per le piattaforme di soluzioni, sono supportati ARM64, x64 e x86.

È possibile effettuare una chiamata in uscita fornendo un ID utente nel campo di testo e facendo clic sul pulsante Start Call/Join. Con la chiamata a 8:echo123 ci si connette a un Echo Bot, questa funzionalità è la scelta ideale per iniziare e per verificare che i dispositivi audio funzionino.

Screenshot che mostra l'esecuzione dell'app di avvio rapido UWP

Per iniziare a usare Servizi di comunicazione di Azure, usare l'SDK per chiamate di Servizi di comunicazione per aggiungere 1:1 chiamate vocali e videochiamate all'app. Si apprenderà come avviare e rispondere a una chiamata usando Azure Communication Services Calling SDK per Java.

Codice di esempio

Se si vuole passare direttamente alla fine, è possibile scaricare questa guida introduttiva come esempio in GitHub.

Prerequisiti

Configurazione

Creare un'app Android con un'attività vuota

In Android Studio avviare un nuovo progetto.

Screenshot che mostra il pulsante per l'avvio di un nuovo progetto di Android Studio selezionato in Android Studio.

Selezionare il modello di progetto "Empty Activity" (Attività vuota) in "Phone and Tablet" (Telefono e tablet).

Screenshot che mostra l'opzione 'Empty Activity' selezionata nella schermata del modello di progetto.

Selezionare Minimum SDK (SDK minimo) di "API 26: Android 8.0 (Oreo)" o versione successiva.

Screenshot che mostra l'opzione 'Empty Activity' selezionata nella schermata 2 del modello di progetto.

Installare il pacchetto

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

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

In build.gradle a livello di modulo aggiungere quindi le righe seguenti alle sezioni dependencies e android

android {
    ...
    packagingOptions {
        pickFirst  'META-INF/*'
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

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

Aggiungere autorizzazioni al manifesto dell'applicazione

Per richiedere le autorizzazioni necessarie per effettuare una chiamata, bisogna dichiararle nel manifesto dell'applicazione (app/src/main/AndroidManifest.xml). Sostituire il contenuto del file con il codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.contoso.ctequickstart">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <!--Our Calling SDK depends on the Apache HTTP SDK.
When targeting Android SDK 28+, this library needs to be explicitly referenced.
See https://developer.android.com/about/versions/pie/android-9.0-changes-28#apache-p-->
        <uses-library android:name="org.apache.http.legacy" android:required="false"/>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
    

Configurare il layout per l'app

Sono necessari due input: un input di testo per l'ID del destinatario e un pulsante per effettuare la chiamata. Questi input possono essere aggiunti tramite la finestra di progettazione o modificando il codice XML del layout. Creare un pulsante con un ID call_button e un input di testo callee_id. Passare a app/src/main/res/layout/activity_main.xml e sostituire il contenuto del file con il codice segue:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/call_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:text="Call"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <EditText
        android:id="@+id/callee_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Callee Id"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toTopOf="@+id/call_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Creare lo scaffolding e i binding per l'attività principale

Con il layout creato è possibile aggiungere i binding e lo scaffolding di base dell'attività. L'attività gestisce la richiesta delle autorizzazioni in fase di esecuzione, la creazione dell'agente di chiamata teams e l'effettuazione della chiamata quando viene premuto il pulsante. Ognuno di essi è trattato nella propria sezione. Il metodo onCreate viene sottoposto a override per richiamare getAllPermissions e createTeamsAgent e aggiungere le associazioni per il pulsante di chiamata. Questo evento si verifica una sola volta quando viene creata l'attività. Per altre informazioni su onCreate, vedere la guida sul ciclo di vita dell'attività.

Passare a MainActivity.java e sostituire il contenuto con il codice seguente:

package com.contoso.ctequickstart;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.media.AudioManager;
import android.Manifest;
import android.content.pm.PackageManager;

import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.azure.android.communication.common.CommunicationUserIdentifier;
import com.azure.android.communication.common.CommunicationTokenCredential;
import com.azure.android.communication.calling.TeamsCallAgent;
import com.azure.android.communication.calling.CallClient;
import com.azure.android.communication.calling.StartTeamsCallOptions;


import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    
    private TeamsCallAgent teamsCallAgent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getAllPermissions();
        createTeamsAgent();
        
        // Bind call button to call `startCall`
        Button callButton = findViewById(R.id.call_button);
        callButton.setOnClickListener(l -> startCall());
        
        setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
    }

    /**
     * Request each required permission if the app doesn't already have it.
     */
    private void getAllPermissions() {
        // See section on requesting permissions
    }

    /**
      * Create the call agent for placing calls
      */
    private void createTeamsAgent() {
        // See section on creating the call agent
    }

    /**
     * Place a call to the callee id provided in `callee_id` text input.
     */
    private void startCall() {
        // See section on starting the call
    }
}

Richiedere autorizzazioni in fase di esecuzione

Per Android 6.0 e versioni successive (livello API 23) e targetSdkVersion 23 o versione successiva, le autorizzazioni vengono concesse in fase di esecuzione invece che durante l'installazione dell'app. Per supportare questo requisito, è possibile implementare getAllPermissions per chiamare ActivityCompat.checkSelfPermission e ActivityCompat.requestPermissions per ogni autorizzazione richiesta.

/**
 * Request each required permission if the app doesn't already have it.
 */
private void getAllPermissions() {
    String[] requiredPermissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE};
    ArrayList<String> permissionsToAskFor = new ArrayList<>();
    for (String permission : requiredPermissions) {
        if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            permissionsToAskFor.add(permission);
        }
    }
    if (!permissionsToAskFor.isEmpty()) {
        ActivityCompat.requestPermissions(this, permissionsToAskFor.toArray(new String[0]), 1);
    }
}

Nota

Quando si progetta l'app, valutare quando dovranno essere richieste queste autorizzazioni. È consigliabile che vengano richieste quando è necessario, non prima. Per altre informazioni, vedere la guida alle autorizzazioni di Android..

Modello a oggetti

Le classi e le interfacce seguenti gestiscono alcune delle principali funzionalità del SDK Chiamate di Servizi di comunicazione di Azure:

Nome Descrizione
CallClient CallClient è il punto di ingresso principale di Calling SDK.
TeamsCallAgent TeamsCallAgent viene usato per avviare e gestire le chiamate.
TeamsCall L’oggetto TeamsCall utilizzato per rappresentare una chiamata di Teams.
CommunicationTokenCredential CommunicationTokenCredential viene usato come credenziale del token per creare un'istanza di TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier viene usato come tipo diverso di partecipante che potrebbe far parte di una chiamata.

Creare un agente dal token di accesso utente

Con un token utente è possibile creare un'istanza di un agente di chiamata autenticato. In genere, questo token viene generato da un servizio con l'autenticazione specifica per l'applicazione. Per altre informazioni sui token di accesso utente, vedere la guida ai token di accesso utente.

Per la guida di avvio rapido, sostituire <User_Access_Token> con un token di accesso utente generato per la risorsa del servizio di comunicazione di Azure.


/**
 * Create the teams call agent for placing calls
 */
private void createAgent() {
    String userToken = "<User_Access_Token>";

    try {
        CommunicationTokenCredential credential = new CommunicationTokenCredential(userToken);
        teamsCallAgent = new CallClient().createTeamsCallAgent(getApplicationContext(), credential).get();
    } catch (Exception ex) {
        Toast.makeText(getApplicationContext(), "Failed to create teams call agent.", Toast.LENGTH_SHORT).show();
    }
}

Avviare una chiamata con l'agente di chiamata

La chiamata può essere effettuata tramite l'agente di chiamata di teams: è sufficiente fornire un elenco di ID dei destinatari e le opzioni di chiamata. Per la guida di avvio rapido, si usano le opzioni di chiamata predefinite senza video e un singolo ID destinatario dall'input di testo.

/**
 * Place a call to the callee id provided in `callee_id` text input.
 */
private void startCall() {
    EditText calleeIdView = findViewById(R.id.callee_id);
    String calleeId = calleeIdView.getText().toString();
    
    StartTeamsCallOptions options = new StartTeamsCallOptions();

    teamsCallAgent.startCall(
        getApplicationContext(),
        new MicrosoftTeamsUserCallIdentifier(calleeId),
        options);
}

Rispondere a una chiamata

L'accettazione di una chiamata può essere eseguita usando l'agente di chiamata di teams usando solo un riferimento al contesto corrente.

public void acceptACall(TeamsIncomingCall teamsIncomingCall){
	teamsIncomingCall.accept(this);
}

Partecipare a una chiamata di Teams

Un utente può partecipare a una chiamata esistente passando un collegamento.

/**
 * Join a call using a teams meeting link.
 */
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
	TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
	TeamsCall call = teamsCallAgent.join(this, link);
}

Partecipare a una chiamata di Teams con le opzioni

È anche possibile unire una chiamata esistente con opzioni predefinite, ad esempio disattivate.

/**
 * Join a call using a teams meeting link while muted.
 */
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
	TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
	OutgoingAudioOptions audioOptions = new OutgoingAudioOptions().setMuted(true);
	JoinTeamsCallOptions options = new JoinTeamsCallOptions().setAudioOptions(audioOptions);
	TeamsCall call = teamsCallAgent.join(this, link, options);
}

Configurare il listener di chiamata in ingresso

Per poter rilevare le chiamate in ingresso e altre azioni non eseguite dall'utente, è necessario configurare i listener.

private TeamsIncomingCall teamsincomingCall;
teamsCallAgent.addOnIncomingCallListener(this::handleIncomingCall);

private void handleIncomingCall(TeamsIncomingCall incomingCall) {
	this.teamsincomingCall = incomingCall;
}

Avviare l'app e chiamare l'Echo Bot

È ora possibile avviare l'app usando il pulsante "Run App" (Esegui app) sulla barra degli strumenti (MAIUSC+F10). Verificare che sia possibile effettuare chiamate chiamando 8:echo123. Verrà riprodotto un messaggio preregistrato che ripete il messaggio inviato.

Screenshot che mostra l'applicazione completata.

Per iniziare a usare Servizi di comunicazione di Azure, usare l'SDK per chiamate di Servizi di comunicazione per aggiungerlo a una sola videochiamata all'app. Si apprenderà come avviare e rispondere a una videochiamata usando Azure Communication Services Calling SDK per iOS usando l'identità di Teams.

Codice di esempio

Se si vuole passare direttamente alla fine, è possibile scaricare questa guida introduttiva come esempio in GitHub.

Prerequisiti

Configurazione

Creazione del progetto Xcode

In Xcode creare un nuovo progetto iOS e selezionare il modello Single View Application. Questa esercitazione usa il framework SwiftUI, quindi è necessario impostare Linguaggio su Swift e Interfaccia utente su SwiftUI. In questo argomento di avvio rapido non verranno creati test. È possibile deselezionare Includi test.

Screenshot che mostra la finestra relativa al nuovo progetto in Xcode.

Installazione di CocoaPods

Usare questa guida per installare CocoaPods nel Mac.

Installare il pacchetto e le dipendenze con CocoaPods

  1. Per creare un oggetto Podfile per l'applicazione, aprire il terminale e passare alla cartella del progetto ed eseguire il pod init.

  2. Aggiungere il codice seguente a Podfile e salvare. Vedere Le versioni del supporto sdk.

platform :ios, '13.0'
use_frameworks!

target 'VideoCallingQuickstart' do
  pod 'AzureCommunicationCalling', '~> 2.10.0'
end
  1. Eseguire l'installazione del pod.

  2. Aprire .xcworkspace con Xcode.

Richiedere l'accesso al microfono e alla fotocamera

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

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

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

Configurare il framework dell'app

Aprire il file ContentView.swift del progetto e aggiungere una dichiarazione di importazione all'inizio del file per importare la libreria AzureCommunicationCalling e AVFoundation. AVFoundation viene usato per acquisire l'autorizzazione audio dal codice.

import AzureCommunicationCalling
import AVFoundation

Modello a oggetti

Le classi e le interfacce seguenti gestiscono alcune delle principali funzionalità del SDK Chiamate di Servizi di comunicazione di Azure per iOS.

Nome Descrizione
CallClient CallClient è il punto di ingresso principale di Calling SDK.
TeamsCallAgent TeamsCallAgent viene usato per avviare e gestire le chiamate.
TeamsIncomingCall TeamsIncomingCall viene usato per accettare o rifiutare le chiamate dei team in arrivo.
CommunicationTokenCredential CommunicationTokenCredential viene usato come credenziale del token per creare un'istanza di TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier viene usato per rappresentare l'identità dell'utente, che può essere una delle opzioni seguenti: CommunicationUserIdentifier, PhoneNumberIdentifier o CallingApplication.

Creare l'agente di chiamata di Teams

Sostituire l'implementazione del ContentView struct con alcuni semplici controlli dell'interfaccia utente che consentano a un utente di avviare e terminare una chiamata. In questa guida di avvio rapido viene aggiunta logica di business a questi controlli.

struct ContentView: View {
    @State var callee: String = ""
    @State var callClient: CallClient?
    @State var teamsCallAgent: TeamsCallAgent?
    @State var teamsCall: TeamsCall?
    @State var deviceManager: DeviceManager?
    @State var localVideoStream:[LocalVideoStream]?
    @State var teamsIncomingCall: TeamsIncomingCall?
    @State var sendingVideo:Bool = false
    @State var errorMessage:String = "Unknown"

    @State var remoteVideoStreamData:[Int32:RemoteVideoStreamData] = [:]
    @State var previewRenderer:VideoStreamRenderer? = nil
    @State var previewView:RendererView? = nil
    @State var remoteRenderer:VideoStreamRenderer? = nil
    @State var remoteViews:[RendererView] = []
    @State var remoteParticipant: RemoteParticipant?
    @State var remoteVideoSize:String = "Unknown"
    @State var isIncomingCall:Bool = false
    
    @State var callObserver:CallObserver?
    @State var remoteParticipantObserver:RemoteParticipantObserver?

    var body: some View {
        NavigationView {
            ZStack{
                Form {
                    Section {
                        TextField("Who would you like to call?", text: $callee)
                        Button(action: startCall) {
                            Text("Start Teams Call")
                        }.disabled(teamsCallAgent == nil)
                        Button(action: endCall) {
                            Text("End Teams Call")
                        }.disabled(teamsCall == nil)
                        Button(action: toggleLocalVideo) {
                            HStack {
                                Text(sendingVideo ? "Turn Off Video" : "Turn On Video")
                            }
                        }
                    }
                }
                // Show incoming call banner
                if (isIncomingCall) {
                    HStack() {
                        VStack {
                            Text("Incoming call")
                                .padding(10)
                                .frame(maxWidth: .infinity, alignment: .topLeading)
                        }
                        Button(action: answerIncomingCall) {
                            HStack {
                                Text("Answer")
                            }
                            .frame(width:80)
                            .padding(.vertical, 10)
                            .background(Color(.green))
                        }
                        Button(action: declineIncomingCall) {
                            HStack {
                                Text("Decline")
                            }
                            .frame(width:80)
                            .padding(.vertical, 10)
                            .background(Color(.red))
                        }
                    }
                    .frame(maxWidth: .infinity, alignment: .topLeading)
                    .padding(10)
                    .background(Color.gray)
                }
                ZStack{
                    VStack{
                        ForEach(remoteViews, id:\.self) { renderer in
                            ZStack{
                                VStack{
                                    RemoteVideoView(view: renderer)
                                        .frame(width: .infinity, height: .infinity)
                                        .background(Color(.lightGray))
                                }
                            }
                            Button(action: endCall) {
                                Text("End Call")
                            }.disabled(teamsCall == nil)
                            Button(action: toggleLocalVideo) {
                                HStack {
                                    Text(sendingVideo ? "Turn Off Video" : "Turn On Video")
                                }
                            }
                        }
                        
                    }.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                    VStack{
                        if(sendingVideo)
                        {
                            VStack{
                                PreviewVideoStream(view: previewView!)
                                    .frame(width: 135, height: 240)
                                    .background(Color(.lightGray))
                            }
                        }
                    }.frame(maxWidth:.infinity, maxHeight:.infinity,alignment: .bottomTrailing)
                }
            }
     .navigationBarTitle("Video Calling Quickstart")
        }.onAppear{
            // Authenticate the client
            
            // Initialize the TeamsCallAgent and access Device Manager
            
            // Ask for permissions
        }
    }
}

//Functions and Observers

struct PreviewVideoStream: UIViewRepresentable {
    let view:RendererView
    func makeUIView(context: Context) -> UIView {
        return view
    }
    func updateUIView(_ uiView: UIView, context: Context) {}
}

struct RemoteVideoView: UIViewRepresentable {
    let view:RendererView
    func makeUIView(context: Context) -> UIView {
        return view
    }
    func updateUIView(_ uiView: UIView, context: Context) {}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Autenticare il client

Per inizializzare un'istanza TeamsCallAgent, è necessario un token di accesso utente che consente di effettuare e ricevere chiamate. Se non è disponibile un token, vedere la documentazione dei token di accesso utente.

Dopo aver ottenuto un token, aggiungere il codice seguente al callback onAppear in ContentView.swift. È necessario sostituire <USER ACCESS TOKEN> con un token di accesso utente valido per la risorsa.

var userCredential: CommunicationTokenCredential?
do {
    userCredential = try CommunicationTokenCredential(token: "<USER ACCESS TOKEN>")
} catch {
    print("ERROR: It was not possible to create user credential.")
    return
}

Inizializzare Teams CallAgent e accedere a Gestione dispositivi

Per creare un'istanza di TeamsCallAgent da un CallClient, usare il metodo callClient.createTeamsCallAgent che restituisce in modo asincrono un oggetto TeamsCallAgent dopo l'inizializzazione. DeviceManager consente di enumerare i dispositivi locali che possono essere usati in una chiamata per trasmettere flussi audio/video. Consente inoltre di richiedere l'autorizzazione da un utente per accedere al microfono o alla fotocamera.

self.callClient = CallClient()
let options = TeamsCallAgentOptions()
// Enable CallKit in the SDK
options.callKitOptions = CallKitOptions(with: createCXProvideConfiguration())
self.callClient?.createTeamsCallAgent(userCredential: userCredential, options: options) { (agent, error) in
    if error != nil {
        print("ERROR: It was not possible to create a Teams call agent.")
        return
    } else {
        self.teamsCallAgent = agent
        print("Teams Call agent successfully created.")
        self.teamsCallAgent!.delegate = teamsIncomingCallHandler
        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")
            }
        }
    }
}

Richiedere le autorizzazioni

È necessario aggiungere il codice seguente al callback onAppear per richiedere autorizzazioni per audio e video.

AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
    if granted {
        AVCaptureDevice.requestAccess(for: .video) { (videoGranted) in
            /* NO OPERATION */
        }
    }
}

Effettuare una chiamata in uscita

Il metodo startCall viene impostato come azione che viene eseguita quando viene toccato il pulsante Start Call (Avvia chiamata). In questa guida introduttiva le chiamate in uscita sono solo audio per impostazione predefinita. Per avviare una chiamata con video, è necessario impostare VideoOptions con LocalVideoStream e passarlo con startCallOptions per impostare le opzioni iniziali per la chiamata.

let startTeamsCallOptions = StartTeamsCallOptions()
if sendingVideo  {
    if self.localVideoStream == nil  {
        self.localVideoStream = [LocalVideoStream]()
    }
    let videoOptions = VideoOptions(localVideoStreams: localVideoStream!)
    startTeamsCallOptions.videoOptions = videoOptions
}
let callees: [CommunicationIdentifier] = [CommunicationUserIdentifier(self.callee)]
self.teamsCallAgent?.startCall(participants: callees, options: startTeamsCallOptions) { (call, error) in
    // Handle call object if successful or an error.
}

Partecipare a una riunione di Teams

Il metodo join consente all'utente di partecipare a una riunione di team.

let joinTeamsCallOptions = JoinTeamsCallOptions()
if sendingVideo
{
    if self.localVideoStream == nil {
        self.localVideoStream = [LocalVideoStream]()
    }
    let videoOptions = VideoOptions(localVideoStreams: localVideoStream!)
    joinTeamsCallOptions.videoOptions = videoOptions
}

// Join the Teams meeting muted
if isMuted
{
    let outgoingAudioOptions = OutgoingAudioOptions()
    outgoingAudioOptions.muted = true
    joinTeamsCallOptions.outgoingAudioOptions = outgoingAudioOptions
}

let teamsMeetingLinkLocator = TeamsMeetingLinkLocator(meetingLink: "https://meeting_link")

self.teamsCallAgent?.join(with: teamsMeetingLinkLocator, options: joinTeamsCallOptions) { (call, error) in
    // Handle call object if successful or an error.
}

TeamsCallObserver e RemoteParticipantObserver vengono usati per gestire gli eventi di mid-call e i partecipanti remoti. Gli osservatori vengono impostati nella funzione setTeamsCallAndObserver.

func setTeamsCallAndObserver(call:TeamsCall, error:Error?) {
    if (error == nil) {
        self.teamsCall = call
        self.teamsCallObserver = TeamsCallObserver(self)
        self.teamsCall!.delegate = self.teamsCallObserver
        // Attach a RemoteParticipant observer
        self.remoteParticipantObserver = RemoteParticipantObserver(self)
    } else {
        print("Failed to get teams call object")
    }
}

Rispondere a una chiamata in arrivo

Per rispondere a una chiamata in arrivo, implementare un oggetto TeamsIncomingCallHandler per visualizzare il banner di chiamata in ingresso per rispondere o rifiutare la chiamata. Inserire l'implementazione seguente in TeamsIncomingCallHandler.swift.

final class TeamsIncomingCallHandler: NSObject, TeamsCallAgentDelegate, TeamsIncomingCallDelegate {
    public var contentView: ContentView?
    private var teamsIncomingCall: TeamsIncomingCall?

    private static var instance: TeamsIncomingCallHandler?
    static func getOrCreateInstance() -> TeamsIncomingCallHandler {
        if let c = instance {
            return c
        }
        instance = TeamsIncomingCallHandler()
        return instance!
    }

    private override init() {}
    
    func teamsCallAgent(_ teamsCallAgent: TeamsCallAgent, didReceiveIncomingCall incomingCall: TeamsIncomingCall) {
        self.teamsIncomingCall = incomingCall
        self.teamsIncomingCall.delegate = self
        contentView?.showIncomingCallBanner(self.teamsIncomingCall!)
    }
    
    func teamsCallAgent(_ teamsCallAgent: TeamsCallAgent, didUpdateCalls args: TeamsCallsUpdatedEventArgs) {
        if let removedCall = args.removedCalls.first {
            contentView?.callRemoved(removedCall)
            self.teamsIncomingCall = nil
        }
    }
}

È necessario creare un'istanza di TeamsIncomingCallHandler aggiungendo il codice seguente al callback onAppear in ContentView.swift:

Impostare un delegato su TeamsCallAgent dopo la creazione dell'oggetto TeamsCallAgent:

self.teamsCallAgent!.delegate = incomingCallHandler

Una volta eseguita una chiamata in arrivo, TeamsIncomingCallHandler chiama la funzione showIncomingCallBanner per visualizzare il pulsante answer e decline.

func showIncomingCallBanner(_ incomingCall: TeamsIncomingCall) {
    self.teamsIncomingCall = incomingCall
}

Le azioni associate a answer e decline vengono implementate come codice seguente. Per rispondere alla chiamata con il video, è necessario attivare il video locale e impostare le opzioni di AcceptCallOptions con localVideoStream.

func answerIncomingCall() {
    let options = AcceptTeamsCallOptions()
    guard let teamsIncomingCall = self.teamsIncomingCall else {
      print("No active incoming call")
      return
    }

    guard let deviceManager = deviceManager else {
      print("No device manager instance")
      return
    }

    if self.localVideoStreams == nil {
        self.localVideoStreams = [LocalVideoStream]()
    }

    if sendingVideo
    {
        guard let camera = deviceManager.cameras.first else {
            // Handle failure
            return
        }
        self.localVideoStreams?.append( LocalVideoStream(camera: camera))
        let videoOptions = VideoOptions(localVideoStreams: localVideosStreams!)
        options.videoOptions = videoOptions
    }

    teamsIncomingCall.accept(options: options) { (call, error) in
        // Handle call object if successful or an error.
    }
}

func declineIncomingCall() {
    self.teamsIncomingCall?.reject { (error) in 
        // Handle if rejection was successfully or not.
    }
}

Sottoscrivere eventi

È possibile implementare una classe TeamsCallObserver per sottoscrivere una raccolta di eventi per ricevere una notifica quando i valori cambiano durante la chiamata.

public class TeamsCallObserver: NSObject, TeamsCallDelegate, TeamsIncomingCallDelegate {
    private var owner: ContentView
    init(_ view:ContentView) {
            owner = view
    }
        
    public func teamsCall(_ teamsCall: TeamsCall, didChangeState args: PropertyChangedEventArgs) {
        if(teamsCall.state == CallState.connected) {
            initialCallParticipant()
        }
    }

    // render remote video streams when remote participant changes
    public func teamsCall(_ teamsCall: TeamsCall, didUpdateRemoteParticipant args: ParticipantsUpdatedEventArgs) {
        for participant in args.addedParticipants {
            participant.delegate = self.remoteParticipantObserver
        }
    }

    // Handle remote video streams when the call is connected
    public func initialCallParticipant() {
        for participant in owner.teamsCall.remoteParticipants {
            participant.delegate = self.remoteParticipantObserver
            for stream in participant.videoStreams {
                renderRemoteStream(stream)
            }
            owner.remoteParticipant = participant
        }
    }
}

Eseguire il codice

È possibile creare ed eseguire un'app nel simulatore iOS selezionando Prodotto >Esegui, o premendo i tasti di scelta rapida (⌘-R).

Pulire le risorse

Se si vuole pulire e rimuovere una sottoscrizione a Servizi di comunicazione, è possibile eliminare la risorsa o il gruppo di risorse. L'eliminazione del gruppo di risorse comporta anche l'eliminazione di tutte le altre risorse associate. Altre informazioni sulla pulizia delle risorse.

Passaggi successivi

Per altre informazioni, vedere gli articoli seguenti: