Schnellstart: Hinzufügen von 1:1-Videoanrufen zu Ihrer Anwendung als Teams-Benutzer

Steigen Sie in Azure Communication Services ein, indem Sie das Calling-SDK von Communication Services nutzen, um Ihrer App 1:1-Sprach- und Videoanrufe hinzuzufügen. Hier erfahren Sie, wie Sie einen Anruf mithilfe des Calling SDK von Azure Communication Services für JavaScript beginnen und beantworten.

Beispielcode

Wenn Sie direkt zum Ende springen möchten, können Sie diese Schnellstartanleitung als Beispiel auf GitHub herunterladen.

Voraussetzungen

Einrichten

Erstellen einer neuen Node.js-Anwendung

Öffnen Sie das Terminal- oder Befehlsfenster, erstellen Sie ein neues Verzeichnis für Ihre App, und navigieren Sie zu diesem Verzeichnis.

mkdir calling-quickstart && cd calling-quickstart

Führen Sie npm init -y aus, um die Datei package.json mit den Standardeinstellungen zu erstellen.

npm init -y

Installieren des Pakets

Verwenden Sie den Befehl npm install, um das Azure Communication Services Calling SDK für JavaScript zu installieren.

Wichtig

In dieser Schnellstartanleitung wird die aktuelle Version des Azure Communication Services Calling SDK verwendet.

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

Einrichten des App-Frameworks

In dieser Schnellstartanleitung wird Webpack verwendet, um die Anwendungsressourcen zu bündeln. Führen Sie den folgenden Befehl aus, um die npm-Pakete webpack, webpack-cli und webpack-dev-server zu installieren und diese als Entwicklungsabhängigkeiten in package.json aufzulisten:

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

Erstellen Sie im Stammverzeichnis Ihres Projekts die Datei index.html. Diese Datei wird zum Konfigurieren eines grundlegenden Layouts verwendet, das es dem Benutzer ermöglicht, einen 1:1-Videoanruf zu tätigen.

Der Code lautet wie folgt:

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

Objektmodell des Azure Communication Services Calling Web SDK

Die folgenden Klassen und Schnittstellen befassen sich mit einigen der wichtigsten Features des Azure Communication Services Calling SDK:

Name BESCHREIBUNG
CallClient Der Haupteinstiegspunkt des Calling SDK.
AzureCommunicationTokenCredential Implementiert die CommunicationTokenCredential-Schnittstelle zum Instanziieren von teamsCallAgent.
TeamsCallAgent Dient zum Starten und Verwalten von Teams-Anrufen.
DeviceManager Dient zum Verwalten von Mediengeräten.
TeamsCall Dient zum Darstellen eines Teams-Anrufs.
LocalVideoStream Dient zum Erstellen eines lokalen Videostreams für ein Kameragerät auf dem lokalen System.
RemoteParticipant Dient zum Darstellen eines Remoteteilnehmers für den Anruf.
RemoteVideoStream Dient zum Darstellen eines Remotevideostreams von einem Remoteteilnehmer.

Erstellen Sie im Stammverzeichnis Ihres Projekts eine Datei mit dem Namen index.js, die die Anwendungslogik für diese Schnellstartanleitung enthalten soll. Fügen Sie „index.js“ den folgenden Code hinzu:

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

Hinzufügen des lokalen Webpack-Servercodes

Erstellen Sie im Stammverzeichnis Ihres Projekts eine Datei mit dem Namen webpack.config.js, die die lokale Serverlogik für diesen Schnellstart enthalten soll. Fügen Sie der Datei webpack.config.js den folgenden Code hinzu:

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'
            ]
        }),
    ]
};

Ausführen des Codes

Verwenden Sie webpack-dev-server, um Ihre App zu erstellen und auszuführen. Führen Sie den folgenden Befehl aus, um den Anwendungshost auf einem lokalen Webserver zu bündeln:

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

Öffnen Sie Ihren Browser, und navigieren Sie auf zwei Registerkarten zu http://localhost:8080/.. Die Registerkarten sollten ein ähnliches Ergebnis wie in der folgenden Abbildung anzeigen: Screenshot mit zwei Registerkarten in der Standardansicht. Sie werden jeweils für verschiedene Teams-Benutzer verwendet.

Geben Sie auf der ersten Registerkarte ein gültiges Benutzerzugriffstoken ein. Geben Sie auf der zweiten Registerkarte ein anderes gültiges Benutzerzugriffstoken ein. Wenn Sie noch über kein Zugriffstoken verfügen, finden Sie in der Dokumentation zu Benutzerzugriffstoken weitere Informationen. Klicken Sie auf beiden Registerkarten auf die Schaltflächen „Initialize Call Agent“ (Anruf-Agent initialisieren). Die Registerkarten sollten ein ähnliches Ergebnis wie in der folgenden Abbildung anzeigen: Screenshot mit Schritten zum Initialisieren jedes Teams-Benutzers auf der Browserregisterkarte

Geben Sie auf der ersten Registerkarte die Azure Communication Services-Benutzeridentität der zweiten Registerkarte ein, und klicken Sie auf die Schaltfläche „Anruf starten“. Auf der ersten Registerkarte wird der ausgehende Aufruf der zweiten Registerkarte gestartet, und die Schaltfläche „Accept Call“ (Anruf annehmen) der zweiten Registerkarte wird aktiviert: Screenshot zeigt die Anwendung bei der Initialisierung des SDK durch die Teams-Benutzer, Schritte zum Starten eines Anrufs des zweiten Benutzers sowie die Option zum Annehmen des Anrufs.

Klicken Sie auf der zweiten Registerkarte auf die Schaltfläche „Accept Call“ (Anruf annehmen). Der Anruf wird angenommen, und eine Verbindung wird hergestellt. Die Registerkarten sollten ein ähnliches Ergebnis wie in der folgenden Abbildung anzeigen: Screenshot zeigt zwei Registerkarten mit einem aktiven Anruf zwischen zwei Teams-Benutzern auf jeweils einer eigenen Registerkarte.

Auf beiden Registerkarten ist nun ein 1:1-Videoanruf erfolgreich hergestellt worden. Beide Benutzer können die Audioausgabe des jeweils anderen hören und den Videostream sehen.

Steigen Sie in Azure Communication Services ein, indem Sie das Calling-SDK von Communication Services nutzen, um Ihrer App 1:1-Sprach- und Videoanrufe hinzuzufügen. Hier erfahren Sie, wie Sie einen Anruf mithilfe des Calling SDK von Azure Communication Services für Windows beginnen und beantworten.

Beispielcode

Wenn Sie direkt zum Ende springen möchten, können Sie diese Schnellstartanleitung als Beispiel auf GitHub herunterladen.

Voraussetzungen

Zum Durchführen dieses Tutorials benötigen Sie Folgendes:

Einrichten

Erstellen des Projekts

Erstellen Sie in Visual Studio ein neues Projekt mit der Vorlage Leere App (Universelle Windows-App) , um eine einseitige UWP-App (Universelle Windows-Plattform) einzurichten.

Screenshot des Fensters „Neues UWP Projekt“ in Visual Studio.

Installieren des Pakets

Klicken Sie mit der rechten Maustaste auf Ihr Projekt, und wechseln Sie zu Manage Nuget Packages, um Azure.Communication.Calling.WindowsClient1.2.0-beta.1 oder höher zu installieren. Vergewissern Sie sich, dass „Vorabversion einbeziehen“ aktiviert ist.

Anfordern des Zugriffs

Wechseln Sie zu Package.appxmanifest, und wählen Sie Capabilities aus. Aktivieren Sie Internet (Client) und Internet (Client & Server), um ein- und ausgehenden Zugriff auf das Internet zu erhalten. Markieren Sie Microphone, um auf den Audiofeed des Mikrofons zuzugreifen und Webcam, um auf den Videofeed der Kamera zuzugreifen.

Screenshot: Anfordern des Zugriffs auf das Internet und das Mikrofon in Visual Studio

Einrichten des App-Frameworks

Wir müssen ein einfaches Layout konfigurieren, um unsere Logik anzufügen. Um einen ausgehenden Anruf zu tätigen, benötigen wir ein TextBox-Element, um die Benutzer-ID des Angerufenen bereitzustellen. Außerdem benötigen wir die Schaltflächen Start/Join call und Hang up. In diesem Beispiel sind auch die Kontrollkästchen A Mute und a BackgroundBlur enthalten, um die Funktionen des Umschaltens von Audiozuständen und Videoeffekten zu veranschaulichen.

