Mal kurz mit Azure telefonieren

Mal kurz mit Azure telefonieren

Azure Communication Services (ACS) ist ein neuer Kommunikationsdienst von Microsoft Azure, der sich aktuell in der Preview-Phase befindet (Stand 11.11.2020). Dieser bietet eine API, um Chat-Funktionalitäten bzw. Sprach- und Videoanrufe in Anwendungen zu integrieren.

Dabei wird die Microsoft Teams Infrastruktur verwendet. Mit Hilfe der verfügbaren Client-Bibliotheken soll die Verwendung auf verschiedenen Plattformen und Endgeräten möglich sein. Da Remote-Kommunikation, insbesondere in Zeiten von COVID-19, zunehmend wichtiger wird, kann dieser Dienst für verschiedene Anwendungen eine sinnvolle Erweiterung sein. Ich habe mich im Rahmen einer selbst entwickelten Beispielanwendung mit ACS vertraut gemacht und berichte nachfolgend über die Hauptfunktionen und deren Verwendung anhand einiger Codeausschnitte. Anschließend werfen wir noch einen Blick auf die Roadmap bis zur ersten „stable“ Version und ziehen ein Resümee zum aktuellen Funktionsumfang.

Azure Communication Services

Mittels Azure Communication Services können Chats und Sprach-/Videoanrufe auf unterschiedlichen Plattformen realisiert werden. Sowohl Direktanrufe als auch Gruppentelefonate mit anderen Anwendungsbenutzern sowie Teilnehmern aus dem Telefonnetz (PSTN) sind möglich. Anrufe und SMS ins Telefonnetz sind jedoch aktuell nur für US-Benutzer verwendbar.

Für sämtliche Funktionalitäten werden Client-Bibliotheken für den API-Zugriff angeboten. Aktuell existieren Codebeispiele für die Sprachen C#, JavaScript (Web), Java, Python und iOS (nicht durchgängig). Um die API zu nutzen, muss zunächst eine Azure Communication Service Ressource im Azure Portal angelegt werden. Bevor es mit der Umsetzung losgeht, sollten wir uns aber noch mit dem Architekturkonzept hinter ACS vertraut machen, um den Dienst in eine Anwendung integrieren zu können.

acs chat architecture
Bildquelle: Chat arcitecture

Laut dieser schematischen Darstellung aus der Azure-Dokumentation, besteht eine ACS Ressource aus einer „Chat Data Plane“ und einer „Communication Administration“. Wenn eine Webanwendung „Chat Web App“ die ACS-API nutzen möchte, muss sie davor einen Access Token von einem Backend-Service anfordern. Diese erhält den Token wiederum von der „Communication Administration“ im ACS. Erst mit diesem Access Token ist die Webanwendung berechtigt die ACS-API zu nutzen (gilt für alle Clients). Für den Backend-Service werden wir daher ein simples .NET Core Web-API Projekt mit einer HTTP-Action zum Anfordern des Tokens erstellen. Unser Client wird mit Angular umgesetzt.

Erstellung des Access Tokens

Nachdem die Azure Ressource erstellt wurde, können wir mit dem Connection-String einen CommunicationIdentityClient im Backend-Service instanziieren. Dafür ist das NuGet-Paket Azure.Communication.Administration notwendig (aktuell nur als Pre-Release Paket verfügbar).

Mit dem Client kann daraufhin ein ACS-User erstellt werden. Hierbei ist es wichtig, zwischen unseren Domänen-Usern, welche sich vermutlich beim Anfordern des Access Tokens bei unserem Backend-Service authentifizieren werden und ACS-Usern, welche diesen Benutzer beim Azure Communication Service repräsentieren, zu unterscheiden. Der von uns implementierte Backend-Service ist eigenständig für die Zuordnung zwischen Domänen-User und ACS-User zuständig. Dieses Mapping ist notwendig, um beispielsweise die gültigen Access Tokens eines Domänen-Users zu widerrufen (z.B. wenn sein Konto gelöscht wurde). Diese Operation kann nur mit den Benutzerinfos des ACS-Users durchgeführt werden.

var client = new CommunicationIdentityClient(this.azureSettings.ACSConnectionString);
var userResponse = await client.CreateUserAsync();
var acsUser = userResponse.Value;

Ist der ACS-User erstellt, können für ihn Access Tokens mit einem bestimmten Scope (aktuell Chat, VoIP und PSTN) erstellt werden. Dieser Scope gibt an, welche Funktionalitäten der Benutzer beim Azure Communication Service nutzen darf. Anschließend können wir für das User-Mapping noch die notwendigen Infos persistieren und dann den Access Token an die Client-Anwendung weiterleiten.

var tokenResponse = await client.IssueTokenAsync(acsUser, scopes: new[] {
    CommunicationTokenScope.VoIP,
    CommunicationTokenScope.Chat
});
var acsToken = tokenResponse.Value.Token;

Chatten mit Azure

Azure Communication Services bieten verschiedene Operationen im Zusammenhang mit Chats an. So können beispielsweise Chat-Threads erstellt und gelöscht werden. Zu diesen Threads können anschließend Teilnehmer (ACS-User) hinzugefügt und entfernt werden, welche wiederum Nachrichten verschicken können. An diesem Punkt zeigt sich, warum ein korrektes Mapping zwischen Domänen- und ACS-User in unserem Backend-Service notwendig ist. Möchte ein Thread-Ersteller einen Teilnehmer hinzufügen, kann er dies nur über die ACS-UserId des Benutzers tun. Diese kennt er jedoch nicht, weshalb der Backend-Service für die „Übersetzung“ des Anzeigenamens in die ACS-UserId herangezogen werden muss.

Um in unserem Angular Client die ACS Client-Bibliotheken zu nutzen, müssen wir zunächst folgende NPM-Pakete installieren.

npm install @azure/communication-common --save
npm install @azure/communication-administration --save
npm install @azure/communication-signaling --save
npm install @azure/communication-chat --save

Damit der Angular Client Nachrichten anzeigen kann, muss dieser zunächst unter Verwendung des Access Tokens einen ChatClient erzeugen. Anschließend starten wir die Echtzeitbenachrichtigungen mit dem Aufruf von startRealtimeNotifications() und setzen einen Event-Handler für das Event „chatMessageReceived“.

this.chatClient = new ChatClient(
    environment.acsEndpoint,
    new AzureCommunicationUserCredential(this.authService.token)
);
await this.chatClient.startRealtimeNotifications();
this.chatClient.on('chatMessageReceived', async (e) => {
    await this.loadThreadMessages();
});

Um die Nachrichten von ACS zu laden, muss zunächst noch ein ChatThreadClient erzeugt werden, welcher für sämtliche Interaktionen mit dem Thread verwendet wird (Hinzufügen/Entfernen von Teilnehmern, Senden von Nachrichten, etc.).

this.chatThreadClient = await this.chatClient.getChatThreadClient(thread.id);
await this.loadThreadMessages();
await this.loadThreadMembers();

Die aktuellen Nachrichten eines Threads können mittels this.chatThreadClient.listMessages() geladen werden. Ähnlich einfach lässt sich auch eine Nachricht verschicken, nämlich mit dem Aufruf this.chatThreadClient.sendMessage(…). Mit diesen Operationen können wir bereits einfache Chat-Funktionalitäten in unsere Beispielanwendung integrieren, wie unser Chat zwischen Alice und Bob verdeutlicht.

Hier sehen wir auch, dass für bestimmte Aktionen, wie das Hinzufügen oder Entfernen von Teilnehmern, ebenfalls Nachrichten erzeugt werden. Diese weisen einen besonderen Nachrichtentyp auf (siehe Message Types), um sie zu identifizieren und ggf. einen Hinweis im Chatverlauf anzuzeigen (z.B. “Bob wurde zum Chat hinzugefügt”) .

Chat zwischen Alice und Bob

Anrufe mit Azure

Um einen Anruf zu einem anderen ACS-User zu starten, benötigen wir zunächst (ähnlich wie beim Chat) einen CallClient.
Hierfür installieren wir zuvor ein weiteres NPM-Paket mittels npm install @azure/communication-calling --save. Im Anschluss können wir mit dem Access Token einen CallAgent erstellen, welcher Anrufe tätigen und entgegennehmen kann. Ein eingehender Anruf löst ein Event „callsUpdated“ aus, welches wir nutzen können, um den Anruf entgegenzunehmen.

this.callClient = new CallClient();
const tokenCredential = new AzureCommunicationUserCredential(this.authService.token);
this.callAgent = await this.callClient.createCallAgent(tokenCredential);
this.callAgent.on('callsUpdated', (e) => this.onCallsUpdated(e));

Um einen Anruf zu tätigen, benötigen wir zunächst die ACS-UserId des angerufenen Benutzers, weshalb wir wieder auf unser User-Mapping im Backend-Service zurückgreifen müssen. Danach können wir einen Anruf starten und erhalten ein Call-Objekt, welches diesen Anruf darstellt. Beim beenden des Anrufs, muss natürlich auch this.call.hangUp() aufgerufen werden um der Gegenseite das Ende zu signalisieren.

this.call = await this.callAgent.call([{ communicationUserId: user.communicationUserId }]);

Natürlich wollen wir unser Gegenüber auch sehen und übertragen daher für den Call auch unser Kamerabild. Über den DeviceManager können wir auf die verfügbaren Mikrofone, Lautsprecher und Kameras zugreifen und danach einen Stream unserer Kamera zum Anruf hinzufügen. Für unser Beispiel greifen wir einfach auf die erstbeste verfügbare Kamera zu.

this.deviceManager = await this.callClient.getDeviceManager();
this.selectedCamera = this.deviceManager.getCameraList()[0];
this.localVideoStream = new LocalVideoStream(this.selectedCamera);
await this.call.startVideo(this.localVideoStream);

So einfach lässt sich das Kamerabild oder falls gewünscht auch der aktuelle Bildschirm übertragen. Natürlich müssen wir unseren Video-Stream sowie das Bild der Gegenseite noch in unsere Client-Anwendung rendern. Doch auch dies gestaltet sich simpel.

const renderer = new Renderer(this.localVideoStream);
const view = await renderer.createView();
document.getElementById('my-video').appendChild(view.target);

In der resultierenden Anwendung können wir somit nicht nur Chat-Threads erstellen, Nachrichten schreiben und empfangen, Teilnehmer hinzufügen und entfernen, sondern auch Videoanrufe zwischen ACS-Usern starten. Unterhalb ein Screenshot meiner Beispielanwendung während eines Anrufes zwischen Alice und Bob.

Videoanruf zwischen Alice und Bob

Roadmap und Fazit

Nachdem sich die Azure Communication Services aktuell in einer Preview-Phase befinden, soll die „stable“ Version voraussichtlich im ersten Quartal 2021 einsatzbereit sein. Preise für die Verwendung der Chat-/Anrufdienste sind bereits verfügbar, wobei die Abrechnung pro verschickter Nachricht bzw. pro telefonierter Minute (pro Benutzer) erfolgt. Anrufe ins Telefonnetz sind jedoch aktuell nur US-Benutzern vorbehalten und werden mit der „stable“ Version hoffentlich auch in der EU möglich sein. Auch Gruppenanrufe und das Teilnehmen an Microsoft Teams Besprechungen soll möglich sein.

Die Azure Communication Services lassen sich bereits jetzt ohne Hindernisse in Anwendungen integrieren. Auch wenn an einigen Stellen noch ein komfortabler(er) Funktionsaufruf gewünscht wäre oder die Dokumentation kleine Lücken aufweist, kann mithilfe der Samples in verschiedenen Sprachen die Lösung prompt gefunden werden. Für die Rechteverwaltung der ACS-User wird vermutlich noch eine Erweiterung notwendig sein, da jeder ACS-User in einem Chat-Thread dieselben Rechte besitzt und somit auch andere Teilnehmer zum Thread hinzufügen oder entfernen kann. Ob dies in Zukunft Azure-seitig stattfindet oder wiederum über einen Backend-Service gelöst werden muss, ist noch offen. Aus meiner Sicht ist es auch denkbar, dass in Zukunft ein Storage Account mit der ACS Ressource verknüpft werden muss. Insbesondere wenn in einer späteren Version das Versenden von Anhängen im Chat ermöglicht wird, kann dies aufgrund der potenziell großen Datenmengen schlagend werden.

Azure Communication Services ermöglichen skalierbare Videoanrufe und Chats direkt in der Anwendung - viel Spaß beim Ausprobieren!