Schnellstart: Verknüpfen Ihrer Anruf-App mit einer automatischen Teams-Telefonzentrale

In diesem Schnellstart werden Sie erfahren, wie Sie einen Anruf von einem Azure Communication Services-Benutzer an die automatische Teams-Telefonzentrale starten. Sie werden dies mit den folgenden Schritten erreichen:

  1. Aktivieren Sie den Verbund der Azure Communication Services-Ressource mit dem Teams-Mandanten.
  2. Wählen oder erstellen Sie eine automatische Teams-Telefonzentrale über das Teams Admin Center.
  3. Rufen Sie die E-Mail-Adresse der automatischen Telefonzentrale über das Teams Admin Center ab.
  4. Rufen Sie die Objekt-ID der automatischen Telefonzentrale über die Graph-API ab.
  5. Starten Sie einen Anruf mit dem Anruf-SDK von Azure Communication Services.

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

Aktivieren der Interoperabilität in Ihrem Teams-Mandanten

Microsoft Entra-Benutzer mit der Teams-Administratorrolle können das PowerShell-Cmdlet mit dem MicrosoftTeams-Modul ausführen, um die Communication Services-Ressource im Mandanten zu aktivieren.

1. Vorbereiten des Microsoft Teams-Moduls

Öffnen Sie zunächst die PowerShell, und überprüfen Sie das Vorhandensein des Teams-Moduls mit dem folgenden Befehl:

Get-module *teams* 

Wenn das MicrosoftTeams-Modul nicht angezeigt wird, installieren Sie es zuerst. Um das Modul zu installieren, müssen Sie PowerShell als Administrator ausführen. Führen Sie dann den folgenden Befehl aus:

	Install-Module -Name MicrosoftTeams

Sie werden über die Module informiert, die installiert werden, die Sie mit der Antwort Y oder A bestätigen können. Wenn das Modul installiert, aber veraltet ist, können Sie den folgenden Befehl ausführen, um das Modul zu aktualisieren:

	Update-Module MicrosoftTeams

2. Herstellen einer Verbindung mit dem Microsoft Teams-Modul

Wenn das Modul installiert und bereit ist, können Sie mit dem folgenden Befehl eine Verbindung mit dem MicrosftTeams-Modul herstellen. Sie werden aufgefordert, sich mit einem interaktiven Fenster anzumelden. Das Benutzerkonto, das Sie verwenden möchten, muss über Teams-Administratorberechtigungen verfügen. Andernfalls erhalten Sie möglicherweise in den nächsten Schritten die Antwort access denied.

Connect-MicrosoftTeams

3. Aktivieren der Mandantenkonfiguration

Die Interoperabilität mit Communication Services-Ressourcen wird über die Mandantenkonfiguration und die zugewiesene Richtlinie gesteuert. Der Teams-Mandant verfügt über eine einzelne Mandantenkonfiguration, und Teams-Benutzer*innen wurde eine globale Richtlinie oder eine benutzerdefinierte Richtlinie zugewiesen. Weitere Informationen finden Sie unter Zuweisen von Richtlinien in Teams.

Nach erfolgreicher Anmeldung können Sie das Cmdlet Set-CsTeamsAcsFederationConfiguration ausführen, um die Communication Services-Ressource in Ihrem Mandanten zu aktivieren. Ersetzen Sie den Text IMMUTABLE_RESOURCE_ID durch eine unveränderliche Ressourcen-ID in Ihrer Kommunikationsressource. Weitere Informationen zum Abrufen dieser Informationen finden Sie hier.

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4. Aktivieren der Mandantenrichtlinie

Jedem Teams-Benutzer wurde eine External Access Policy zugewiesen, die festlegt, ob Communication Services-Benutzer*innen diese Teams-Benutzer*in bzw. diesen Teams-Benutzer aufrufen können. Verwenden Sie das Cmdlet Set-CsExternalAccessPolicy, um sicherzustellen, dass für die der Teams-Benutzer*in bzw. dem Teams-Benutzer zugewiesene Richtlinie EnableAcsFederationAccess auf $true festgelegt ist.

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Erstellen oder Auswählen der automatischen Teams-Telefonzentrale