Öffnen Sie das MainPage.xaml-Element Ihres Projekts, und fügen Sie Ihrer Page den Knoten Grid hinzu:

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

Öffnen Sie MainPage.xaml.cs, und ersetzen Sie den Inhalt durch folgende Implementierung:

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

Objektmodell

Die folgende Tabelle führt die Klassen und Schnittstellen auf, die sich mit einigen der wichtigsten Features des Calling SDK von Azure Communication Services befassen:

Name BESCHREIBUNG
CallClient CallClient ist der Haupteinstiegspunkt des Calling SDK.
TeamsCallAgent TeamsCallAgent dient zum Starten und Verwalten von Anrufen.
TeamsCommunicationCall TeamsCommunicationCall wird verwendet, um einen laufenden Anruf zu verwalten.
CallTokenCredential CallTokenCredential dient als tokengestützte Anmeldeinformation zum Instanziieren von TeamsCallAgent.
CallIdentifier CallIdentifier wird zur Darstellung der Identität des Benutzers verwendet, die eine der folgenden Optionen sein kann: MicrosoftTeamsUserCallIdentifier, UserCallIdentifier, PhoneNumberCallIdentifier usw.

Authentifizieren des Clients

Initialisieren Sie eine TeamsCallAgent-Instanz mit einem Benutzerzugriffs-Token, mit dem wir Anrufe tätigen und empfangen können, und rufen Sie optional eine DeviceManager-Instanz ab, um auf Client-Gerätekonfigurationen abzufragen.

Ersetzen Sie im folgenden Code <AUTHENTICATION_TOKEN> durch ein Benutzerzugriffs-Token. Wenn Sie noch über kein Token verfügen, finden Sie in der Dokumentation zu Benutzerzugriffstoken weitere Informationen.

Fügen Sie die Funktion InitCallAgentAndDeviceManagerAsync hinzu , die das SDK lädt. Dieses Hilfsprogramm kann an die Anforderungen Ihrer Anwendung angepasst werden.

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

Beginnen eines Anrufs

Fügen Sie die Implementierung zum CallButton_Click hinzu, um verschiedene Arten von Aufrufen mit dem von uns erstellten Objekt teamsCallAgent zu starten, und binden Sie die Ereignishandler RemoteParticipantsUpdated und StateChanged für das Objekt TeamsCommunicationCall ein.

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

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

Beenden eines Anrufs

Beenden Sie den aktuellen Anruf, nachdem auf die Schaltfläche Hang up geklickt wurde. Fügen Sie die Implementierung zum „HangupButton_Click“ hinzu, um einen Anruf zu beenden, und beenden Sie die Vorschau und Videostreams.

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

Stummschalten/Lautschalten bei Audio

Schalten Sie das ausgehende Audio stumm, wenn auf die Schaltfläche Mute geklickt wird. Fügen Sie die Implementierung zum „MuteLocal_Click“ hinzu, um den Aufruf stummzuschalten.

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

Starten des Anrufs

Sobald ein Objekt StartTeamsCallOptions abgerufen wurde, kann TeamsCallAgent zum Initiieren des Teams-Anrufs verwendet werden:

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

Annehmen eines eingehenden Anrufs

Die Ereignissenke TeamsIncomingCallReceived ist im SDK-Bootstrap-Hilfsprogramm InitCallAgentAndDeviceManagerAsync eingerichtet.

    this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;

Die Anwendung hat die Möglichkeit, zu konfigurieren, wie der eingehende Anruf angenommen werden soll, z. B. Video- und Audiostreamtypen.

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

            var acceptteamsCallOptions = new AcceptTeamsCallOptions() { };

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

Einem Teams-Anruf beitreten

Ein Benutzer kann auch einem vorhandenen Anruf beitreten, indem er einen Link übergibt

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

Überwachen und Reagieren auf ein Anrufstatusänderungsereignis

Das Ereignis StateChanged für das Objekt TeamsCommunicationCall wird ausgelöst, wenn ein gerade ausgeführter Vorgang Transaktionen von einem Zustand in einen anderen aufruft. Der Anwendung wird die Möglichkeit geboten, die Zustandsänderungen auf der Benutzeroberfläche widerzuspiegeln oder Geschäftslogiken einzufügen.

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

Ausführen des Codes

Sie können den Build in Visual Studio erstellen und den Code ausführen. Für Lösungsplattformen unterstützen wir ARM64, x64 und x86.

Sie können einen ausgehenden Anruf tätigen, indem Sie eine Benutzer-ID im Textfeld eingeben und auf die Schaltfläche Start Call/Join klicken. Wenn Sie 8:echo123 anrufen, werden Sie mit einem Echo-Bot verbunden. Dieses Feature eignet sich hervorragend für die ersten Schritte und zum Überprüfen, ob Ihre Audiogeräte funktionieren.

Screenshot: Ausführen der UWP-Schnellstart-App

Steigen Sie in Azure Communication Services ein, indem Sie das Calling-SDK von Communication Services nutzen, um Ihrer App 1:1-Sprach- und Videoanrufe hinzuzufügen. Hier erfahren Sie, wie Sie einen Anruf mithilfe des Calling SDK von Azure Communication Services für Java beginnen und beantworten.

Beispielcode

Wenn Sie direkt zum Ende springen möchten, können Sie diese Schnellstartanleitung als Beispiel auf GitHub herunterladen.

Voraussetzungen

Einrichten

Erstellen Sie eine Android-App mit einer leeren Aktivität.

Wählen Sie in Android Studio „Start a new Android Studio project“ (Neues Android Studio-Projekt starten) aus.

Ein Screenshot, der die Schaltfläche „Start a new Android Studio project“ (Neues Android Studio-Projekt starten) zeigt, die in Android Studio ausgewählt ist

Wählen Sie unter „Phone and Tablet“ (Telefon und Tablet) die Projektvorlage „Empty Activity“ (Leere Aktivität) aus.

Screenshot, der die Option „Empty Activity“ (Leere Aktivität) zeigt, die auf dem Bildschirm für die Projektvorlage ausgewählt ist

Wählen Sie das SDK „API 26: Android 8.0 (Oreo)“ oder höher aus.

Screenshot, der die Option „Empty Activity“ (Leere Aktivität) zeigt, die auf dem Bildschirm für die Projektvorlage 2 ausgewählt ist

Installieren des Pakets

Suchen Sie die Datei „build.gradle“ auf Projektebene, und fügen Sie mavenCentral() zur Liste der Repositorys unter buildscript und allprojects hinzu.

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

Fügen Sie anschließend in der Datei „build.gradle“ auf Modulebene die folgenden Zeilen zu den Abschnitten „dependencies“ und „android“ hinzu.

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

Hinzufügen von Berechtigungen zum Anwendungsmanifest

Damit Sie Berechtigungen anfordern können, die für einen Anruf erforderlich sind, müssen diese zunächst im Anwendungsmanifest (app/src/main/AndroidManifest.xml) deklariert werden. Ersetzen Sie den Inhalt der Datei durch den folgenden 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>
    

Festlegen des Layouts für die App

Zwei Eingaben sind erforderlich: eine Texteingabe für die Angerufenen-ID und eine Schaltfläche zum Tätigen des Anrufs. Diese Eingaben können über den Designer oder durch Bearbeiten der Layout-XML-Datei hinzugefügt werden. Erstellen Sie eine Schaltfläche mit der ID von call_button und einer Texteingabe für callee_id. Navigieren Sie zu app/src/main/res/layout/activity_main.xml, und ersetzen Sie den Inhalt der Datei durch den folgenden 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>

Erstellen des Hauptaktivitätsgerüsts und der Bindungen

Nachdem das Layout erstellt wurde, können die Bindungen und das Grundgerüst der Aktivität hinzugefügt werden. Die Aktivität verarbeitet das Anfordern von Runtime-Berechtigungen, erstellt den Teams-Anruf-Agent und platziert den Anruf, wenn die Schaltfläche gedrückt wird. Jede dieser Optionen wird in einem eigenen Abschnitt behandelt. Die onCreate-Methode wird überschrieben, um getAllPermissions und createTeamsAgent aufzurufen und die Bindungen für die Schaltfläche „Anruf“ hinzuzufügen. Dieses Ereignis tritt nur einmal auf, wenn die Aktivität erstellt wird. Weitere Informationen zu onCreate finden Sie im Leitfaden mit grundlegenden Informationen zum Aktivitätslebenszyklus.

Navigieren Sie zu MainActivity.java, und ersetzen Sie den Inhalt durch den folgenden Code:

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

Anfordern von Berechtigungen zur Runtime

Für Android 6.0 und höher (API-Ebene 23) und targetSdkVersion 23 oder höher werden Berechtigungen zur Runtime gewährt und nicht bei der Installation der App. Damit dies unterstützt wird, kann getAllPermissions implementiert werden, um für jede erforderliche Berechtigung ActivityCompat.checkSelfPermission und ActivityCompat.requestPermissions aufzurufen.

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

Hinweis

Berücksichtigen Sie beim Entwerfen der App, wann diese Berechtigungen angefordert werden sollen. Berechtigungen müssen angefordert werden, wenn sie benötigt werden, und nicht vorher. Weitere Informationen finden Sie im Leitfaden zu Android-Berechtigungen.

Objektmodell

Die folgenden Klassen und Schnittstellen befassen sich mit einigen der wichtigsten Features des Azure Communication Services Calling SDK:

Name BESCHREIBUNG
CallClient CallClient ist der Haupteinstiegspunkt des Calling SDK.
TeamsCallAgent TeamsCallAgent dient zum Starten und Verwalten von Anrufen.
TeamsCall TeamsCall dient zum Darstellen eines Teams-Anrufs.
CommunicationTokenCredential CommunicationTokenCredential dient als tokengestützte Anmeldeinformation zum Instanziieren von TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier wird als anderer Typ von Teilnehmer verwendet, der Teil eines Anrufs sein könnte.

Erstellen eines Agents aus dem Benutzerzugriffstoken

Mit einem Benutzertoken kann ein authentifizierter Anruf-Agent instanziiert werden. In der Regel wird dieses Token von einem Dienst mit einer für die Anwendung spezifischen Authentifizierung generiert. Weitere Informationen zu Benutzerzugriffstoken finden Sie im Leitfaden zu Benutzerzugriffstoken.

Ersetzen Sie für den Schnellstart <User_Access_Token> durch ein Benutzerzugriffstoken, das für Ihre Azure Communication Service-Ressource generiert wurde.


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

Beginnen eines Anrufs mithilfe eines Anruf-Agents

Ein Anruf kann über den Teams-Anruf-Agent getätigt werden. Dafür müssen lediglich eine Liste von Angerufenen-IDs und die Anrufoptionen bereitgestellt werden. In der Schnellstartanleitung werden die standardmäßigen Anrufoptionen ohne Video und eine einzelne Angerufenen-ID aus der Texteingabe verwendet.

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

Annehmen eines Anrufs

Das Annehmen eines Anrufs kann mithilfe des Teams-Anruf-Agents nur mithilfe eines Verweises auf den aktuellen Kontext erfolgen.

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

Einem Teams-Anruf beitreten

Ein Benutzer kann einem vorhandenen Anruf beitreten, indem er einen Link übergibt.

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

Teilnehmen an einem Teams-Anruf mit Optionen

Wir können auch einem vorhandenen Anruf mit vordefinierten Optionen beitreten, z. B. stummgeschaltet werden.

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

Einrichten des Listeners für eingehende Anrufe

Um eingehende Anrufe und andere Aktionen erkennen zu können, die von diesem Benutzer nicht ausgeführt werden, müssen Listener eingerichtet sein.

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

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

Starten der App und Anrufen des Echobots

Die App kann jetzt mithilfe der Schaltfläche „Run app“ (App ausführen) auf der Symbolleiste gestartet werden (UMSCHALT + F10). Überprüfen Sie, ob Sie Anrufe tätigen können, indem Sie 8:echo123 anrufen. Eine vorab aufgezeichnete Nachricht wird wiedergegeben, anschließend wird Ihre Nachricht an Sie wiederholt.

Screenshot, der die fertige Anwendung zeigt

Steigen Sie in Azure Communication Services ein, indem Sie das Communication Services-SDK „Calling“ nutzen, um Ihrer App Eins-zu-Eins-Sprach- und Videoanrufe hinzuzufügen. Hier erfahren Sie, wie Sie einen Videoanruf mithilfe des Calling SDK von Azure Communication Services für iOS mithilfe der Teams-Identität starten und beantworten.

Beispielcode

Wenn Sie direkt zum Ende springen möchten, können Sie diese Schnellstartanleitung als Beispiel auf GitHub herunterladen.

Voraussetzungen

Einrichten

Erstellen des Xcode-Projekts

Erstellen Sie in Xcode ein neues iOS-Projekt, und wählen Sie die Vorlage Single View App (Einzelansicht-App) aus. In diesem Tutorial wird das SwiftUI-Framework verwendet – legen Sie daher „Sprache“ auf „Swift“ und „Benutzeroberfläche“ auf „SwiftUI“ fest. Im Rahmen dieses Schnellstarts werden keine Tests erstellt. Sie können die Option Tests einschließen daher deaktivieren.

Screenshot des Fensters „Neues Projekt“ in Xcode

Installieren von CocoaPods

Verwenden Sie diese Anleitung, um CocoaPods auf Ihrem Mac zu installieren.

Installieren des Pakets und der Abhängigkeiten mit CocoaPods

  1. Zum Erstellen einer Podfile-Datei für Ihre Anwendung öffnen Sie das Terminal, navigieren Sie zum Projektordner, und führen Sie „pod init“ aus.

  2. Fügen Sie der Podfile-Datei den folgenden Code hinzu und speichern Sie diese. Weitere Informationen zu unterstützten SDK-Versionen.

platform :ios, '13.0'
use_frameworks!

target 'VideoCallingQuickstart' do
  pod 'AzureCommunicationCalling', '~> 2.10.0'
end
  1. Führen Sie „pod install“ aus.

  2. Öffnen Sie .xcworkspace mit Xcode.

Anfordern des Zugriffs auf Mikrofon und Kamera

Um auf Mikrofon und Kamera des Geräts zuzugreifen, müssen Sie die Liste der Informationseigenschaften Ihrer App mit NSMicrophoneUsageDescription und NSCameraUsageDescription aktualisieren. Legen Sie den zugehörigen Wert auf eine Zeichenfolge fest, die den Dialog beinhaltet, mit dem das System den Zugriff beim Benutzer anfordert.

Klicken Sie mit der rechten Maustaste auf den Eintrag Info.plist der Projektstruktur, und wählen Sie anschließend „Öffnen als ...“ > „Quellcode“ aus. Fügen Sie die folgenden Zeilen im Abschnitt <dict> der obersten Ebene hinzu, und speichern anschließend Sie die Datei.

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

Einrichten des App-Frameworks

Öffnen Sie die Datei ContentView.swift Ihres Projekts und fügen Sie am Anfang der Datei eine Deklaration vom Typ „Importieren“ hinzu, um die Bibliothek AzureCommunicationCalling und AVFoundation zu importieren. AVFoundation wird verwendet, um Audioberechtigungen aus Code zu erfassen.

import AzureCommunicationCalling
import AVFoundation

Objektmodell

Die folgenden Klassen und Schnittstellen befassen sich mit einigen der wichtigsten Features des Azure Communication Services Calling SDK für iOS.

Name BESCHREIBUNG
CallClient CallClient ist der Haupteinstiegspunkt des Calling SDK.
TeamsCallAgent TeamsCallAgent dient zum Starten und Verwalten von Anrufen.
TeamsIncomingCall TeamsIncomingCall wird verwendet, um eingehende Teams-Anrufe anzunehmen oder abzulehnen.
CommunicationTokenCredential CommunicationTokenCredential dient als tokengestützte Anmeldeinformation zum Instanziieren von TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier wird zur Darstellung der Identität des Benutzers verwendet, der eine der folgenden Optionen sein kann: CommunicationUserIdentifier, PhoneNumberIdentifier oder CallingApplication.

Erstellen des Teams-Anruf-Agents

Ersetzen Sie die Implementierung der ContentView-Struktur struct durch einige einfache Benutzeroberflächen-Steuerelemente, die einem Benutzer das Initiieren und Beenden eines Anrufs ermöglichen. In dieser Schnellstartanleitung fügen wir Geschäftslogik an diese Steuerelemente an.

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

Authentifizieren des Clients

Um eine TeamsCallAgent-Instanz zu initialisieren, benötigen Sie ein Benutzerzugriffs-Token, mit dem Anrufe getätigt und empfangen werden können. Wenn Sie noch nicht über ein Token verfügen, finden Sie in der Dokumentation zu Benutzerzugriffs-Token weitere Informationen.

Sobald Sie über ein Token verfügen, fügen Sie den folgenden Code zum onAppear-Rückruf in ContentView.swift hinzu: Sie müssen <USER ACCESS TOKEN> durch ein gültiges Zugriffstoken-Token für Ihre Ressource ersetzen:

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

Initialisieren des Teams-CallAgent und Zugreifen auf Geräte-Manager

Um eine TeamsCallAgent-Instanz anhand von CallClient zu erzeugen, nutzen Sie die callClient.createTeamsCallAgent-Methode, die ein TeamsCallAgent-Objekt asynchron zurückgibt, nachdem es initialisiert wurde. DeviceManager ermöglicht Ihnen das Aufzählen lokaler Geräte, die in einem Anruf zur Übertragung von Audio-/Videostreams verwendet werden können. Mithilfe des Geräte-Managers können Sie auch beim Benutzer die Berechtigung für den Zugriff auf Mikrofon/Kamera anfordern.

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

Anfordern von Berechtigungen

Wir müssen dem onAppear Rückruf den folgenden Code hinzufügen, um Berechtigungen für Audio und Video anzufordern.

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

Tätigen eines ausgehenden Anrufs

Die Methode startCall wird als die Aktion festgelegt, die ausgeführt wird, wenn auf die Schaltfläche „Start Call“ (Anruf starten) getippt wird. In dieser Schnellstartanleitung sind ausgehende Aufrufe standardmäßig nur Audiodaten. Um einen Anruf per Video zu starten, müssen wir VideoOptions mit LocalVideoStream festlegen und an startCallOptions übergeben, um Anfangsoptionen für den Anruf festzulegen.

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

An einer Teams-Besprechung teilnehmen

Mit der join-Methode kann der Benutzer an einer Teams-Besprechung teilnehmen.

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 und RemotePariticipantObserver werden verwendet, um Mid-Call-Ereignisse und Remote-Teilnehmer zu verwalten. Wir legen die Beobachter in der Funktion setTeamsCallAndObserver fest.

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

Annehmen eines eingehenden Anrufs

Implementieren Sie zum Beantworten eines eingehenden Anrufs ein TeamsIncomingCallHandler, um das Banner für eingehende Anrufe anzuzeigen, um den Anruf zu beantworten oder zu ablehnen. Legen Sie die folgende Implementierung in TeamsIncomingCallHandler.swift ab.

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

Wir müssen eine Instanz von TeamsIncomingCallHandler erstellen, indem wir den folgenden Code zum onAppear-Rückruf in ContentView.swift hinzufügen:

Legen Sie einen Delegaten auf TeamsCallAgent fest, nachdem TeamsCallAgent erfolgreich erstellt wurde:

self.teamsCallAgent!.delegate = incomingCallHandler

Sobald es einen eingehenden Anruf gibt, ruft TeamsIncomingCallHandler die Funktion showIncomingCallBanner zum Anzeigen der Schaltfläche answer und decline auf.

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

Die Aktionen, die an answer und decline angefügt sind, werden als der folgende Code implementiert. Zum Beantworten des Anrufs mit Video müssen wir das lokale Video aktivieren und die Optionen von AcceptCallOptions mit localVideoStream festlegen.

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

Abonnieren von Ereignissen

Wir können eine TeamsCallObserver Klasse implementieren, um eine Sammlung von Ereignissen zu abonnieren, über die wir benachrichtigt werden, wenn sich Werte während des Anrufs ändern.

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

Ausführen des Codes

Sie können Ihre App auf dem iOS-Simulator erstellen und ausführen, indem Sie „Produkt“ > „Ausführen“ auswählen oder die Tastenkombination „⌘-R“ verwenden.

Bereinigen von Ressourcen

Wenn Sie ein Communication Services-Abonnement bereinigen und entfernen möchten, können Sie die Ressource oder die Ressourcengruppe löschen. Wenn Sie die Ressourcengruppe löschen, werden auch alle anderen Ressourcen gelöscht, die ihr zugeordnet sind. Weitere Informationen zum Bereinigen von Ressourcen finden Sie hier.

Nächste Schritte

Weitere Informationen finden Sie in den folgenden Artikeln: