Démarrage rapide : ajouter un appel vidéo 1:1 à votre application en tant qu’utilisateur Teams

Démarrage avec Azure Communication Services à l’aide du Kit de développement logiciel (SDK) d’appel Communication Services pour ajouter des appels vocaux et vidéo individuels à votre application. Vous allez découvrir comment démarrer un appel et y répondre à l’aide du SDK d’appel Azure Communication Services pour JavaScript.

Exemple de code

Si vous souhaitez passer à la fin, vous pouvez télécharger ce guide de démarrage rapide en guise d’exemple sur GitHub.

Prérequis

Configuration

Création d’une application Node.js

Ouvrez votre fenêtre de terminal ou de commande, créez un répertoire pour votre application, puis accédez-y.

mkdir calling-quickstart && cd calling-quickstart

Exécutez npm init -y pour créer un fichier package.json avec les paramètres par défaut.

npm init -y

Installer le package

Utilisez la commande npm install pour installer le SDK Azure Communication Services Calling pour JavaScript.

Important

Ce guide de démarrage rapide utilise la dernière version du Kit de développement logiciel (SDK) Azure Communication Services Calling.

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

Configurer le framework d’application

Ce guide de démarrage rapide utilise webpack pour regrouper les ressources de l’application. Exécutez la commande suivante pour installer les packages npm webpack, webpack-cli et webpack-dev-server, puis listez-les comme dépendances de développement dans votre fichier 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

Créez un fichier index.html dans le répertoire racine de votre projet. Nous utiliserons ce fichier pour configurer une disposition de base qui permettra à l’utilisateur d’effectuer un appel vidéo 1 à 1.

Voici le code :

<!-- 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>

Modèle d’objet Azure Communication Services Calling Web SDK

Les classes et les interfaces suivantes gèrent certaines des principales fonctionnalités du SDK Appel Azure Communication Services :

Nom Description
CallClient Point d’entrée principal du SDK Appel.
AzureCommunicationTokenCredential Implémente l’interface CommunicationTokenCredential, qui est utilisée pour instancier teamsCallAgent.
TeamsCallAgent Sert à démarrer et à gérer les appels Teams.
DeviceManager Utilisé pour gérer les appareils multimédias.
TeamsCall Utilisé pour représenter un appel Teams
LocalVideoStream Utilisé pour créer un flux vidéo local pour un appareil photo sur le système local.
RemoteParticipant Utilisé pour représenter un participant distant dans l’appel
RemoteVideoStream Utilisé pour représenter un flux vidéo distant d’un participant distant.

Créez un fichier dans le répertoire racine de votre projet sous le nom index.js qui contiendra la logique d’application pour ce guide de démarrage rapide. Ajoutez le code suivant à 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 udpates.
subscribeToCall = (call) => {
    try {
        // Inspect the initial call.id value.
        console.log(`Call Id: ${call.id}`);
        //Subsribe 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 udpates.
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 remoteParticiapant 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();
});

Ajouter le code du serveur local webpack

Créez un fichier dans le répertoire racine de votre projet sous le nom webpack.config.js qui contiendra la logique de serveur local pour ce guide de démarrage rapide. Ajoutez le code suivant à 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'
            ]
        }),
    ]
};

Exécuter le code

Utilisez webpack-dev-server pour créer et exécuter votre application. Exécutez la commande suivante pour créer un bundle de l’application hôte sur un serveur web local :

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

Ouvrez votre navigateur, puis accédez à http://localhost:8080/. sous deux onglets Les onglets devraient montrer un résultat similaire à l’image suivante : La capture d’écran montre deux onglets dans la vue par défaut. Chaque onglet sera utilisé pour différents utilisateurs Teams.

Dans le premier onglet, entrez un jeton d’accès utilisateur valide. Sous le deuxième onglet, entrez un jeton d’accès utilisateur valide différent. Consultez la documentation sur les jetons d’accès utilisateur si vous n’avez pas encore de jeton d’accès disponible. Sous les deux onglets, cliquez sur le bouton « Initialiser l’agent d’appel ». Les onglets devraient montrer un résultat similaire à l’image suivante : La capture d’écran montre les étapes pour initialiser chaque utilisateur Teams dans l’onglet de navigateur.

Sous le premier onglet, entrez l’identité de l’utilisateur Azure Communication Services du deuxième onglet, puis sélectionnez le bouton « Démarrer l’appel ». Le premier onglet démarre l’appel sortant vers le deuxième onglet, et le bouton « Accepter l’appel » du deuxième onglet est activé : La capture d’écran montre l’expérience lorsque les utilisateurs Teams initialisent le SDK et montre les étapes pour démarrer un appel vers le deuxième utilisateur et la façon d’accepter l’appel.

Dans le deuxième onglet, sélectionnez le bouton « Accepter l’appel ». L’appel est pris et connecté. Les onglets devraient montrer un résultat similaire à l’image suivante : La capture d’écran montre deux onglets, avec un appel en cours entre deux utilisateurs Teams, chacun étant connecté dans un onglet individuel.

Les deux onglets se trouvent maintenant dans un appel vidéo 1:1. Les deux utilisateurs peuvent entendre l’audio de l’autre et voir le flux vidéo de l’autre.

Démarrage avec Azure Communication Services à l’aide du Kit de développement logiciel (SDK) d’appel Communication Services pour ajouter des appels vocaux et vidéo individuels à votre application. Vous allez découvrir comment passer un appel et répondre à un appel à l’aide du kit de développement logiciel (SDK) d’appel Azure Communication Services pour Windows.

Exemple de code

Si vous souhaitez passer à la fin, vous pouvez télécharger ce guide de démarrage rapide en guise d’exemple sur GitHub.

Prérequis

Pour effectuer ce didacticiel, vous avez besoin de ce qui suit :

Configuration

Création du projet

Dans Visual Studio, créez un projet avec le modèle Application vide (Windows universel) pour configurer une application de plateforme Windows universelle (UWP) monopage.

Capture d’écran montrant la fenêtre Nouveau projet UWP dans Visual Studio.

Installer le package

Sélectionnez votre projet à droite et accédez à Manage Nuget Packages pour installer la version Azure.Communication.Calling.WindowsClient1.2.0-beta.1 ou ultérieure. Vérifiez que l’option Inclure la préversion est activée.

Demander l'accès

Accédez à Package.appxmanifest et sélectionnez Capabilities. Cochez la case Internet (Client) et Internet (Client & Server) pour obtenir un accès entrant et sortant à Internet. Vérifiez Microphone pour accéder au flux audio du microphone et Webcam au flux vidéo de la caméra.

Capture d’écran montrant la demande d’accès à Internet et au microphone dans Visual Studio.

Configurer le framework d’application

Nous devons configurer une disposition de base pour attacher notre logique. Afin de passer un appel sortant, nous avons besoin d’une TextBox pour fournir l’ID d’utilisateur de l’appelé. Nous avons également besoin d’un bouton Start/Join call et d’un bouton Hang up. Des Mute cases à cocher et un BackgroundBlur sont également inclus dans cet exemple pour illustrer les fonctionnalités de basculement des états audio et des effets vidéo.

Ouvrez le fichier MainPage.xaml de votre projet et ajoutez le nœud Grid à votre 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>

Ouvrez le fichier MainPage.xaml.cs et remplacez le contenu par l’implémentation suivante :

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

Modèle objet

Le tableau suivant a répertorié les classes et interfaces suivantes gèrent certaines des principales fonctionnalités du SDK Appel d’Azure Communication Services :

Nom Description
CallClient CallClient est le point d’entrée principal du SDK Appel.
TeamsCallAgent TeamsCallAgent sert à démarrer et à gérer les appels.
TeamsCommunicationCall Le TeamsCommunicationCall est utilisé pour gérer un appel en cours.
CallTokenCredential CallTokenCredential sert de jeton d’informations d'identification pour initier le TeamsCallAgent.
CallIdentifier Le CallIdentifier est utilisé pour représenter l'identité de l'utilisateur, qui peut être l'une des options suivantes : MicrosoftTeamsUserCallIdentifier, UserCallIdentifier, PhoneNumberCallIdentifier, etc.

Authentifier le client

Initialisez une instance TeamsCallAgent avec un jeton d’accès utilisateur qui permet de passer et de recevoir des appels, et éventuellement d’obtenir une instance DeviceManager pour rechercher des configurations d’appareil client.

Dans le code, remplacez <AUTHENTICATION_TOKEN> par un jeton d’accès utilisateur. Consultez la documentation sur les jetons d’accès utilisateur si vous n’avez pas encore de jeton disponible.

Ajouter InitCallAgentAndDeviceManagerAsync une fonction, qui démarre le KIT de développement logiciel (SDK). Cette assistance peut être personnalisée pour répondre aux exigences de votre application.

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

Démarrer un appel

Ajoutez l’implémentation à pour CallButton_Click démarrer différents types d’appels avec l’objet teamsCallAgent que nous avons créé, et branchez RemoteParticipantsUpdated et StateChanged gestionnaires d’événements sur TeamsCommunicationCall l’objet.

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

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

Terminer un appel

Terminez l’appel en cours quand l’utilisateur clique sur le bouton Hang up. Ajoutez l’implémentation à l’HangupButton_Click pour mettre fin à un appel et arrêter les flux d’aperçu et de vidéo.

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

Activer/désactiver le son

Désactivez le son sortant lorsque vous cliquez sur le Mute bouton. Ajoutez l’implémentation à la MuteLocal_Click pour désactiver l’appel.

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

Commencer l’appel

Une fois un objet StartTeamsCallOptions obtenu, TeamsCallAgent peut être utilisé pour passer l'appel Teams :

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

Acceptation d’un appel entrant

TeamsIncomingCallReceived le récepteur d’événements est configuré dans l’assistance InitCallAgentAndDeviceManagerAsyncd’amorçage du KIT de développement logiciel (SDK).

    this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;

L’application a la possibilité de configurer la façon dont l’appel entrant doit être accepté, comme les types de flux vidéo et 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;
        }

Rejoindre un appel Teams

L’utilisateur peut également rejoindre un appel existant en transmettant un lien

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

analyser un événement de changement d'état d'appel et y répondre

L’événement StateChanged sur l’objet TeamsCommunicationCall est déclenché lorsqu’un appel en cours de transactions d’un état à un autre. L’application a la possibilité de refléter les changements d’état sur l’interface utilisateur ou d’insérer des logiques métier.

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

Exécuter le code

Vous pouvez générer et exécuter le code sur Visual Studio. Les plateformes de solution que nous prenons en charge sont ARM64, x64 et x86.

Vous pouvez passer un appel sortant en entrant un ID d’utilisateur dans le champ de texte, puis en cliquant sur le bouton Start Call/Join. L’appel de 8:echo123 vous connecte à un bot d’écho, cette fonctionnalité est parfaite pour démarrer et vérifier que vos périphériques audio fonctionnent.

Capture d’écran montrant l’exécution de l’application de démarrage rapide UWP

Démarrage avec Azure Communication Services à l’aide du Kit de développement logiciel (SDK) d’appel Communication Services pour ajouter des appels vocaux et vidéo individuels à votre application. Vous saurez comment passer un appel et à y répondre à l'aide du kit de développement logiciel (SDK) d'appel Azure Communication Services pour Java.

Exemple de code

Si vous souhaitez passer à la fin, vous pouvez télécharger ce guide de démarrage rapide en guise d’exemple sur GitHub.

Prérequis

Configuration

Créer une application Android avec une activité vide

Dans Android Studio, sélectionnez Start a new Android Studio project (Commencer un nouveau projet Android Studio).

Capture d’écran montrant le bouton « Start a new Android Studio Project » sélectionné dans Android Studio.

Sélectionnez le modèle de projet « Empty Activity » sous « Phone and Tablet ».

Capture d’écran montrant l’option « Empty Activity » sélectionnée dans l’écran Project Template.

Sélectionnez le kit de développement logiciel (SDK) minimal « API 26 : Android 8.0 (Oreo) » ou version ultérieure.

Capture d’écran montrant l’option « Empty Activity » sélectionnée dans l’écran 2 Project Template.

Installer le package

Recherchez votre projet build.gradle et veillez à ajouter mavenCentral() à la liste des référentiels sous buildscript et allprojects.

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

Ensuite, dans votre niveau de module build.gradle, ajoutez les lignes suivantes aux sections dependencies et 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'
    ...
}

Ajouter des autorisations au manifeste de l’application

Afin de pouvoir demander les autorisations nécessaires pour effectuer un appel, vous devez les déclarer dans le manifeste de l’application (app/src/main/AndroidManifest.xml). Remplacez le contenu du fichier par ce code :

    <?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>
    

Configurer la disposition de l’application

Deux entrées sont nécessaires : une entrée de texte pour l’ID de l’appelé et un bouton pour établir l’appel. Ces entrées peuvent être ajoutées par le biais du concepteur ou en modifiant le fichier XML de disposition. Créez un bouton avec call_button comme ID et callee_id comme entrée de texte. Accédez à (app/src/main/res/layout/activity_main.xml) et remplacez le contenu du fichier par ce code :

<?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>

Créer la génération de modèles automatique et les liaisons de l’activité principale

La mise en page étant créée, nous pouvons ajouter les liaisons ainsi que la génération de modèles automatique de base de l’activité. L'activité décrit la requête d'autorisations de runtime, la création de l'agent chargé de l'appel Teams, et l'émission de l'appel une fois le bouton appuyé. Chacune de ces opérations est traitée dans sa propre section. La méthode onCreate est remplacée pour appeler getAllPermissions et createTeamsAgent, et pour ajouter les liaisons pour le bouton d’appel. Cet événement ne se produit qu’une seule fois lors de la création de l’activité. Pour plus d’informations sur onCreate, consultez le guide Présentation du cycle de vie des activités.

Accédez à MainActivity.java et remplacez le contenu par le code suivant :

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

Demander des autorisations au moment de l’exécution

Pour Android 6.0 et ultérieur (niveau d’API 23) et targetSdkVersion 23 ou plus, les autorisations sont accordées au moment de l’exécution et non lors de l’installation de l’application. Pour le prendre en charge, vous pouvez implémenter getAllPermissions afin d’appeler ActivityCompat.checkSelfPermission et ActivityCompat.requestPermissions pour chaque autorisation requise.

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

Notes

Lorsque vous concevez votre application, tenez compte du moment où ces autorisations doivent être demandées. Les autorisations doivent être demandées lorsqu’elles sont nécessaires, et non à l’avance. Pour plus d’informations, consultez le guide des autorisations Android.

Modèle objet

Les classes et les interfaces suivantes gèrent certaines des principales fonctionnalités du SDK Azure Communication Services Calling :

Nom Description
CallClient CallClient est le point d’entrée principal du SDK Appel.
TeamsCallAgent TeamsCallAgent sert à démarrer et à gérer les appels.
TeamsCall Le TeamsCall est utilisé pour représenter un appel Teams.
CommunicationTokenCredential CommunicationTokenCredential sert de jeton d’informations d'identification pour initier le TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier sert de type de participant différent susceptible de faire partie d’un appel.

Créer un agent à partir du jeton d’accès utilisateur

Avec un jeton utilisateur, un agent d’appel authentifié peut être instancié. En règle général, ce jeton est généré à partir d’un service avec une authentification propre à l’application. Pour plus d’informations sur les jetons d’accès utilisateur, consultez le guide Jetons d’accès utilisateur.

Pour les besoins de ce guide de démarrage rapide, remplacez <User_Access_Token> par un jeton d’accès utilisateur généré pour votre ressource Azure Communication Services.


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

Démarrer un appel à l’aide de l’agent d’appel

L'appel peut être passé par l'agent chargé de l'appel Teams, et il suffit de fournir une liste des ID d'appelés et les options d'appel. Pour les besoins de ce guide de démarrage rapide, nous utilisons les options d’appel par défaut sans vidéo et un ID d’appel unique tiré de l’entrée de texte.

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

Répondre à un appel

L’acceptation d’un appel peut s’effectuer à l’aide de l’agent d’appel Teams en utilisant uniquement une référence au contexte actuel.

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

Rejoindre un appel Teams

Un utilisateur peut rejoindre un appel existant en transmettant un lien.

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

Rejoindre un appel Teams avec des options

Nous pouvons également rejoindre un appel existant avec des options prédéfinies, telles que la désactivation du micro.

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

Configurer l’écouteur d’appel entrant

Pour qu’il soit possible de détecter les appels entrants et d’autres actions non effectuées par cet utilisateur, des écouteurs doivent être configurés.

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

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

Lancer l’application et appeler le bot d’écho

L’application peut maintenant être lancée à l’aide du bouton « Run App » de la barre d’outils (Maj+F10). Vérifiez que vous êtes en mesure d’effectuer des appels en appelant 8:echo123. Un message pré-enregistré est lu, puis vous répète votre propre message.

Capture d’écran montrant l’application terminée.

Commencez à utiliser Azure Communication Services avec le kit de développement logiciel (SDK) d’appels Communication Services pour ajouter un des appels vidéo à votre application. Vous saurez comment passer un appel vidéo et à y répondre à l'aide du kit de développement logiciel (SDK) d'appel Azure Communication Services pour iOS en utilisant l'identité Teams.

Exemple de code

Si vous souhaitez passer à la fin, vous pouvez télécharger ce guide de démarrage rapide en guise d’exemple sur GitHub.

Prérequis

Configuration

Création du projet Xcode

Dans Xcode, créez un projet iOS et sélectionnez le modèle Single View App. Ce tutoriel utilise le framework SwiftUI. Vous devez donc définir Swift comme langage et SwiftUI comme interface utilisateur. Vous n’allez pas créer de tests au cours de ce guide démarrage rapide. N’hésitez pas à décocher Inclure des tests.

Capture d’écran représentant la fenêtre Nouveau projet dans Xcode.

Installation de CocoaPods

Utilisez ce guide pour installer CocoaPods sur votre Mac.

Installer le package et les dépendances avec CocoaPods

  1. Pour créer un Podfile pour votre application, ouvrez le terminal et accédez au dossier du projet, puis exécutez pod init.

  2. Ajoutez le code suivant à Podfile et enregistrez. Consultez les versions de prise en charge du SDK.

platform :ios, '13.0'
use_frameworks!

target 'VideoCallingQuickstart' do
  pod 'AzureCommunicationCalling', '~> 2.10.0'
end
  1. Exécutez pod install.

  2. Ouvrez le .xcworkspace avec Xcode.

Demandez l’accès au microphone et à la caméra.

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

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

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

Configurer le framework d’application

Ouvrez le fichier ContentView.swift de votre projet et ajoutez une déclaration d’importation en haut du fichier pour importer la bibliothèque AzureCommunicationCalling et AVFoundation. AVFoundation est utilisé pour capturer l’autorisation audio à partir du code.

import AzureCommunicationCalling
import AVFoundation

Modèle objet

Les classes et les interfaces suivantes gèrent certaines des principales fonctionnalités du kit de développement logiciel (SDK) Azure Communication Services Calling pour iOS.

Nom Description
CallClient CallClient est le point d’entrée principal du SDK Appel.
TeamsCallAgent TeamsCallAgent sert à démarrer et à gérer les appels.
TeamsIncomingCall Le TeamsIncomingCall est utilisé pour accepter ou rejeter les appels Teams entrants.
CommunicationTokenCredential CommunicationTokenCredential sert de jeton d’informations d'identification pour initier le TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier sert à représenter l’identité de l’utilisateur, parmi l’une des options suivantes : CommunicationUserIdentifier, PhoneNumberIdentifier ou CallingApplication.

Créer l'agent chargé de l'appel Teams

Remplacez l'implémentation du ContentView struct par des contrôles d'interface utilisateur simples qui permettent à un utilisateur de passer et de terminer un appel. Dans ce démarrage rapide, nous attacherons une logique métier à ces contrôles.

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

Authentifier le client

Pour initialiser une instance TeamsCallAgent, il vous faut un jeton d’accès utilisateur qui permet d’effectuer et de recevoir des appels. Consultez la documentation sur les jetons d’accès utilisateur si vous ne disposez pas encore de jeton disponible.

Une fois que vous avez le jeton, ajoutez le code suivant au rappel onAppear dans ContentView.swift. Vous devez remplacer <USER ACCESS TOKEN> par un jeton d’accès utilisateur valide pour votre ressource :

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

Initialiser le Teams CallAgent et accéder au gestionnaire d'appareils

Pour créer une instance TeamsCallAgent à partir d'un CallClient, utilisez la méthode callClient.createTeamsCallAgent qui renvoie de manière asynchrone un objet TeamsCallAgent une fois qu'il a été initialisé. DeviceManager permet d’énumérer les périphériques locaux qui peuvent être utilisés dans un appel pour transmettre des flux audio/vidéo. Il vous permet également de demander l’autorisation à un utilisateur d’accéder au microphone/à la caméra.

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

Demander des autorisations

Nous avons besoin d’ajouter le code suivant au rappel onAppear pour demander des autorisations pour l’audio et la vidéo.

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

Passer un appel sortant

La méthode startCall est définie en tant qu’action qui sera exécutée lors d’un appui sur le bouton Démarrer l’appel. Dans ce guide de démarrage rapide, les appels sortants sont uniquement audio par défaut. Pour démarrer un appel avec la vidéo, vous avez besoin de définir VideoOptions avec LocalVideoStream et de le passer avec startCallOptions pour définir les options initiales de l’appel.

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

Rejoindre une réunion Teams

La méthode join permet à l’utilisateur de participer à une réunion Teams.

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 et RemotePariticipantObserver sont utilisés pour gérer les événements durant l’appel et les participants distants. Nous définissons les observateurs dans la fonction 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")
    }
}

Répondre à un appel entrant

Pour répondre à un appel entrant, implémentez un TeamsIncomingCallHandler pour afficher la bannière d’appel entrant afin de répondre à cet appel ou de le refuser. Placez l’implémentation suivante dans 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, didRecieveIncomingCall 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
        }
    }
}

Nous avons besoin de créer une instance de TeamsIncomingCallHandler en ajoutant le code suivant au rappel onAppear dans ContentView.swift :

Définissez un délégué pour TeamsCallAgent à la suite de la création de TeamsCallAgent :

self.teamsCallAgent!.delegate = incomingCallHandler

Une fois qu’il y a un appel entrant, le TeamsIncomingCallHandler appelle la fonction showIncomingCallBanner pour afficher les boutons answer et decline.

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

Les actions attachées à answer et decline sont implémentées dans le code ci-dessous. Pour répondre à l’appel avec la vidéo, nous devons activer la vidéo locale et définir les options de AcceptCallOptions avec 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.
    }
}

S’abonner à des événements

Nous pouvons implémenter une classe TeamsCallObserver pour nous abonner à une collecte d’événements afin d’être avertis quand des valeurs changent pendant l’appel.

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

Exécuter le code

Vous pouvez générer et exécuter votre application sur un simulateur iOS en sélectionnant Product > Run ou en utilisant le raccourci clavier (⌘-R).

Nettoyer les ressources

Si vous voulez nettoyer et supprimer un abonnement Communication Services, vous pouvez supprimer la ressource ou le groupe de ressources. La suppression du groupe de ressources efface également les autres ressources qui y sont associées. Apprenez-en davantage sur le nettoyage des ressources.

Étapes suivantes

Pour plus d’informations, consultez les articles suivants :