Schnellstart: Verknüpfen Ihrer Anruf-App mit einer Teams-Anrufwarteschlange
In diesem Schnellstart werden Sie erfahren, wie Sie einen Anruf von einem Azure Communication Services-Benutzer an die Teams-Anrufwarteschlange starten. Sie werden dies mit den folgenden Schritten erreichen:
- Aktivieren Sie den Verbund der Azure Communication Services-Ressource mit dem Teams-Mandanten.
- Wählen oder erstellen Sie die Teams-Anrufwarteschlange über das Teams Admin Center.
- Rufen Sie die E-Mail-Adresse der Anrufwarteschlange über das Teams Admin Center ab.
- Rufen Sie die Objekt-ID der Anrufwarteschlange über die Graph-API ab.
- 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 einer Teams-Anrufwarteschlange
Die Teams-Anrufwarteschlange ist ein Feature in Microsoft Teams, das eingehende Anrufe effizient an eine Gruppe von bestimmten Benutzern oder Agenten verteilt. Es ist nützlich für Kundensupport- oder Callcenterszenarien. Aufrufe werden in eine Warteschlange gestellt und dem nächsten verfügbaren Agenten basierend auf einer vordefinierten Routingmethode zugewiesen. Agenten erhalten Benachrichtigungen und können Anrufe mithilfe der Anrufsteuerelementen von Teams verarbeiten. Das Feature bietet Berichte und Analysen für die Leistungsnachverfolgung. Es vereinfacht die Anrufbehandlung, stellt eine konsistente Kundenerfahrung sicher und optimiert die Produktivität der Agenten. Sie können über das Teams Admin Center eine vorhandene Anrufwarteschlange auswählen oder eine neue erstellen.
Weitere Informationen zum Erstellen der Anrufwarteschlange mit dem Teams Admin Center finden Sie hier.
Objekt-ID für Anrufwarteschlange suchen
Nachdem die Anrufwarteschlange erstellt wurde, müssen wir die korrelierte Objekt-ID suchen, um sie später für Aufrufe verwenden zu können. Die Objekt-ID ist mit dem Ressourcenkonto verbunden, das an die Anrufwarteschlange angefügt wurde. Öffnen Sie die Registerkarte „Ressourcenkonten“ in Teams Admin, und suchen Sie nach der E-Mail. Alle erforderlichen Informationen zu Ressourcenkonten können im Microsoft Graph-Explorer gefunden werden, indem diese E-Mail 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
- Rufen Sie ein Azure-Konto mit einem aktiven Abonnement ab. Sie können kostenlos ein Konto erstellen.
- Node.js: Active LTS- und Maintenance LTS-Versionen (8.11.1 und 10.14.1).
- Erstellen Sie eine aktive Communication Services-Ressource. Erstellen Sie eine Communication Services-Ressource.
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 callee's Teams user 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 callQueueId = 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 a Teams Call Queue
* 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: callQueueId.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.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();
});
});
call.on('isLocalVideoStartedChanged', () => {
console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
});
console.log(`isLocalVideoStarted: ${call.isLocalVideoStarted}`);
// 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:
- Navigieren Sie in Ihrem Browser zu http://localhost:8080/..
- Geben Sie ein gültiges Benutzerzugriffstoken ein. Wenn Sie noch über kein Zugriffstoken verfügen, finden Sie in der Dokumentation zu Benutzerzugriffstoken weitere Informationen.
- Klicken Sie auf die Schaltflächen „Anruf-Agent initialisieren“.
- Geben Sie die ID des Anrufwarteschlangenobjekts ein, und wählen Sie die Schaltfläche „Anruf starten“ aus. Die Anwendung wird den ausgehenden Aufruf an die Anrufwarteschlange mit der angegebenen Objekt-ID starten.
- Der Anruf ist mit der Anrufwarteschlange verbunden.
- Der Communication Services-Benutzer wird basierend auf seiner Konfiguration durch die Anrufwarteschlange 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:
- Erste Schritte mit der Benutzeroberfläche für Anrufe an Teams Voice Apps
- Informieren Sie sich über die Funktionen des Calling SDK.
- Informieren Sie sich über die Funktionsweise von Anrufen.