Verwalten von Videos während Anrufen

Lernen Sie, wie Sie Videoanrufe mit den Azure Communication Services SDKS verwalten können. Sie lernen, wie Sie den Empfang und das Senden von Videos während eines Anrufs verwalten können.

Voraussetzungen

Das SDK installieren

Verwenden Sie den Befehl npm install, um die Common und Calling SDKs von Azure Communication Services für JavaScript zu installieren:

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

Initialisieren erforderlicher Objekte

Für die meisten Anrufvorgänge ist eine CallClient-Instanz erforderlich. Wenn Sie eine neue CallClient-Instanz erstellen, können Sie diese mit benutzerdefinierten Optionen wie einer Logger-Instanz konfigurieren.

Mit der CallClient-Instanz können Sie eine CallAgent-Instanz erstellen, indem Sie den Agent für die Anruferstellung (createCallAgent) aufrufen. Durch diese Methode wird ein CallAgent-Instanzobjekt asynchron zurückgegeben.

Die Methode createCallAgent verwendet CommunicationTokenCredential als Argument, welches ein Benutzerzugriffstoken akzeptiert.

Sie können die Methode getDeviceManager für die Instanz CallClient verwenden, um auf deviceManager zuzugreifen.

const { CallClient } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential} = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");

// Set the logger's log level
setLogLevel('verbose');

// Redirect log output to console, file, buffer, REST API, or whatever location you want
AzureLogger.log = (...args) => {
    console.log(...args); // Redirect log output to console
};

const userToken = '<USER_TOKEN>';
callClient = new CallClient(options);
const tokenCredential = new AzureCommunicationTokenCredential(userToken);
const callAgent = await callClient.createCallAgent(tokenCredential, {displayName: 'optional Azure Communication Services user name'});
const deviceManager = await callClient.getDeviceManager()

Optimale Verwaltung der Konnektivität zwischen SDK und Microsoft-Infrastruktur

Die Call Agent-Instanz unterstützt Sie bei der Verwaltung von Anrufen (Annehmen oder Starten von Anrufen). Damit Ihr Calling SDK funktioniert, muss eine Verbindung mit der Microsoft-Infrastruktur hergestellt werden, um Benachrichtigungen über eingehende Anrufe zu erhalten und andere Anrufdetails zu koordinieren. Ihre Call Agent-Instanz kann zwei Status haben:

Connected (Verbunden) – Der connectionState-Wert Connected für Call Agent bedeutet, dass das Client-SDK verbunden ist und Benachrichtigungen von der Microsoft-Infrastruktur empfangen kann.

Disconnected (Getrennt) – Der connectionState-Wert Disconnected für Call Agent weist darauf hin, dass das SDK nicht ordnungsgemäß verbunden werden kann. Call Agent muss neu erstellt werden.

  • invalidToken: Wenn ein Token abgelaufen oder ungültig ist, wird die Call Agent-Instanz mit diesem Fehler getrennt.
  • connectionIssue: Wenn ein Problem mit der Verbindung zwischen dem Client und der Microsoft-Infrastruktur auftritt, gibt Call Agent nach mehreren Versuchen den connectionIssue-Fehler zurück.

Sie können überprüfen, ob Ihre lokale Call Agent-Instanz mit der Microsoft-Infrastruktur verbunden ist, indem Sie den aktuellen Wert der connectionState-Eigenschaft prüfen. Während eines aktiven Anrufs können Sie auf das connectionStateChanged-Ereignis lauschen, um zu bestimmen, ob sich der Call Agent-Status von Connected in Disconnected ändert.

const connectionState = callAgentInstance.connectionState;
console.log(connectionState); // it may return either of 'Connected' | 'Disconnected'

const connectionStateCallback = (args) => {
    console.log(args); // it will return an object with oldState and newState, each of having a value of either of 'Connected' | 'Disconnected'
    // it will also return reason, either of 'invalidToken' | 'connectionIssue'
}
callAgentInstance.on('connectionStateChanged', connectionStateCallback);

Geräteverwaltung

Wenn Sie Video mit dem Calling SDK verwenden möchten, müssen Sie Geräte verwalten können. Mit den Geräten können Sie die Übertragung von Audio und Video an den Anruf steuern.

Mit deviceManager können Sie lokale Geräte aufzählen, die Ihre Audio- und Videostreams in einem Anruf übertragen können. Außerdem können Sie mit deviceManager die Berechtigung für den Zugriff auf die Mikrofone und Kameras des lokalen Geräts anfordern.

Sie können auf deviceManager zugreifen, indem Sie die Methode callClient.getDeviceManager() aufrufen:

const deviceManager = await callClient.getDeviceManager();

Abrufen lokaler Geräte

Für den Zugriff auf lokale Geräte können Sie die deviceManager-Enumerationsmethoden getCameras() und getMicrophones verwenden. Diese Methoden sind asynchrone Aktionen.

//  Get a list of available video devices for use.
const localCameras = await deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

// Get a list of available microphone devices for use.
const localMicrophones = await deviceManager.getMicrophones(); // [AudioDeviceInfo, AudioDeviceInfo...]

// Get a list of available speaker devices for use.
const localSpeakers = await deviceManager.getSpeakers(); // [AudioDeviceInfo, AudioDeviceInfo...]

Festlegen der Standardgeräte

Sobald Sie wissen, welche Geräte zur Verfügung stehen, können Sie Standardgeräte für Mikrofon, Lautsprecher und Kamera festlegen. Wenn keine Clientstandardwerte festgelegt sind, verwendet das Communication Services SDK die Standardwerte des Betriebssystems.

Mikrofon

Zugreifen auf das verwendete Gerät

// Get the microphone device that is being used.
const defaultMicrophone = deviceManager.selectedMicrophone;

Festlegen des zu verwendenden Geräts

// Set the microphone device to use.
await deviceManager.selectMicrophone(localMicrophones[0]);

Referent

Zugreifen auf das verwendete Gerät

// Get the speaker device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;

Festlegen des zu verwendenden Geräts

// Set the speaker device to use.
await deviceManager.selectSpeaker(localSpeakers[0]);

Kamera

Zugreifen auf das verwendete Gerät

// Get the camera device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;

Festlegen des zu verwendenden Geräts

// Set the speaker device to use.
await deviceManager.selectSpeaker(localCameras[0]);

Jeder CallAgent kann ein eigenes Mikrofon und Lautsprecher für die zugeordnete DeviceManager auswählen. Es wird empfohlen, dass verschiedene CallAgents unterschiedliche Mikrofone und Lautsprecher verwenden. Sie sollten nicht dieselben Mikrofone oder Lautsprecher gemeinsam nutzen. Eine gemeinsame Nutzung kann dazu führen, dass eine benutzerseitige Mikrofondiagnose ausgelöst wird und das Mikrofon je nach Browser bzw. Betriebssystem nicht mehr funktioniert.

Lokaler Videostream

Um Videodaten in einem Anruf senden zu können, müssen Sie ein LocalVideoStream-Objekt erstellen.

const localVideoStream = new LocalVideoStream(camera);

Die als Parameter übergebene Kamera ist eines der VideoDeviceInfo-Objekte, die von der deviceManager.getCameras()-Methode zurückgegeben wurden.

Ein LocalVideoStream hat folgende Eigenschaften:

  • source: Die Geräteinformationen.
const source = localVideoStream.source;
  • mediaStreamType: Kann Video, ScreenSharing oder RawMedia sein.
const type: MediaStreamType = localVideoStream.mediaStreamType;

Vorschau auf lokaler Kamera

Sie können deviceManager und VideoStreamRenderer verwenden, um mit dem Rendern von Streams über Ihre lokale Kamera zu beginnen. Nach Erstellung eines lokalen Videostreams (LocalVideoStream) können Sie diesen verwenden, um VideoStreamRenderer einzurichten. Rufen Sie nach Erstellung von VideoStreamRenderer die zugehörige createView()-Methode auf, um eine Ansicht zu erhalten, die Sie Ihrer Seite als untergeordnetes Element hinzufügen können.

Dieser Stream wird nicht an andere Teilnehmer gesendet. Es handelt sich um einen lokalen Vorschaufeed.

// To start viewing local camera preview
const cameras = await deviceManager.getCameras();
const camera = cameras[0];
const localVideoStream = new LocalVideoStream(camera);
const videoStreamRenderer = new VideoStreamRenderer(localVideoStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

Beenden der lokalen Vorschau

Um den lokalen Vorschauanruf zu beenden, löschen Sie die von VideoStreamRenderer abgeleitete Ansicht. Entfernen Sie nach dem Löschen des Videostream-Renderers die Ansicht aus der HTML-Struktur, indem Sie die removeChild()-Methode über den DOM-Knoten aufrufen, der Ihre Vorschau enthält.

// To stop viewing local camera preview
view.dispose();
htmlElement.removeChild(view.target);

Anfordern der Berechtigung für Kamera und Mikrofon

Eine Anwendung kann die Kamera oder das Mikrofon nicht ohne Berechtigungen verwenden. Sie können den Geräte-Manager verwenden, um Benutzer aufzufordern, Berechtigungen für die Kamera und/oder für das Mikrofon zu erteilen:

const result = await deviceManager.askDevicePermission({audio: true, video: true});

Nach Auflösung der Zusage gibt die Methode ein DeviceAccess-Objekt zurück, das angibt, ob audio- und video-Berechtigungen erteilt wurden:

console.log(result.audio);
console.log(result.video);

Hinweise

  • Das videoDevicesUpdated-Ereignis wird ausgelöst, wenn Videogeräte angeschlossen/entfernt werden.
  • Das audioDevicesUpdated-Ereignis wird ausgelöst, wenn Audiogeräte angeschlossen werden.
  • Wenn der Geräte-Manager erstellt wird, liegen ihm zunächst keine Geräteinformationen vor, falls noch keine Berechtigungen erteilt wurden. Daher ist der Gerätename zunächst leer, und es sind keine ausführlichen Geräteinformationen vorhanden. Wenn wir dann die DeviceManager.askPermission()-API aufrufen, wird der Benutzer zur Eingabe des Gerätezugriffs aufgefordert. Wenn der Benutzer „Zulassen“ auswählt, um dem Geräte-Manager Zugriff zu gewähren, erfährt dieser mehr über die Geräte des Systems, aktualisiert die Gerätelisten und gibt die Ereignisse „audioDevicesUpdated“ und „videoDevicesUpdated“ aus. Wenn ein Benutzer die Seite aktualisiert und einen Geräte-Manager erstellt, kann der Geräte-Manager mehr über Geräte erfahren, da der Benutzer bereits Zugriff gewährt hat. Die Gerätelisten werden gleich gefüllt, und es wird kein Ereignis vom Typ „audioDevicesUpdated“ oder „videoDevicesUpdated“ ausgegeben.
  • Die Enumeration/Auswahl von Sprechern wird unter Android Chrome, iOS Safari und macOS Safari nicht unterstützt.

Tätigen eines Anrufs mit Videokamera

Wichtig

Derzeit wird nur ein ausgehender lokaler Videostream unterstützt.

Zum Tätigen eines Videoanrufs müssen lokale Kameras mithilfe der Methode getCameras() in deviceManager aufgezählt werden.

Nachdem Sie eine Kamera ausgewählt haben, verwenden Sie sie, um eine LocalVideoStream-Instanz zu erstellen. Übergeben Sie sie in videoOptions als Element innerhalb des localVideoStream-Arrays an die CallAgentstartCall-Methode.

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
const placeCallOptions = {videoOptions: {localVideoStreams:[localVideoStream]}};
const userCallee = { communicationUserId: '<ACS_USER_ID>' }
const call = callAgent.startCall([userCallee], placeCallOptions);
  • Sie können auch mit der CallAgent.join()-API an einem Videoanruf teilnehmen und mit der Call.Accept()-API Videoanrufe akzeptieren und tätigen.
  • Sobald die Anrufverbindung hergestellt ist, wird automatisch ein Videostream von der ausgewählten Kamera an den anderen Teilnehmer gesendet.

Starten und Beenden des Sendens eines lokalen Videosignals während eines Anrufs

Video starten

Um während eines Anrufs ein Video zu starten, müssen Sie Kameras mit der getCameras-Methode für das deviceManager-Objekt aufzählen. Erstellen Sie dann eine neue Instanz von LocalVideoStream mit der gewünschten Kamera, und übergeben Sie anschließend das Objekt LocalVideoStream an die Methode startVideo eines vorhandenen Anrufobjekts:

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
await call.startVideo(localVideoStream);

Video beenden

Sobald Videodaten erfolgreich gesendet werden, wird eine LocalVideoStream-Instanz vom Typ Video der localVideoStreams-Sammlung für eine Anrufinstanz hinzugefügt.

Suchen des Videostreams im Call-Objekt

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'Video'} );

Beenden des lokalen Videos Um das lokale Video während eines Anrufs zu beenden, übergeben Sie die localVideoStream-Instanz, die für Video verwendet wird, an die stopVideo-Methode des Call-Objekts:

await call.stopVideo(localVideoStream);

Sie können zu einem anderen Kameragerät wechseln, während ein lokaler Videostream aktiv ist, indem Sie switchSource für die entsprechende LocalVideoStream-Instanz aufrufen:

const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);

Falls das angegebene Videogerät nicht verfügbar ist, gilt Folgendes:

  • Wenn Ihr Video deaktiviert ist und Sie ein Video mithilfe von call.startVideo() beginnen, löst diese Methode eine SourceUnavailableError aus und legt die cameraStartFailed-Benutzerdiagnose auf „true“ fest.
  • Ein Aufruf an die localVideoStream.switchSource()-Methode bewirkt, dass cameraStartFailed auf „true“ festgelegt wird. Unser Leitfaden zur Anrufdiagnose enthält zusätzliche Informationen zum Diagnostizieren von Problemen im Zusammenhang mit Anrufen.

Um zu überprüfen, ob das lokale Video aktiviert oder deaktiviert ist, können Sie die Call-Methode isLocalVideoStarted verwenden. Diese gibt entweder „true“ oder „false“ zurück:

// Check if local video is on or off
call.isLocalVideoStarted;

Zum Lauschen auf Änderungen am lokalen Video können Sie das isLocalVideoStartedChanged-Ereignis abonnieren bzw. kündigen:

// Subscribe to local video event
call.on('isLocalVideoStartedChanged', () => {
    // Callback();
});
// Unsubscribe from local video event
call.off('isLocalVideoStartedChanged', () => {
    // Callback();
});

Starten und Beenden der Bildschirmfreigabe in einem Anruf

Um die Bildschirmfreigabe während eines Anrufs zu starten, können Sie die asynchrone Methode startScreenSharing() für ein Call-Objekt verwenden:

Starten der Bildschirmfreigabe

// Start screen sharing
await call.startScreenSharing();

Suchen der Bildschirmfreigabe in der Auflistung von „LocalVideoStream“

Nachdem Sie erfolgreich mit dem Senden der Bildschirmfreigabe begonnen haben, wird eine LocalVideoStream-Instanz des Typs ScreenSharing der localVideoStreams-Sammlung in der Anrufinstanz hinzugefügt.

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing'} );

Beenden der Bildschirmfreigabe

Um die Bildschirmfreigabe während eines Aufrufs zu beenden, können Sie asynchrone API stoptScreenSharing verwenden:

// Stop screen sharing
await call.stopScreenSharing();

Überprüfen des Status der Bildschirmfreigabe

Zur Überprüfung, ob die Bildschirmfreigabe aktiviert oder deaktiviert ist, können Sie die isScreenSharingOn-API verwenden, die TRUE oder FALSE zurückgibt:

// Check if screen sharing is on or off
call.isScreenSharingOn;

Zum Lauschen auf Änderungen an der Bildschirmfreigabe können Sie das isScreenSharingOnChanged-Ereignis abonnieren bzw. kündigen:

// Subscribe to screen share event
call.on('isScreenSharingOnChanged', () => {
    // Callback();
});
// Unsubscribe from screen share event
call.off('isScreenSharingOnChanged', () => {
    // Callback();
});

Wichtig

Dieses Feature von Azure Communication Services befindet sich derzeit in der Vorschau.

Vorschau-APIs und -SDKs werden ohne Vereinbarung zum Servicelevel bereitgestellt. Es wird empfohlen, diese nicht für Produktionsworkloads zu verwenden. Einige Features werden möglicherweise nicht unterstützt oder bieten nur eingeschränkte Funktionalität.

Weitere Informationen finden Sie in den ergänzenden Nutzungsbestimmungen für Microsoft Azure-Vorschauversionen.

Die lokale Bildschirmfreigabevorschau ist in der öffentlichen Vorschau und ist im Rahmen von Version 1.15.1-beta.1+ verfügbar.

Lokale Bildschirmfreigabevorschau

Sie können einen Videostream-Renderer (VideoStreamRenderer) verwenden, um mit dem Rendern von Streams Ihrer lokalen Bildschirmfreigabe beginnen, damit Sie sehen können, was Sie als Bildschirmfreigabestream senden.

// To start viewing local screen share preview
await call.startScreenSharing();
const localScreenSharingStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing' });
const videoStreamRenderer = new VideoStreamRenderer(localScreenSharingStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

// To stop viewing local screen share preview.
await call.stopScreenSharing();
view.dispose();
htmlElement.removeChild(view.target);

// Screen sharing can also be stoped by clicking on the native browser's "Stop sharing" button.
// The isScreenSharingOnChanged event will be triggered where you can check the value of call.isScreenSharingOn.
// If the value is false, then that means screen sharing is turned off and so we can go ahead and dispose the screen share preview.
// This event is also triggered for the case when stopping screen sharing via Call.stopScreenSharing() API.
call.on('isScreenSharingOnChanged', () => {
    if (!call.isScreenSharingOn) {
        view.dispose();
        htmlElement.removeChild(view.target);
    }
});

Rendern von Video-/Bildschirmfreigabedatenströmen für Remoteteilnehmer

Beim Rendern des Videos oder der Bildschirmfreigabe eines Remoteteilnehmers besteht der erste Schritt darin, einen Verweis auf den Remotevideostream abzurufen, den Sie rendern möchten. Hierzu kann das Array oder der Videostream (videoStreams) des Remoteteilnehmers (RemoteParticipant) durchlaufen werden. Der Zugriff auf die Remoteteilnehmerauflistung erfolgt über das Call-Objekt.

const remoteVideoStream = call.remoteParticipants[0].videoStreams[0];
const streamType = remoteVideoStream.mediaStreamType;

Zum Rendern von RemoteVideoStream muss das zugehörige Ereignis vom Typ isAvailableChanged abonniert werden. Wenn sich die isAvailable-Eigenschaft in true ändert, sendet ein Remoteteilnehmer einen Videostream. Erstellen Sie daraufhin eine neue Instanz von VideoStreamRenderer und anschließend mit der asynchronen Methode createView eine neue VideoStreamRendererView-Instanz.
Sie können dann view.target an ein beliebiges Benutzeroberflächenelement anfügen.

Wenn sich die Verfügbarkeit eines Remotestreams ändert, können Sie den gesamten Videostream-Renderer (VideoStreamRenderer) oder eine bestimmte Ansicht des Videostream-Renderers (VideoStreamRendererView) zerstören. Wenn Sie beschließen, sie beizubehalten, zeigt die Ansicht einen leeren Videoframe an.

// Reference to the html's div where we would display a grid of all remote video stream from all participants.
let remoteVideosGallery = document.getElementById('remoteVideosGallery');

subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    let renderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    let remoteVideoContainer = document.createElement('div');
    remoteVideoContainer.className = 'remote-video-container';

    let loadingSpinner = document.createElement('div');
    // See the css example below for styling the loading spinner.
    loadingSpinner.className = 'loading-spinner';
    remoteVideoStream.on('isReceivingChanged', () => {
        try {
            if (remoteVideoStream.isAvailable) {
                const isReceiving = remoteVideoStream.isReceiving;
                const isLoadingSpinnerActive = remoteVideoContainer.contains(loadingSpinner);
                if (!isReceiving && !isLoadingSpinnerActive) {
                    remoteVideoContainer.appendChild(loadingSpinner);
                } else if (isReceiving && isLoadingSpinnerActive) {
                    remoteVideoContainer.removeChild(loadingSpinner);
                }
            }
        } catch (e) {
            console.error(e);
        }
    });

    const createView = async () => {
        // Create a renderer view for the remote video stream.
        view = await renderer.createView();
        // Attach the renderer view to the UI.
        remoteVideoContainer.appendChild(view.target);
        remoteVideosGallery.appendChild(remoteVideoContainer);
    }

    // Remote participant has switched video on/off
    remoteVideoStream.on('isAvailableChanged', async () => {
        try {
            if (remoteVideoStream.isAvailable) {
                await createView();
            } else {
                view.dispose();
                remoteVideosGallery.removeChild(remoteVideoContainer);
            }
        } catch (e) {
            console.error(e);
        }
    });

    // Remote participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        try {
            await createView();
        } catch (e) {
            console.error(e);
        }
    }
    
    console.log(`Initial stream size: height: ${remoteVideoStream.size.height}, width: ${remoteVideoStream.size.width}`);
    remoteVideoStream.on('sizeChanged', () => {
        console.log(`Remote video stream size changed: new height: ${remoteVideoStream.size.height}, new width: ${remoteVideoStream.size.width}`);
    });
}

Das CSS zum Formatieren des Wartekreisels für den Ladevorgang über den Remotevideostream.

.remote-video-container {
   position: relative;
}
.loading-spinner {
   border: 12px solid #f3f3f3;
   border-radius: 50%;
   border-top: 12px solid #ca5010;
   width: 100px;
   height: 100px;
   -webkit-animation: spin 2s linear infinite; /* Safari */
   animation: spin 2s linear infinite;
   position: absolute;
   margin: auto;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   transform: translate(-50%, -50%);
}
@keyframes spin {
   0% { transform: rotate(0deg); }
   100% { transform: rotate(360deg); }
}
/* Safari */
@-webkit-keyframes spin {
   0% { -webkit-transform: rotate(0deg); }
   100% { -webkit-transform: rotate(360deg); }
}

Remotevideoqualität

Ab der Version 1.15.1 bietet das Azure Communication Services WebJS SDK ein Feature namens OVC (Optimal Video Count; optimale Videoanzahl). Dieses Feature kann verwendet werden, um Anwendungen zur Laufzeit darüber zu informieren, wie viele eingehende Videos von verschiedenen Teilnehmern in einem Gruppenanruf optimal gerendert werden können (2+ Teilnehmer). Dieses Feature macht eine Eigenschaft optimalVideoCount verfügbar, die während des Anrufs dynamisch geändert wird, basierend auf den Netzwerk- und Hardwarefunktionen eines lokalen Endpunkts. Der Wert von optimalVideoCount gibt an, wie viele Videos aus einer anderen Teilnehmeranwendung zu einem bestimmten Zeitpunkt gerendert werden sollen. Anwendungen müssen diese Änderungen behandeln und die Anzahl gerenderter Videos entsprechend der Empfehlung aktualisieren. Zwischen den Updates gibt es einen Entprellungszeitraum von etwa zehn Sekunden.

Verwendung: Das optimalVideoCount-Feature ist ein Anruffeature. Sie müssen über die feature-Methode des Call-Objekts auf das OptimalVideoCount-Feature verweisen. Anschließend können Sie einen Listener über die on-Methode des Features für die optimale Videoanzahl (OptimalVideoCountCallFeature) festlegen, um benachrichtigt zu werden, wenn sich die optimale Videoanzahl ändert. Wenn Sie die Änderungen nicht mehr abonnieren möchten, können Sie die off-Methode aufrufen.

const optimalVideoCountFeature = call.feature(Features.OptimalVideoCount);
optimalVideoCountFeature.on('optimalVideoCountChanged', () => {
    const localOptimalVideoCountVariable = optimalVideoCountFeature.optimalVideoCount;
})

Beispiel: Die Anwendung soll Änderungen der optimalen Videoanzahl in Gruppenanrufen abonnieren. Eine Änderung der optimalen Videoanzahl kann entweder durch Erstellen eines neuen Renderers (createView-Methode) oder durch Löschen von Ansichten (dispose) und entsprechendes Aktualisieren des Anwendungslayouts behandelt werden.

Eigenschaften von Remotevideostreams

Für Remotevideostreams gibt es die folgenden Eigenschaften:

const id: number = remoteVideoStream.id;
  • id: ID eines Remotevideostreams.
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
  • mediaStreamType: Dies kann Video oder ScreenSharing sein.
const isAvailable: boolean = remoteVideoStream.isAvailable;
  • isAvailable: Gibt an, ob der Endpunkt des Remoteteilnehmers aktiv einen Stream sendet.
const isReceiving: boolean = remoteVideoStream.isReceiving;
  • isReceiving:
    • Informiert die Anwendung darüber, ob Remotevideostreamdaten empfangen werden oder nicht.

    • In den folgenden Szenarien ändert sich das Flag in false:

      • Ein Remoteteilnehmer, der einen mobilen Browser verwendet, versetzt die Browser-App in den Hintergrund.
      • Ein Remoteteilnehmer oder der Benutzer, der das Video empfängt, hat Netzwerkprobleme, die die Videoqualität stark beeinträchtigen.
      • Ein Remoteteilnehmer unter macOS/iOS Safari wählt auf der Adressleiste die Anhalteoption aus.
      • Die Netzwerkverbindung eines Remoteteilnehmers wurde getrennt.
      • Ein Remoteteilnehmer mit mobilem Gerät beendet den Browser.
      • Ein Remoteteilnehmer mit mobilem Gerät oder Desktop sperrt sein Gerät. Dieses Szenario gilt auch, wenn der Remoteteilnehmer einen Desktopcomputer verwendet und dieser in den Energiesparmodus wechselt.
    • In den folgenden Szenarien ändert sich das Flag in true:

      • Ein Remoteteilnehmer, der einen mobilen Browser verwendet und diesen in den Hintergrund versetzt hatte, bringt ihn wieder in den Vordergrund.
      • Ein Remoteteilnehmer unter macOS/iOS Safari wählt auf der Adressleiste die Fortsetzungsoption aus, nachdem er das Video angehalten hatte.
      • Ein Remoteteilnehmer stellt nach einer vorübergehenden Verbindungstrennung erneut eine Verbindung mit dem Netzwerk her.
      • Ein Remoteteilnehmer mit mobilem Gerät entsperrt sein Gerät und kehrt zu dem Anruf im mobilen Browser zurück.
    • Dieses Feature verbessert das Nutzungserlebnis für das Rendern von Remotevideostreams.

    • Sie können einen Wartekreisel für den Ladevorgang über den Remotevideostream anzeigen, wenn sich das Flag „isReceiving“ ändert. Sie müssen keinen Wartekreisel für den Ladevorgang implementieren, er ist jedoch die gängige Methode, um die Benutzerfreundlichkeit zu verbessern.

const size: StreamSize = remoteVideoStream.size;
  • size: Die Streamgröße mit Informationen zur Breite und Höhe des Videos.

Methoden und Eigenschaften von VideoStreamRenderer

await videoStreamRenderer.createView();

Erstellen Sie eine VideoStreamRendererView-Instanz, die auf der Benutzeroberfläche der Anwendung angefügt werden kann, um den Remotevideostream zu rendern. Verwenden Sie die asynchrone Methode createView(). Sie wird aufgelöst, wenn der Stream zum Rendern bereit ist, und gibt ein Objekt mit der Eigenschaft target zurück. Dieses stellt ein video-Element dar, das an beliebiger Stelle in die DOM-Struktur eingefügt werden kann.

videoStreamRenderer.dispose();

Löschen Sie videoStreamRenderer und alle zugeordneten VideoStreamRendererView-Instanzen.

Methoden und Eigenschaften von VideoStreamRendererView

Beim Erstellen von VideoStreamRendererView können Sie die Eigenschaften scalingMode und isMirrored angeben. scalingMode kann Stretch, Crop oder Fit sein. Bei Angabe von isMirrored wird der gerenderte Stream vertikal gespiegelt.

const videoStreamRendererView: VideoStreamRendererView = await videoStreamRenderer.createView({ scalingMode, isMirrored });

Jede VideoStreamRendererView-Instanz verfügt über eine target-Eigenschaft, die die Renderingoberfläche darstellt. Fügen Sie diese Eigenschaft in der Benutzeroberfläche der Anwendung an:

htmlElement.appendChild(view.target);

Sie können scalingMode aktualisieren, indem Sie die Methode updateScalingMode aufrufen:

view.updateScalingMode('Crop');

Senden Sie Videostreams von zwei verschiedenen Kameras im selben Anruf von demselben Desktopgerät.

Wichtig

Dieses Feature von Azure Communication Services befindet sich derzeit in der Vorschau.

Vorschau-APIs und -SDKs werden ohne Vereinbarung zum Servicelevel bereitgestellt. Es wird empfohlen, diese nicht für Produktionsworkloads zu verwenden. Einige Features werden möglicherweise nicht unterstützt oder bieten nur eingeschränkte Funktionalität.

Weitere Informationen finden Sie in den ergänzenden Nutzungsbestimmungen für Microsoft Azure-Vorschauversionen.

Das Senden von Videostreams von zwei verschiedenen Kameras im gleichen Anruf wird im Rahmen der Version 1.17.1-beta.1+ in unterstützten Desktopbrowsern unterstützt.

  • Sie können Videostreams von zwei verschiedenen Kameras aus einer einzelnen Desktopbrowserregisterkarte/-App im gleichen Aufruf mit dem folgenden Codeausschnitt senden:
// Create your first CallAgent with identity A
const callClient1 = new CallClient();
const callAgent1 = await callClient1.createCallAgent(tokenCredentialA);
const deviceManager1 = await callClient1.getDeviceManager();

// Create your second CallAgent with identity B
const callClient2 = new CallClient();
const callAgent2 = await callClient2.createCallAgent(tokenCredentialB);
const deviceManager2 = await callClient2.getDeviceManager();

// Join the call with your first CallAgent
const camera1 = await deviceManager1.getCameras()[0];
const callObj1 = callAgent1.join({ groupId: ‘123’}, { videoOptions: { localVideoStreams: [new LocalVideoStream(camera1)] } });

// Join the same call with your second CallAgent and make it use a different camera
const camera2 = (await deviceManager2.getCameras()).filter((camera) => { return camera !== camera1 })[0];
const callObj2 = callAgent2.join({ groupId: '123' }, { videoOptions: { localVideoStreams: [new LocalVideoStream(camera2)] } });

//Mute the microphone and speakers of your second CallAgent’s Call, so that there is no echos/noises.
await callObj2.muteIncomingAudio();
await callObj2.mute();

Einschränkungen:

  • Hierzu müssen zwei verschiedene CallAgent-Instanzen mit unterschiedlichen Identitäten verwendet werden. Der Codeschnipsel zeigt die Verwendung von zwei Anruf-Agents mit jeweils eigenem Call-Objekt.
  • Im Codebeispiel werden beide CallAgents demselben Aufruf (gleiche Anruf-ID) beitreten. Sie können auch mit den Agents jeweils an verschiedenen Anrufen teilnehmen und ein Video an den einen Anruf und das andere Video an den anderen Anruf senden.
  • Das Senden derselben Kamera in beiden CallAgents wird nicht unterstützt. Es müssen zwei verschiedene Kameras sein.
  • Das Senden von zwei verschiedenen Kameras mit einem CallAgent wird derzeit nicht unterstützt.
  • Auf macOS Safari können Videoeffekte im Hintergrund weichzeichnen (von @azure/communication-effects), kann nur auf eine Kamera und nicht auf beide gleichzeitig angewendet werden.

Das SDK installieren

Wählen Sie Ihre Datei build.gradle auf Projektebene aus, und fügen Sie mavenCentral() zur Liste der Repositorys unter buildscript und allprojects hinzu:

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

Fügen Sie dann in der Datei build.gradle auf Modulebene die folgenden Zeilen zum Abschnitt dependencies hinzu:

dependencies {
    ...
    implementation 'com.azure.android:azure-communication-calling:1.0.0'
    ...
}

Initialisieren der erforderlichen Objekte

Zum Erstellen einer CallAgent-Instanz müssen Sie die createCallAgent-Methode für eine CallClient-Instanz aufrufen. Dieser Aufruf gibt asynchron ein CallAgent-Instanzobjekt zurück.

Die createCallAgent-Methode verwendet CommunicationUserCredential als Argument, womit ein Zugriffstoken gekapselt wird.

Um auf DeviceManager zuzugreifen, müssen Sie zuerst eine callAgent-Instanz erstellen. Anschließend können Sie die CallClient.getDeviceManager-Methode zum Abrufen von DeviceManager verwenden.

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential).get();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

Zum Festlegen eines Anzeigenamens für den Anrufer verwenden Sie diese alternative Methode:

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgentOptions callAgentOptions = new CallAgentOptions();
callAgentOptions.setDisplayName("Alice Bob");
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential, callAgentOptions).get();

Geräteverwaltung

Um Video mit Calling verwenden zu können, müssen Sie wissen, wie Geräte verwaltet werden. Mit den Geräten können Sie die Übertragung von Audio und Video an den Anruf steuern.

DeviceManager ermöglicht Ihnen das Aufzählen lokaler Geräte, die in einem Anruf zur Übertragung Ihrer Audio-/Videostreams verwendet werden können. DeviceManager erlaubt Ihnen auch, von einem Benutzer die Berechtigung für den Zugriff auf sein Mikrofon und seine Kamera über die native Browser-API anzufordern.

Sie können auf deviceManager zugreifen, indem Sie die callClient.getDeviceManager()-Methode aufrufen.

Context appContext = this.getApplicationContext();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

Aufzählen lokaler Geräte

Für den Zugriff auf lokale Geräte können Sie Enumerationsmethoden für den Geräte-Manager verwenden. Enumeration ist ein synchroner Vorgang.

//  Get a list of available video devices for use.
List<VideoDeviceInfo> localCameras = deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

Vorschau auf lokaler Kamera

Sie können DeviceManager und Renderer verwenden, um mit dem Rendern von Streams über Ihre lokale Kamera zu beginnen. Dieser Stream wird nicht an andere Teilnehmer gesendet. Es handelt sich um einen lokalen Vorschaufeed. Dies ist ein asynchroner Vorgang.

VideoDeviceInfo videoDevice = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentVideoStream = new LocalVideoStream(videoDevice, appContext);

LocalVideoStream[] localVideoStreams = new LocalVideoStream[1];
localVideoStreams[0] = currentVideoStream;

VideoOptions videoOptions = new VideoOptions(localVideoStreams);

RenderingOptions renderingOptions = new RenderingOptions(ScalingMode.Fit);
VideoStreamRenderer previewRenderer = new VideoStreamRenderer(currentVideoStream, appContext);

VideoStreamRendererView uiView = previewRenderer.createView(renderingOptions);

// Attach the uiView to a viewable location on the app at this point
layout.addView(uiView);

Tätigen eines 1:1-Anrufs mit Videokamera

Warnung

Derzeit wird nur ein einzelner ausgehender lokaler Videostream unterstützt. Um einen Anruf mit Video zu tätigen, müssen die lokalen Kameras mit der getCameras-API von deviceManager aufgezählt werden. Sobald Sie eine gewünschte Kamera ausgewählt haben, erzeugen Sie damit eine LocalVideoStream-Instanz und übergeben diese an videoOptions als Element im localVideoStream-Array an eine call-Methode. Nachdem die Anrufverbindung hergestellt ist, wird automatisch ein Videostream von der ausgewählten Kamera an andere Teilnehmende gesendet.

Hinweis

Aus Gründen des Datenschutzes wird das Video nicht für den Anruf freigegeben, wenn es nicht lokal in der Vorschau angezeigt wird. Weitere Informationen finden Sie unter Vorschau auf lokaler Kamera.

VideoDeviceInfo desiredCamera = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentVideoStream = new LocalVideoStream(desiredCamera, appContext);

LocalVideoStream[] localVideoStreams = new LocalVideoStream[1];
localVideoStreams[0] = currentVideoStream;

VideoOptions videoOptions = new VideoOptions(localVideoStreams);

// Render a local preview of video so the user knows that their video is being shared
Renderer previewRenderer = new VideoStreamRenderer(currentVideoStream, appContext);
View uiView = previewRenderer.createView(new CreateViewOptions(ScalingMode.FIT));

// Attach the uiView to a viewable location on the app at this point
layout.addView(uiView);

CommunicationUserIdentifier[] participants = new CommunicationUserIdentifier[]{ new CommunicationUserIdentifier("<acs user id>") };

StartCallOptions startCallOptions = new StartCallOptions();
startCallOptions.setVideoOptions(videoOptions);

Call call = callAgent.startCall(context, participants, startCallOptions);

Starten und Beenden des Sendens von lokalem Video

Um ein Video zu starten, müssen Sie die Kameras mit der getCameraList-API für das deviceManager-Objekt aufzählen. Erstellen Sie dann eine neue Instanz von LocalVideoStream zur Übergabe der gewünschten Kamera, und übergeben Sie sie in der startVideo-API als Argument:

VideoDeviceInfo desiredCamera = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentLocalVideoStream = new LocalVideoStream(desiredCamera, appContext);

VideoOptions videoOptions = new VideoOptions(currentLocalVideoStream);

Future startVideoFuture = call.startVideo(appContext, currentLocalVideoStream);
startVideoFuture.get();

Sobald Sie erfolgreich mit dem Senden von Video beginnen, wird eine LocalVideoStream-Instanz der localVideoStreams-Sammlung für die Anrufinstanz hinzugefügt.

List<LocalVideoStream> videoStreams = call.getLocalVideoStreams();
LocalVideoStream currentLocalVideoStream = videoStreams.get(0); // Please make sure there are VideoStreams in the list before calling get(0).

Um das lokale Video anzuhalten, übergeben Sie die LocalVideoStream-Instanz, die in der localVideoStreams-Sammlung verfügbar ist:

call.stopVideo(appContext, currentLocalVideoStream).get();

Sie können während des Sendens von Video auf ein anderes Kameragerät umschalten, indem Sie switchSource für eine LocalVideoStream-Instanz aufrufen:

currentLocalVideoStream.switchSource(source).get();

Rendern von Videostreams von Remoteteilnehmern

Um die Video- und Bildschirmübertragungs-Streams der Remoteteilnehmer aufzulisten, sehen Sie sich die videoStreams-Sammlungen an:

List<RemoteParticipant> remoteParticipants = call.getRemoteParticipants();
RemoteParticipant remoteParticipant = remoteParticipants.get(0); // Please make sure there are remote participants in the list before calling get(0).

List<RemoteVideoStream> remoteStreams = remoteParticipant.getVideoStreams();
RemoteVideoStream remoteParticipantStream = remoteStreams.get(0); // Please make sure there are video streams in the list before calling get(0).

MediaStreamType streamType = remoteParticipantStream.getType(); // of type MediaStreamType.Video or MediaStreamType.ScreenSharing

Um einen RemoteVideoStream von einem Remoteteilnehmer zu rendern, müssen Sie ein OnVideoStreamsUpdated-Ereignis abonnieren.

Innerhalb des Ereignisses zeigt der Wechsel der isAvailable-Eigenschaft zu „true“ an, dass der Remoteteilnehmer gerade einen Stream sendet. Sobald dies geschieht, erstellen Sie eine neue Instanz eines Renderer, dann erstellen Sie eine neue RendererView mithilfe der asynchronen createView-API, und fügen Sie view.target an beliebiger Stelle in der Benutzeroberfläche Ihrer Anwendung an.

Immer wenn sich die Verfügbarkeit eines Remotestreams ändert, können Sie wählen, ob Sie den gesamten Renderer, eine bestimmte RendererView zerstören oder sie behalten möchten, was jedoch zur Anzeige leerer Videobilder führt.

VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteParticipantStream, appContext);
VideoStreamRendererView uiView = remoteVideoRenderer.createView(new RenderingOptions(ScalingMode.FIT));
layout.addView(uiView);

remoteParticipant.addOnVideoStreamsUpdatedListener(e -> onRemoteParticipantVideoStreamsUpdated(p, e));

void onRemoteParticipantVideoStreamsUpdated(RemoteParticipant participant, RemoteVideoStreamsEvent args) {
    for(RemoteVideoStream stream : args.getAddedRemoteVideoStreams()) {
        if(stream.getIsAvailable()) {
            startRenderingVideo();
        } else {
            renderer.dispose();
        }
    }
}

Eigenschaften von Remotevideostreams

Ein Remotevideostream hat verschiedene Eigenschaften.

  • Id: ID eines Remotevideostreams
int id = remoteVideoStream.getId();
  • MediaStreamType: Kann „Video“ oder „ScreenSharing“ sein
MediaStreamType type = remoteVideoStream.getMediaStreamType();
  • isAvailable: gibt an, ob der Endpunkt des Remoteteilnehmers einen Stream aktiv sendet
boolean availability = remoteVideoStream.isAvailable();

Methoden und Eigenschaften des Renderers

Rendererobjekt nach APIs

  • Erstellt eine VideoStreamRendererView-Instanz, die später auf der Benutzeroberfläche der Anwendung angefügt werden kann, um Remotevideostreams zu rendern.
// Create a view for a video stream
VideoStreamRendererView.createView()
  • Löscht den Renderer und alle mit ihm verknüpften VideoStreamRendererView-Instanzen. Wird aufgerufen, wenn Sie alle zugehörigen Ansichten aus der Benutzeroberfläche entfernt haben.
VideoStreamRenderer.dispose()
  • StreamSize: Größe (Breite/Höhe) eines Remotevideostreams
StreamSize renderStreamSize = VideoStreamRenderer.getSize();
int width = renderStreamSize.getWidth();
int height = renderStreamSize.getHeight();

Methoden und Eigenschaften von RendererView

Beim Erstellen einer VideoStreamRendererView können Sie die Eigenschaften ScalingMode und mirrored angeben, die für diese Ansicht gelten sollen: Der Skalierungsmodus kann „CROP“ oder „FIT“ sein.

VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteVideoStream, appContext);
VideoStreamRendererView rendererView = remoteVideoRenderer.createView(new CreateViewOptions(ScalingMode.Fit));

Das erstellte RendererView-Objekt kann dann mit dem folgenden Ausschnitt an die Benutzeroberfläche der Anwendung angefügt werden:

layout.addView(rendererView);

Sie können den Skalierungsmodus später durch Aufrufen der updateScalingMode-API für das RendererView-Objekt mit entweder ScalingMode.CROP oder ScalingMode.FIT als Argument aktualisieren.

// Update the scale mode for this view.
rendererView.updateScalingMode(ScalingMode.CROP)

Einrichten des Systems

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 Schnellstart wird das SwiftUI-Framework verwendet. Legen Sie daher Sprache auf Swift und Schnittstelle auf SwiftUI fest.

Im Rahmen dieser Schnellstartanleitung werden keine Tests erstellt. Sie können das Kontrollkästchen Tests einschließen deaktivieren.

Screenshot: Fenster zum Erstellen eines Projekts in Xcode

Installieren des Pakets und der Abhängigkeiten mithilfe von CocoaPods

  1. Erstellen Sie eine Podfile-Datei für die Anwendung, wie in diesem Beispiel:

    platform :ios, '13.0'
    use_frameworks!
    target 'AzureCommunicationCallingSample' do
        pod 'AzureCommunicationCalling', '~> 1.0.0'
    end
    
  2. Führen Sie pod install aus.

  3. Öffnen Sie .xcworkspace mithilfe von Xcode.

Anfordern des Zugriffs auf das Mikrofon

Um auf das Mikrofon des Geräts zuzugreifen, müssen Sie die Liste der Informationseigenschaften Ihrer App mithilfe von NSMicrophoneUsageDescription aktualisieren. Legen Sie den zugehörigen Wert auf eine Zeichenfolge fest. Diese wird in das Dialogfeld eingeschlossen, mit dem das System Zugriff vom Benutzer anfordert.

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

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

Einrichten des App-Frameworks

Öffnen Sie die Datei ContentView.swift Ihres Projekts. Fügen Sie am Anfang der Datei eine import-Deklaration hinzu, um die AzureCommunicationCalling-Bibliothek zu importieren. Importieren Sie außerdem AVFoundation. Dies ist für Audioberechtigungsanforderungen im Code erforderlich.

import AzureCommunicationCalling
import AVFoundation

Initialisieren von „CallAgent“

Um eine CallAgent-Instanz auf der Grundlage von CallClient zu erstellen, müssen Sie eine Methode vom Typ callClient.createCallAgent verwenden, die asynchron ein Objekt vom Typ CallAgent zurückgibt, nachdem es initialisiert wurde.

Übergeben Sie für die Erstellung eines Anrufclients ein Objekt vom Typ CommunicationTokenCredential:

import AzureCommunication

let tokenString = "token_string"
var userCredential: CommunicationTokenCredential?
do {
    let options = CommunicationTokenRefreshOptions(initialToken: token, refreshProactively: true, tokenRefresher: self.fetchTokenSync)
    userCredential = try CommunicationTokenCredential(withOptions: options)
} catch {
    updates("Couldn't created Credential object", false)
    initializationDispatchGroup!.leave()
    return
}

// tokenProvider needs to be implemented by Contoso, which fetches a new token
public func fetchTokenSync(then onCompletion: TokenRefreshOnCompletion) {
    let newToken = self.tokenProvider!.fetchNewToken()
    onCompletion(newToken, nil)
}

Übergeben Sie das von Ihnen erstellte CommunicationTokenCredential-Objekt an CallClient, und legen Sie den Anzeigenamen fest:

self.callClient = CallClient()
let callAgentOptions = CallAgentOptions()
options.displayName = " iOS Azure Communication Services User"

self.callClient!.createCallAgent(userCredential: userCredential!,
    options: callAgentOptions) { (callAgent, error) in
        if error == nil {
            print("Create agent succeeded")
            self.callAgent = callAgent
        } else {
            print("Create agent failed")
        }
})

Verwalten von Geräten

Um Video mit Calling verwenden zu können, müssen Sie wissen, wie Geräte verwaltet werden. Mit den Geräten können Sie die Übertragung von Audio und Video an den Anruf steuern.

DeviceManager ermöglicht das Aufzählen lokaler Geräte, die in einem Anruf zur Übertragung von Audio- oder Videostreams verwendet werden können. Außerdem können Sie damit beim Benutzer auch Zugriff auf ein Mikrofon oder auf eine Kamera anfordern. Auf deviceManager kann im Objekt callClient zugegriffen werden.

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

Aufzählen lokaler Geräte

Für den Zugriff auf lokale Geräte können Enumerationsmethoden für den Geräte-Manager verwendet werden. Enumeration ist ein synchroner Vorgang.

// enumerate local cameras
var localCameras = deviceManager.cameras // [VideoDeviceInfo, VideoDeviceInfo...]

Abrufen einer Vorschau der lokalen Kamera

Sie können mit Renderer mit dem Rendern eines Streams von Ihrer lokalen Kamera beginnen. Dieser Stream wird nicht an andere Teilnehmer gesendet. Es handelt sich um einen lokalen Vorschaufeed. Dies ist ein asynchroner Vorgang.

let camera: VideoDeviceInfo = self.deviceManager!.cameras.first!
let localVideoStream = LocalVideoStream(camera: camera)
let localRenderer = try! VideoStreamRenderer(localVideoStream: localVideoStream)
self.view = try! localRenderer.createView()

Abrufen von Eigenschaften der Vorschau der lokalen Kamera

Der Renderer verfügt über eine Reihe von Eigenschaften und Methoden zum Steuern des Renderings.

// Constructor can take in LocalVideoStream or RemoteVideoStream
let localRenderer = VideoStreamRenderer(localVideoStream:localVideoStream)
let remoteRenderer = VideoStreamRenderer(remoteVideoStream:remoteVideoStream)

// [StreamSize] size of the rendering view
localRenderer.size

// [VideoStreamRendererDelegate] an object you provide to receive events from this Renderer instance
localRenderer.delegate

// [Synchronous] create view
try! localRenderer.createView()

// [Synchronous] create view with rendering options
try! localRenderer!.createView(withOptions: CreateViewOptions(scalingMode: ScalingMode.fit))

// [Synchronous] dispose rendering view
localRenderer.dispose()

Tätigen eines 1:1-Anrufs mit Video

Wie Sie eine Geräte-Manager-Instanz erhalten, erfahren Sie im Abschnitt zur Geräteverwaltung.

let firstCamera = self.deviceManager!.cameras.first
self.localVideoStreams = [LocalVideoStream]()
self.localVideoStreams!.append(LocalVideoStream(camera: firstCamera!))
let videoOptions = VideoOptions(localVideoStreams: self.localVideoStreams!)

let startCallOptions = StartCallOptions()
startCallOptions.videoOptions = videoOptions

let callee = CommunicationUserIdentifier('UserId')
self.callAgent?.startCall(participants: [callee], options: startCallOptions) { (call, error) in
    if error == nil {
        print("Successfully started outgoing video call")
        self.call = call
    } else {
        print("Failed to start outgoing video call")
    }
}

Rendern von Videostreams von Remoteteilnehmern

Remoteteilnehmer können während eines Anrufs eine Video- oder Bildschirmfreigabe initiieren.

Behandeln von Video- oder Bildschirmfreigabestreams von Remoteteilnehmern

Untersuchen Sie die Sammlungen vom Typ videoStreams, um die Streams von Remoteteilnehmern aufzulisten.

var remoteParticipantVideoStream = call.remoteParticipants[0].videoStreams[0]

Abrufen von Eigenschaften von Remotevideostreams

var type: MediaStreamType = remoteParticipantVideoStream.type // 'MediaStreamTypeVideo'
var isAvailable: Bool = remoteParticipantVideoStream.isAvailable // indicates if remote stream is available
var id: Int = remoteParticipantVideoStream.id // id of remoteParticipantStream

Rendern von Streams von Remoteteilnehmern

Verwenden Sie den folgenden Code, um das Rendern von Streams von Remoteteilnehmern zu starten:

let renderer = VideoStreamRenderer(remoteVideoStream: remoteParticipantVideoStream)
let targetRemoteParticipantView = renderer?.createView(withOptions: CreateViewOptions(scalingMode: ScalingMode.crop))
// To update the scaling mode later
targetRemoteParticipantView.update(scalingMode: ScalingMode.fit)

Abrufen von Methoden und Eigenschaften des Remotevideo-Renderers

// [Synchronous] dispose() - dispose renderer and all `RendererView` associated with this renderer. To be called when you have removed all associated views from the UI.
remoteVideoRenderer.dispose()

Einrichten des Systems

Erstellen des Visual Studio-Projekts

Erstellen Sie für eine UWP-App in Visual Studio 2022 ein neues Projekt vom Typ Leere App (universelles Windows). Nachdem Sie den Projektnamen eingegeben haben, können Sie ein beliebiges Windows SDK höher als Version 10.0.17763.0 auswählen.

Für eine WinUI 3-App erstellen Sie ein neues Projekt mit der Vorlage Leere App, Gepackt (WinUI 3 in Desktop), um eine WinUI 3-Single-Page-Webanwendung einzurichten. Windows-App SDK-Version 1.3 oder höher ist erforderlich.

Installieren Sie das Paket und die Abhängigkeiten mit dem NuGet-Paket-Manager

Die Calling SDK-APIs und -Bibliotheken sind über ein NuGet-Paket öffentlich verfügbar.

In den folgenden Schritten wird veranschaulicht, wie Sie das NuGet-Paket für das Calling SDK suchen, herunterladen und installieren:

  1. Öffnen Sie den NuGet-Paket-Manager, indem Sie Tools>NuGet-Paket-Manager>NuGet-Pakete für Lösung verwalten auswählen.
  2. Wählen Sie Durchsuchen aus, und geben Sie dann Azure.Communication.Calling.WindowsClient in das Suchfeld ein.
  3. Stellen Sie sicher, dass das Kontrollkästchen Vorabversion einbeziehen aktiviert ist.
  4. Wählen Sie das Azure.Communication.Calling.WindowsClient-Paket und dann Azure.Communication.Calling.WindowsClient1.4.0-beta.1 oder eine neuere Version aus.
  5. Aktivieren Sie das Kontrollkästchen, das dem Communication Services-Projekt auf der rechten Registerkarte entspricht.
  6. Wählen Sie die Schaltfläche Installieren aus.

Anfordern des Zugriffs auf das Mikrofon

Für die App ist der Zugriff auf die Kamera erforderlich, damit sie ordnungsgemäß ausgeführt wird. In UWP-Apps sollte die Kamerafunktion in der App-Manifestdatei deklariert werden.

Mit den folgenden Schritten wird veranschaulicht, wie dies erreicht werden kann.

  1. Doppelklicken Sie im Panel Solution Explorer auf die Datei mit der Erweiterung .appxmanifest.
  2. Klicken Sie auf die Registerkarte Capabilities.
  3. Aktivieren Sie in der Liste der Funktionen das Kontrollkästchen Camera.

Erstellen von Benutzeroberflächenschaltflächen zum Tätigen und Beenden des Anrufs

Diese einfache Beispiel-App enthält zwei Schaltflächen. eine zum Tätigen und eine weitere zum Beenden des Anrufs. In den folgenden Schritten wird veranschaulicht, wie Sie diese Schaltflächen zur App hinzufügen.

  1. Doppelklicken Sie im Bereich Solution Explorer für UWP auf die Datei namens MainPage.xaml bzw. für WinUI 3 auf MainWindows.xaml.
  2. Suchen Sie im zentralen Panel in der Vorschau der Benutzeroberfläche nach dem XAML-Code.
  3. Ersetzen Sie den XAML-Code durch den folgenden Auszug:
<TextBox x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" />
<StackPanel>
    <Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" />
    <Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" />
</StackPanel>

Einrichten der App mit Calling SDK-APIs

Die Calling SDK-APIs befinden sich in zwei verschiedenen Namespaces. Mit den folgenden Schritten wird der C#-Compiler über diese Namespaces informiert, damit das IntelliSense-Feature von Visual Studio verwendet werden kann.

  1. Klicken Sie im Bereich Solution Explorer für UWP auf den Pfeil links neben der Datei mit dem Namen MainPage.xaml bzw. für WinUI 3 auf MainWindows.xaml.
  2. Doppelklicken Sie auf die Datei namens MainPage.xaml.cs oder MainWindows.xaml.cs.
  3. Fügen Sie am Ende der aktuellen using-Anweisungen die folgenden Befehle hinzu.
using Azure.Communication.Calling.WindowsClient;

MainPage.xaml.cs oder MainWindows.xaml.cs offen halten. In den nächsten Schritten wird weiterer Code hinzugefügt.

Zulassen von App-Interaktionen

Die zuvor hinzugefügten Schaltflächen sind von CommunicationCall abhängig. Das bedeutet, dass ein CommunicationCall-Datenelement zur Klasse MainPage oder MainWindow hinzugefügt werden muss. Darüber hinaus muss derselben Klasse ein CallAgent-Datenelement hinzugefügt werden, damit der asynchrone Vorgang für die Erstellung von CallAgent erfolgreich ist.

Fügen Sie der Klasse MainPage oder MainWindow die folgenden Datenelemente hinzu:

CallAgent callAgent;
CommunicationCall call;

Erstellen von Schaltflächenhandlern

Zuvor wurden dem XAML-Code zwei Schaltflächen hinzugefügt. Der folgende Code fügt die Handler hinzu, die ausgeführt werden sollen, wenn ein Benutzer die Schaltfläche auswählt. Der folgende Code sollte nach den Datenelementen aus dem vorherigen Abschnitt hinzugefügt werden.

private async void CallButton_Click(object sender, RoutedEventArgs e)
{
    // Start call
}

private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
    // End the current call
}

Objektmodell

Die folgenden Klassen und Schnittstellen befassen sich mit einigen der wichtigsten Features der Azure Communication Services-Clientbibliothek „Calling“ für UWP.

Name BESCHREIBUNG
CallClient CallClient ist der Haupteinstiegspunkt der Calling-Client-Bibliothek.
CallAgent CallAgent dient zum Starten von und Teilnehmen an Anrufen.
CommunicationCall CommunicationCall dient zum Verwalten von Anrufen, die getätigt wurden oder an denen teilgenommen wurde.
CommunicationTokenCredential CommunicationTokenCredential dient als tokengestützte Anmeldeinformation zum Instanziieren von CallAgent.
CallAgentOptions Die CallAgentOptions enthält Informationen zum Identifizieren des Anrufers.
HangupOptions HangupOptions gibt an, ob ein Anruf für alle Teilnehmer beendet werden soll.

Registrieren des Videoschemahandlers

Eine Benutzeroberflächenkomponente wie MediaElement oder MediaPlayerElement von XAML erfordert die Registrierung einer Konfiguration zum Rendern von lokalen und Remotevideofeeds durch die App. Fügen Sie den folgenden Inhalt zwischen den Package-Tags von Package.appxmanifest hinzu:

<Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
        <InProcessServer>
            <Path>RtmMvrUap.dll</Path>
            <ActivatableClass ActivatableClassId="VideoN.VideoSchemeHandler" ThreadingModel="both" />
        </InProcessServer>
    </Extension>
</Extensions>

Initialisieren von „CallAgent“

Zum Erzeugen einer CallAgent-Instanz anhand von CallClient müssen Sie die Methode CallClient.CreateCallAgentAsync verwenden, die ein CallAgent-Objekt asynchron zurückgibt, nachdem es initialisiert wurde.

Zum Erstellen von CallAgent müssen Sie ein CallTokenCredential-Objekt und ein CallAgentOptions-Objekt übergeben. Denken Sie daran, dass CallTokenCredential eine Ausnahme auslöst, wenn ein falsch formatiertes Token übergeben wird.

Der folgende Code sollte innerhalb einer Hilfsfunktion hinzugefügt werden, die bei der App-Initialisierung aufgerufen werden soll.

var callClient = new CallClient();
this.deviceManager = await callClient.GetDeviceManagerAsync();

var tokenCredential = new CallTokenCredential("<AUTHENTICATION_TOKEN>");
var callAgentOptions = new CallAgentOptions()
{
    DisplayName = "<DISPLAY_NAME>"
};

this.callAgent = await callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);
this.callAgent.CallsUpdated += Agent_OnCallsUpdatedAsync;
this.callAgent.IncomingCallReceived += Agent_OnIncomingCallAsync;

Ändern Sie das <AUTHENTICATION_TOKEN> mit einem gültigen Anmeldeinformationstoken für Ihre Ressource. Wenn ein Anmeldeinformationstoken eingebunden werden muss, lesen Sie die Dokumentation zu Benutzerzugriffstoken.

Tätigen eines 1:1-Anrufs mit Videokamera

Die Objekte, die zum Erstellen von CallAgent erforderlich sind, sind jetzt bereit. Es ist an der Zeit, CallAgent asynchron zu erstellen und einen Videoanruf zu tätigen.

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

    if (!string.IsNullOrEmpty(callString))
    {
        if (callString.StartsWith("8:")) // 1:1 Azure Communication Services call
        {
            this.call = await StartAcsCallAsync(callString);
        }
    }

    if (this.call != null)
    {
        this.call.RemoteParticipantsUpdated += OnRemoteParticipantsUpdatedAsync;
        this.call.StateChanged += OnStateChangedAsync;
    }
}

private async Task<CommunicationCall> StartAcsCallAsync(string acsCallee)
{
    var options = await GetStartCallOptionsAsynnc();
    var call = await this.callAgent.StartCallAsync( new [] { new UserCallIdentifier(acsCallee) }, options);
    return call;
}

var micStream = new LocalOutgoingAudioStream(); // Create a default local audio stream
var cameraStream = new LocalOutgoingVideoStreamde(this.viceManager.Cameras.FirstOrDefault() as VideoDeviceDetails); // Create a default video stream

private async Task<StartCallOptions> GetStartCallOptionsAsynnc()
{
    return new StartCallOptions() {
        OutgoingAudioOptions = new OutgoingAudioOptions() { IsMuted = true, Stream = micStream  },
        OutgoingVideoOptions = new OutgoingVideoOptions() { Streams = new OutgoingVideoStream[] { cameraStream } }
    };
}

Vorschau auf lokaler Kamera

Sie können optional die lokale Kameravorschau einrichten. Das Video kann über MediaPlayerElement gerendert werden:

<Grid>
    <MediaPlayerElement x:Name="LocalVideo" AutoPlay="True" />
    <MediaPlayerElement x:Name="RemoteVideo" AutoPlay="True" />
</Grid>

So initialisieren Sie die lokale MediaPlayerElement-Vorschau:

private async void CameraList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (cameraStream != null)
    {
        await cameraStream?.StopPreviewAsync();
        if (this.call != null)
        {
            await this.call?.StopVideoAsync(cameraStream);
        }
    }
    var selectedCamerea = CameraList.SelectedItem as VideoDeviceDetails;
    cameraStream = new LocalOutgoingVideoStream(selectedCamerea);

    var localUri = await cameraStream.StartPreviewAsync();
    LocalVideo.Source = MediaSource.CreateFromUri(localUri);

    if (this.call != null) {
        await this.call?.StartVideoAsync(cameraStream);
    }
}

Rendern des Remotekamerastreams

Richten Sie einen Ereignishandler als Reaktion auf das Ereignis OnCallsUpdated ein:

private async void OnCallsUpdatedAsync(object sender, CallsUpdatedEventArgs args)
{
    var removedParticipants = new List<RemoteParticipant>();
    var addedParticipants = new List<RemoteParticipant>();

    foreach(var call in args.RemovedCalls)
    {
        removedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    foreach (var call in args.AddedCalls)
    {
        addedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    await OnParticipantChangedAsync(removedParticipants, addedParticipants);
}

private async void OnRemoteParticipantsUpdatedAsync(object sender, ParticipantsUpdatedEventArgs args)
{
    await OnParticipantChangedAsync(
        args.RemovedParticipants.ToList<RemoteParticipant>(),
        args.AddedParticipants.ToList<RemoteParticipant>());
}

private async Task OnParticipantChangedAsync(IEnumerable<RemoteParticipant> removedParticipants, IEnumerable<RemoteParticipant> addedParticipants)
{
    foreach (var participant in removedParticipants)
    {
        foreach(var incomingVideoStream in  participant.IncomingVideoStreams)
        {
            var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
            if (remoteVideoStream != null)
            {
                await remoteVideoStream.StopPreviewAsync();
            }
        }
        participant.VideoStreamStateChanged -= OnVideoStreamStateChanged;
    }

    foreach (var participant in addedParticipants)
    {
        participant.VideoStreamStateChanged += OnVideoStreamStateChanged;
    }
}

private void OnVideoStreamStateChanged(object sender, VideoStreamStateChangedEventArgs e)
{
    CallVideoStream callVideoStream = e.CallVideoStream;

    switch (callVideoStream.StreamDirection)
    {
        case StreamDirection.Outgoing:
            OnOutgoingVideoStreamStateChanged(callVideoStream as OutgoingVideoStream);
            break;
        case StreamDirection.Incoming:
            OnIncomingVideoStreamStateChanged(callVideoStream as IncomingVideoStream);
            break;
    }
}

Starten Sie das Rendern des Remotevideostreams für MediaPlayerElement:

private async void OnIncomingVideoStreamStateChanged(IncomingVideoStream incomingVideoStream)
{
    switch (incomingVideoStream.State)
    {
        case VideoStreamState.Available:
            {
                switch (incomingVideoStream.Kind)
                {
                    case VideoStreamKind.RemoteIncoming:
                        var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                        var uri = await remoteVideoStream.StartPreviewAsync();

                        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                        {
                            RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                        });

                        /* Or WinUI 3
                        this.DispatcherQueue.TryEnqueue(() => {
                            RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                            RemoteVideo.MediaPlayer.Play();
                        });
                        */

                        break;

                    case VideoStreamKind.RawIncoming:
                        break;
                }

                break;
            }
        case VideoStreamState.Started:
            break;
        case VideoStreamState.Stopping:
            break;
        case VideoStreamState.Stopped:
            if (incomingVideoStream.Kind == VideoStreamKind.RemoteIncoming)
            {
                var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                await remoteVideoStream.StopPreviewAsync();
            }
            break;
        case VideoStreamState.NotAvailable:
            break;
    }
}

Beenden eines Anrufs

Sobald ein Anruf getätigt wurde, sollte die HangupAsync-Methode des CommunicationCall-Objekts verwendet werden, um den Anruf zu beenden.

Eine Instanz von HangupOptions sollte auch verwendet werden, um anzugeben, ob der Anruf für alle Teilnehmer beendet werden muss.

Der folgende Code sollte in HangupButton_Click hinzugefügt werden.

var call = this.callAgent?.Calls?.FirstOrDefault();
if (call != null)
{
    var call = this.callAgent?.Calls?.FirstOrDefault();
    if (call != null)
    {
        foreach (var localVideoStream in call.OutgoingVideoStreams)
        {
            await call.StopVideoAsync(localVideoStream);
        }

        try
        {
            if (cameraStream != null)
            {
                await cameraStream.StopPreviewAsync();
            }

            await call.HangUpAsync(new HangUpOptions() { ForEveryone = false });
        }
        catch(Exception ex) 
        { 
            var errorCode = unchecked((int)(0x0000FFFFU & ex.HResult));
            if (errorCode != 98) // Sample error code, sam_status_failed_to_hangup_for_everyone (98)
            {
                throw;
            }
        }
    }
}

Ausführen des Codes

Stellen Sie sicher, dass Visual Studio die App für x64, x86 oder ARM64 erstellt, und klicken Sie dann auf F5, um die Ausführung der App zu starten. Klicken Sie anschließend auf die Schaltfläche CommunicationCall, um den festgelegten Gesprächspartner anzurufen.

Beachten Sie, dass das System beim ersten Ausführen der App den Benutzer zur Gewährung des Zugriffs auf das Mikrofon auffordert.

Nächste Schritte