Die automatische Teams-Telefonzentrale ist ein System, das ein automatisiertes Anrufverarbeitungssystem für eingehende Anrufe bietet. Sie dient als virtuelle Empfangsperson, sodass Anrufer automatisch an die entsprechende Person oder Abteilung weitergeleitet werden können, ohne dass ein menschlicher Operator erforderlich ist. Sie können über das Teams Admin Center eine vorhandene automatische Telefonzentrale auswählen oder eine neue erstellen.

Weitere Informationen zum Erstellen der automatischen Telefonzentrale mit dem Teams Admin Center finden Sie hier.

Suchen der Objekt-ID für die automatische Telefonzentrale

Nachdem die automatische Telefonzentrale erstellt wurde, müssen wir die korrelierte Objekt-ID finden, um sie später für Aufrufe zu verwenden. Die Objekt-ID ist mit dem Ressourcenkonto verbunden, das an die automatische Telefonzentrale angefügt wurde. Öffnen Sie die Registerkarte „Ressourcenkonten“ in Teams Admin, und suchen Sie nach der E-Mail-Adresse des Kontos. Screenshot der Ressourcenkonten im Teams Admin-Portal. Alle erforderlichen Informationen zu Ressourcenkonten können im Microsoft Graph-Explorer gefunden werden, indem diese E-Mail-Adresse in der Suche verwendet wird.

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

In den Ergebnissen können wir das Feld „ID“ finden

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

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

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 Version next des Azure Communication Services Calling SDK verwendet.

npm install @azure/communication-common@next --save
npm install @azure/communication-calling@next --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 - Calling Web SDK</title>
    </head>
    <body>
        <h4>Azure Communication Services - Calling Web SDK</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">Initialize Call Agent</button>
        <br>
        <br>
        <input id="application-object-id"
            type="text"
            placeholder="Enter application objectId identity in format: 'APP_GUID'"
            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.
CallAgent Dient zum Starten und Verwalten von Anrufen.
DeviceManager Dient zum Verwalten von Mediengeräten.
Call Dient zum Darstellen eines Anrufs.
LocalVideoStream Dient zum Erstellen eines lokalen Videostreams für ein Kameragerät auf dem lokalen System.
RemoteParticipant Dient zum Darstellen eines Remote-Teilnehmers im Anruf.
RemoteVideoStream Dient zum Darstellen eines Remotevideostreams von einem Remoteteilnehmer.

Erstellen Sie im Stammverzeichnis Ihres Projekts eine Datei mit dem Namen client.js, die die Anwendungslogik für diese Schnellstartanleitung enthalten soll. Fügen Sie den folgenden Code zu client.js 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 callAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let applicationObjectId = document.getElementById('application-object-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 CallAgent instance with a AzureCommunicationTokenCredential via created CallClient. CallAgent 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());
        callAgent = await callClient.createCallAgent(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.
        callAgent.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 an Teams Auto attendant
 * 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 = callAgent.startCall([{ teamsAppId: applicationObjectId.value.trim(), cloud:"public" }], { 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 `CallAgent.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: './client.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

Manuelle Schritte zum Einrichten des Anrufs:

  1. Navigieren Sie in Ihrem Browser zu http://localhost:8080/..
  2. Geben Sie ein gültiges Benutzerzugriffstoken ein. Wenn Sie noch über kein Zugriffstoken verfügen, finden Sie in der Dokumentation zu Benutzerzugriffstoken weitere Informationen.
  3. Klicken Sie auf die Schaltflächen „Anruf-Agent initialisieren“.
  4. Geben Sie die Objekt-ID der automatischen Telefonzentrale ein, und wählen Sie die Schaltfläche „Anruf starten“ aus. Die Anwendung startet den ausgehenden Aufruf an die automatische Telefonzentrale mit der angegebenen Objekt-ID.
  5. Der Anruf wird mit der automatischen Telefonzentrale verbunden.
  6. Der Communication Services-Benutzer wird basierend auf seiner Konfiguration durch die automatische Telefonzentrale weitergeleitet.

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: