Udostępnij za pośrednictwem


Zarządzanie wideo podczas połączeń

Dowiedz się, jak zarządzać wywołaniami wideo za pomocą zestawu SDK usług Azure Communication Services. Dowiesz się, jak zarządzać odbieraniem i wysyłaniem wideo w ramach połączenia.

Wymagania wstępne

Instalacja zestawu SDK

Użyj polecenia , npm install aby zainstalować wspólny zestaw SDK usług Azure Communication Services i wywołujący dla języka JavaScript:

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

Inicjowanie wymaganych obiektów

Wystąpienie CallClient jest wymagane w przypadku większości operacji wywołania. Podczas tworzenia nowego CallClient wystąpienia można skonfigurować je za pomocą opcji niestandardowych, takich jak Logger wystąpienie.

CallClient Za pomocą wystąpienia można utworzyć CallAgent wystąpienie, wywołując element createCallAgent. Ta metoda asynchronicznie zwraca CallAgent obiekt wystąpienia.

Metoda createCallAgent używa CommunicationTokenCredential jako argumentu. Akceptuje token dostępu użytkownika.

Aby uzyskać dostęp deviceManagerdo obiektu , możesz użyć getDeviceManager metody w wystąpieniu CallClient .

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

Jak najlepiej zarządzać łącznością zestawu SDK z infrastrukturą firmy Microsoft

Wystąpienie Call Agent pomaga zarządzać wywołaniami (aby dołączyć lub uruchomić wywołania). Aby można było pracować z zestawem SDK wywołującym, należy połączyć się z infrastrukturą firmy Microsoft, aby otrzymywać powiadomienia o połączeniach przychodzących i koordynować inne szczegóły połączeń. Istnieją Call Agent dwa możliwe stany:

PołączonoCall Agent wartość Connected connectionStatue oznacza, że zestaw SDK klienta jest połączony i może odbierać powiadomienia z infrastruktury firmy Microsoft.

RozłączoneCall Agent wartość Disconnected connectionStatue stanów występuje problem uniemożliwiający prawidłowe nawiązywanie połączenia z zestawem SDK. Call Agent należy utworzyć ponownie.

  • invalidToken: Jeśli token wygasł lub jest nieprawidłowe Call Agent wystąpienie rozłącza się z tym błędem.
  • connectionIssue: Jeśli występuje problem z połączeniem klienta z infrastrukturą firmy Microsoft, po wielu ponownych próbach Call Agent connectionIssue wystąpi błąd.

Możesz sprawdzić, czy środowisko lokalne Call Agent jest połączone z infrastrukturą firmy Microsoft, sprawdzając bieżącą connectionState wartość właściwości. Podczas aktywnego wywołania można nasłuchiwać zdarzenia, connectionStateChanged aby określić, czy Call Agent zmiany ze stanu Połączono z Odłączone .

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

Zarządzanie urządzeniami

Aby rozpocząć korzystanie z wideo z zestawem SDK wywoływania, musisz mieć możliwość zarządzania urządzeniami. Urządzenia umożliwiają sterowanie tym, co przesyła dźwięk i wideo do wywołania.

Za pomocą programu deviceManagermożna wyliczyć urządzenia lokalne, które mogą przesyłać strumienie audio i wideo w wywołaniu. Możesz również użyć polecenia deviceManager , aby zażądać uprawnień dostępu do mikrofonów i kamer urządzenia lokalnego.

Dostęp można uzyskać deviceManager , wywołując metodę callClient.getDeviceManager() :

const deviceManager = await callClient.getDeviceManager();

Pobieranie urządzeń lokalnych

Aby uzyskać dostęp do urządzeń lokalnych, możesz użyć deviceManager metod getCameras() wyliczania i getMicrophones. Te metody są akcjami asynchronicznymi.

//  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...]

Ustawianie urządzeń domyślnych

Gdy dowiesz się, jakie urządzenia są dostępne do użycia, możesz ustawić domyślne urządzenia dla mikrofonu, głośnika i aparatu fotograficznego. Jeśli wartości domyślne klienta nie są ustawione, zestaw SDK usług komunikacyjnych używa wartości domyślnych systemu operacyjnego.

Mikrofon

Uzyskiwanie dostępu do używanego urządzenia

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

Ustawianie urządzenia do użycia

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

Speaker

Uzyskiwanie dostępu do używanego urządzenia

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

Ustawianie urządzenia do użycia

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

Kamera

Uzyskiwanie dostępu do używanego urządzenia

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

Ustawianie urządzenia do użycia

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

Każdy CallAgent z nich może wybrać swój własny mikrofon i głośniki na skojarzonym z nim DeviceManager. Zalecamy, aby różne CallAgents mikrofony i głośniki używały różnych mikrofonów. Nie powinny one współdzielić tych samych mikrofonów ani głośników. W przypadku udostępniania może zostać wyzwolona diagnostyka mikrofonu, a mikrofon przestanie działać w zależności od przeglądarki/systemu operacyjnego.

Lokalny strumień wideo

Aby móc wysyłać wideo w wywołaniu, należy utworzyć LocalVideoStreamobiekt.

const localVideoStream = new LocalVideoStream(camera);

Aparat przekazany jako parametr jest jednym z VideoDeviceInfo obiektów zwracanych przez metodę deviceManager.getCameras().

Element LocalVideoStream ma następujące właściwości:

  • source: informacje o urządzeniu.
const source = localVideoStream.source;
  • mediaStreamType: może to być Video, ScreenSharinglub RawMedia.
const type: MediaStreamType = localVideoStream.mediaStreamType;

Podgląd aparatu lokalnego

Możesz użyć funkcji deviceManager i VideoStreamRenderer , aby rozpocząć renderowanie strumieni z aparatu lokalnego. Po utworzeniu elementu LocalVideoStream użyj go do skonfigurowaniaVideoStreamRenderer. Po utworzeniu VideoStreamRenderermetody wywołaj metodę createView() , aby uzyskać widok, który można dodać jako element podrzędny do strony.

Ten strumień nie jest wysyłany do innych uczestników; jest to lokalny kanał informacyjny w wersji zapoznawczej.

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

Zatrzymywanie lokalnej wersji zapoznawczej

Aby zatrzymać lokalne wywołanie podglądu, należy usunąć widok pochodzący z elementu VideoStreamRenderer. Po usunięciu elementu VideoStreamRenderer usuń widok z drzewa HTML, wywołując removeChild() metodę z węzła DOM zawierającego podgląd.

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

Żądanie uprawnień do aparatu i mikrofonu

Aplikacja nie może używać aparatu ani mikrofonu bez uprawnień. Możesz użyć menedżera urządzeń, aby monitować użytkownika o udzielenie uprawnień aparatu i/lub mikrofonu:

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

Po rozwiązaniu obietnicy metoda zwraca obiekt, DeviceAccess który wskazuje, czy audio udzielono uprawnień i video uprawnień:

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

Uwagi

  • videoDevicesUpdated zdarzenie jest uruchamiane, gdy urządzenia wideo są podłączone/odłączone.
  • audioDevicesUpdated zdarzenie jest uruchamiane, gdy urządzenia audio są podłączone.
  • Po utworzeniu menedżera urządzeń na początku nie wiadomo o żadnych urządzeniach, jeśli nie udzielono jeszcze uprawnień, więc początkowo jego nazwa urządzenia jest pusta i nie zawiera szczegółowych informacji o urządzeniu. Jeśli następnie wywołamy interfejs API DeviceManager.askPermission(), użytkownik zostanie poproszony o dostęp do urządzenia. Gdy użytkownik wybierze opcję "zezwalaj", aby udzielić dostępu menedżerowi urządzeń dowiesz się o urządzeniach w systemie, zaktualizuj je na listach urządzeń i emituje zdarzenia "audioDevicesUpdated" i "videoDevicesUpdated". Jeśli użytkownik odświeży stronę i utworzy menedżera urządzeń, menedżer urządzeń będzie mógł dowiedzieć się więcej o urządzeniach, ponieważ użytkownik udzielił wcześniej dostępu. Ma wypełnione początkowo listy urządzeń i nie emituje zdarzeń "audioDevicesUpdated" ani "videoDevicesUpdated".
  • Wyliczenie/wybór osoby mówiącej nie jest obsługiwane w przeglądarce Android Chrome, iOS Safari ani macOS Safari.

Umieszczanie połączenia za pomocą kamery wideo

Ważne

Obecnie obsługiwany jest tylko jeden wychodzący lokalny strumień wideo.

Aby umieścić wywołanie wideo, należy wyliczyć lokalne kamery przy użyciu getCameras() metody w deviceManagerpliku .

Po wybraniu aparatu użyj go do utworzenia LocalVideoStream wystąpienia. Przekaż go jako videoOptions element w tablicy localVideoStream do CallAgent startCall metody .

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);
  • Możesz również dołączyć połączenie za pomocą wideo za pomocą CallAgent.join() interfejsu API i zaakceptować i wywołać za pomocą wideo za pomocą Call.Accept() interfejsu API.
  • Po nawiązaniu połączenia następuje automatyczne wysłanie strumienia wideo z wybranego aparatu do innego uczestnika.

Uruchamianie i zatrzymywanie wysyłania lokalnego wideo podczas rozmowy

Rozpocznij wideo

Aby rozpocząć wideo podczas wywołania, należy wyliczyć kamery przy użyciu getCameras metody na deviceManager obiekcie . Następnie utwórz nowe wystąpienie LocalVideoStream obiektu z żądanym aparatem, a następnie przekaż LocalVideoStream obiekt do startVideo metody istniejącego obiektu wywołania:

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

Zatrzymaj wideo

Po pomyślnym rozpoczęciu wysyłania wideo LocalVideoStream wystąpienie typu Video zostanie dodane do localVideoStreams kolekcji w wystąpieniu wywołania.

Znajdowanie strumienia wideo w obiekcie Call

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

Zatrzymaj lokalne wideo Aby zatrzymać lokalne wideo podczas wywołania, przekaż localVideoStream wystąpienie używane do wideo do metody stopVideo metody Call:

await call.stopVideo(localVideoStream);

Możesz przełączyć się do innego urządzenia z kamerą, używając aktywnego elementu LocalVideoStream, wywołując switchSource to LocalVideoStream wystąpienie:

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

Jeśli określone urządzenie wideo nie jest dostępne:

  • Podczas wywołania, jeśli wideo jest wyłączone i rozpoczynasz wideo przy użyciu call.startVideo()metody , ta metoda zgłasza, że diagnostyka skierowana przez SourceUnavailableError cameraStartFailed użytkownika jest ustawiona na wartość true.
  • Wywołanie localVideoStream.switchSource() metody powoduje cameraStartFailed ustawienie wartości true. Nasz przewodnik diagnostyki połączeń zawiera dodatkowe informacje na temat diagnozowania problemów związanych z wywołaniem.

Aby sprawdzić, czy lokalne wideo jest włączone lub wyłączone, możesz użyć Call metody isLocalVideoStarted, która zwraca wartość true lub false:

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

Aby nasłuchiwać zmian w lokalnym filmie wideo, możesz subskrybować i anulować subskrypcję zdarzenia isLocalVideoStartedChanged:

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

Uruchamianie i zatrzymywanie udostępniania ekranu podczas rozmowy

Aby uruchomić udostępnianie ekranu podczas wywołania, możesz użyć metody startScreenSharing() asynchronicznej w Call obiekcie:

Udostępnianie ekranu startowego

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

Uwaga: wysyłanie udziału ekranu jest obsługiwane tylko w przeglądarce klasycznej.

Znajdź udostępnianie ekranu w kolekcji LocalVideoStream

Po pomyślnym rozpoczęciu wysyłania udostępniania LocalVideoStream ekranu wystąpienie typu ScreenSharingjest dodawane do localVideoStreams kolekcji w wystąpieniu wywołania.

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

Zatrzymywanie udostępniania ekranu

Aby zatrzymać udostępnianie ekranu podczas wywołania, możesz użyć asynchronicznego interfejsu API stoptScreenSharing:

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

Sprawdzanie stanu udostępniania ekranu

Aby sprawdzić, czy udostępnianie ekranu jest włączone lub wyłączone, możesz użyć interfejsu API isScreenSharingOn, który zwraca wartość true lub false:

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

Aby nasłuchiwać zmian w udziale ekranu, możesz subskrybować i anulować subskrypcję zdarzenia isScreenSharingOnChanged:

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

Ważne

Ta funkcja usług Azure Communication Services jest obecnie dostępna w wersji zapoznawczej.

Interfejsy API i zestawy SDK w wersji zapoznawczej są udostępniane bez umowy dotyczącej poziomu usług. Zalecamy, aby nie używać ich w przypadku obciążeń produkcyjnych. Niektóre funkcje mogą nie być obsługiwane lub mogą mieć ograniczone możliwości.

Aby uzyskać więcej informacji, zapoznaj się z dodatkowymi warunkami użytkowania dla wersji zapoznawczych platformy Microsoft Azure.

Podgląd lokalnego udziału ekranu jest dostępny w publicznej wersji zapoznawczej i jest dostępny w wersji 1.15.1-beta.1 lub nowszej.

Podgląd udziału ekranu lokalnego

Możesz użyć elementu , VideoStreamRenderer aby rozpocząć renderowanie strumieni z udziału lokalnego ekranu, aby zobaczyć, co wysyłasz jako strumień udostępniania ekranu.

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

Renderowanie strumieni wideo/udostępniania ekranu uczestnika zdalnego

Aby renderować wideo lub udostępnianie ekranu uczestnika zdalnego, pierwszym krokiem jest uzyskanie odwołania do elementu RemoteVideoStream, który chcesz renderować. Można to zrobić, przechodząc przez tablicę lub strumień wideo (videoStreams) obiektu RemoteParticipant. Dostęp do kolekcji uczestników zdalnych jest uzyskiwany za pośrednictwem Call obiektu .

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

Aby renderować RemoteVideoStreamplik , musisz subskrybować jego isAvailableChanged zdarzenie. isAvailable Jeśli właściwość zmieni się na true, uczestnik zdalny wysyła strumień wideo. Następnie utwórz nowe wystąpienie VideoStreamRendererklasy , a następnie utwórz nowe VideoStreamRendererView wystąpienie przy użyciu metody asynchronicznej createView .
Następnie możesz dołączyć view.target do dowolnego elementu interfejsu użytkownika.

Za każdym razem, gdy dostępność strumienia zdalnego ulegnie zmianie, możesz zniszczyć całość VideoStreamRenderer lub określony VideoStreamRendererViewelement . Jeśli zdecydujesz się je zachować, w widoku zostanie wyświetlona pusta ramka wideo.

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

ARKUSZ CSS do stylów spinner ładowania na zdalnym strumieniu wideo.

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

Zdalna jakość wideo

Zestaw SDK webJS usług Azure Communication Services udostępnia funkcję o nazwie Optimal Video Count (OVC), począwszy od wersji 1.15.1. Ta funkcja może służyć do informowania aplikacji w czasie wykonywania o tym, ile przychodzących filmów wideo od różnych uczestników może być optymalnie renderowanych w danym momencie w wywołaniu grupy (2+ uczestnicy). Ta funkcja uwidacznia właściwość optimalVideoCount , która dynamicznie zmienia się podczas wywołania na podstawie możliwości sieci i sprzętu lokalnego punktu końcowego. Wartość optimalVideoCount szczegółów, ile filmów wideo z innej aplikacji uczestnika powinno być renderowanych w danym momencie. Aplikacje powinny obsługiwać te zmiany i aktualizować liczbę renderowanych filmów wideo odpowiednio do zalecenia. Między poszczególnymi aktualizacjami występuje okres odrzucenia (około 10 s).

Użycie Funkcja optimalVideoCount jest funkcją wywołania. Należy odwołać się do funkcji OptimalVideoCount za pomocą feature metody Call obiektu. Następnie można ustawić odbiornik za pomocą on metody OptimalVideoCountCallFeature , aby otrzymywać powiadomienia po zmianie parametru optimalVideoCount. Aby anulować subskrypcję zmian, możesz wywołać metodę off . Bieżąca maksymalna liczba przychodzących filmów wideo , które można renderować, wynosi 16. Aby prawidłowo obsługiwać 16 przychodzących filmów wideo, komputer powinien mieć mimimum 16 GB pamięci RAM i 4-rdzeniowy lub większy procesor, który nie jest starszy niż 3 lata.

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

Przykładowe użycie: aplikacja powinna subskrybować zmiany optymalnej liczby wideo w wywołaniach grupowych. Zmiana optymalnej liczby wideo może być obsługiwana przez utworzenie nowego modułu renderowania (createView metody) lub usuwanie widoków (dispose) i odpowiednio zaktualizowanie układu aplikacji.

Właściwości zdalnego strumienia wideo

Zdalne strumienie wideo mają następujące właściwości:

const id: number = remoteVideoStream.id;
  • id: identyfikator zdalnego strumienia wideo.
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
  • mediaStreamType: może mieć wartość Video lub ScreenSharing.
const isAvailable: boolean = remoteVideoStream.isAvailable;
  • isAvailable: określa, czy punkt końcowy uczestnika zdalnego aktywnie wysyła strumień.
const isReceiving: boolean = remoteVideoStream.isReceiving;
  • isReceiving:
    • Informuje aplikację o odebraniu lub braku zdalnych danych strumienia wideo.

    • Flaga zostanie przeniesiona do false w następujących scenariuszach:

      • Zdalny uczestnik, który jest w przeglądarce mobilnej, przenosi aplikację przeglądarki w tle.
      • Uczestnik zdalny lub użytkownik odbierający wideo ma problem z siecią, który znacząco wpływa na jakość wideo.
      • Zdalny uczestnik, który jest w systemie macOS/iOS Safari, wybiera pozycję "Wstrzymaj" na pasku adresu.
      • Uczestnik zdalny ma rozłączenie sieci.
      • Zdalny uczestnik na urządzeniach przenośnych zabija lub kończy przeglądarkę.
      • Uczestnik zdalny na urządzeniach przenośnych lub stacjonarnych blokuje swoje urządzenie. Ten scenariusz ma zastosowanie również wtedy, gdy uczestnik zdalny znajduje się na komputerze stacjonarnym i przejdzie w tryb uśpienia.
    • Flaga zostanie przeniesiona do true w następujących scenariuszach:

      • Zdalny uczestnik, który jest w przeglądarce mobilnej i ma w tle przeglądarkę, przywraca go na pierwszym planie.
      • Zdalny uczestnik, który jest w systemie macOS/iOS Safari wybiera pozycję "Wznów" na pasku adresu po wstrzymaniu filmu wideo.
      • Uczestnik zdalny ponownie łączy się z siecią po tymczasowym rozłączeniu.
      • Uczestnik zdalny na urządzeniu przenośnym odblokuj swoje urządzenie i wróć do wywołania w przeglądarce mobilnej.
    • Ta funkcja usprawnia środowisko użytkownika do renderowania zdalnych strumieni wideo.

    • Podczas odbierania flagi na fałsz można wyświetlić pokrętło ładowania na zdalnym strumieniu wideo. Nie musisz implementować spinneru ładowania, ale spinner ładowania jest najczęstszym użyciem w celu uzyskania lepszego środowiska użytkownika.

const size: StreamSize = remoteVideoStream.size;
  • size: rozmiar strumienia z informacjami o szerokości i wysokości filmu wideo.

Metody i właściwości usługi VideoStreamRenderer

await videoStreamRenderer.createView();

VideoStreamRendererView Utwórz wystąpienie, które można dołączyć w interfejsie użytkownika aplikacji, aby renderować zdalny strumień wideo, użyć metody asynchronicznejcreateView(), rozpoznaje, gdy strumień jest gotowy do renderowania i zwraca obiekt z właściwością reprezentującą target video element, który można wstawić w dowolnym miejscu w drzewie DOM.

videoStreamRenderer.dispose();

videoStreamRenderer Usuwanie i wszystkie skojarzone VideoStreamRendererView wystąpienia.

Właściwości i metody VideoStreamRendererView

Podczas tworzenia obiektu VideoStreamRendererViewmożna określić scalingMode właściwości i isMirrored . scalingMode może to być Stretch, Croplub Fit. Jeśli isMirrored zostanie określony, renderowany strumień jest przerzucany w pionie.

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

Każde VideoStreamRendererView wystąpienie ma właściwość reprezentującą target powierzchnię renderowania. Dołącz tę właściwość w interfejsie użytkownika aplikacji:

htmlElement.appendChild(view.target);

Możesz zaktualizować scalingMode , wywołując metodę updateScalingMode :

view.updateScalingMode('Crop');

Wysyłaj strumienie wideo z dwóch różnych kamer w tym samym wywołaniu z tego samego urządzenia stacjonarnego.

Ważne

Ta funkcja usług Azure Communication Services jest obecnie dostępna w wersji zapoznawczej.

Interfejsy API i zestawy SDK w wersji zapoznawczej są udostępniane bez umowy dotyczącej poziomu usług. Zalecamy, aby nie używać ich w przypadku obciążeń produkcyjnych. Niektóre funkcje mogą nie być obsługiwane lub mogą mieć ograniczone możliwości.

Aby uzyskać więcej informacji, zapoznaj się z dodatkowymi warunkami użytkowania dla wersji zapoznawczych platformy Microsoft Azure.

Wysyłanie strumieni wideo z dwóch różnych aparatów w tym samym wywołaniu jest obsługiwane w ramach wersji 1.17.1-beta.1 lub nowszej w przeglądarkach obsługiwanych przez komputery.

  • Strumienie wideo można wysyłać z dwóch różnych kamer z jednej karty/aplikacji przeglądarki klasycznej, w tym samym wywołaniu, przy użyciu następującego fragmentu kodu:
// 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();

Ograniczenia:

  • Należy to zrobić z dwoma różnymi CallAgent wystąpieniami przy użyciu różnych tożsamości. Fragment kodu przedstawia dwóch używanych agentów wywołań, z których każdy ma własny obiekt Call.
  • W przykładzie kodu oba elementy CallAgent dołączają do tego samego wywołania (te same identyfikatory wywołań). Możesz również dołączyć różne połączenia z każdym agentem i wysłać jeden film wideo w jednym wywołaniu i inny film wideo w drugim połączeniu.
  • Wysyłanie tego samego aparatu w obu wywołaniach nie jest obsługiwane. Muszą być dwoma różnymi kamerami.
  • Wysyłanie dwóch różnych aparatów z jedną funkcją CallAgent nie jest obecnie obsługiwane.
  • W przeglądarce Safari w systemie macOS efekty wideo rozmycia tła (z @azure/communication-effects)programu można zastosować tylko do jednego aparatu, a nie obu w tym samym czasie.

Instalacja zestawu SDK

Znajdź plik na poziomie build.gradle projektu i dodaj mavenCentral() go do listy repozytoriów w obszarze buildscript i allprojects:

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

Następnie w pliku na poziomie build.gradle modułu dependencies dodaj następujące wiersze do sekcji:

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

Inicjowanie wymaganych obiektów

Aby utworzyć CallAgent wystąpienie, należy wywołać createCallAgent metodę w wystąpieniu CallClient . To wywołanie asynchroniczne zwraca CallAgent obiekt wystąpienia.

Metoda createCallAgent przyjmuje CommunicationUserCredential jako argument, który hermetyzuje token dostępu.

Aby uzyskać dostęp, DeviceManagernależy najpierw utworzyć callAgent wystąpienie. Następnie możesz użyć metody , aby pobrać metodę CallClient.getDeviceManager DeviceManager.

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

Aby ustawić nazwę wyświetlaną elementu wywołującego, użyj tej alternatywnej metody:

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

Zarządzanie urządzeniami

Aby rozpocząć korzystanie z wideo z funkcją Wywoływanie, musisz wiedzieć, jak zarządzać urządzeniami. Urządzenia umożliwiają sterowanie tym, co przesyła dźwięk i wideo do wywołania.

DeviceManager Umożliwia wyliczanie urządzeń lokalnych, które mogą być używane w wywołaniu do przesyłania strumieni audio/wideo. Umożliwia również zażądanie uprawnień od użytkownika w celu uzyskania dostępu do mikrofonu i aparatu przy użyciu natywnego interfejsu API przeglądarki.

Dostęp można uzyskać deviceManager , wywołując callClient.getDeviceManager() metodę .

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

Wyliczanie urządzeń lokalnych

Aby uzyskać dostęp do urządzeń lokalnych, możesz użyć metod wyliczania w Menedżer urządzeń. Wyliczenie jest akcją synchroniczną.

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

Podgląd aparatu lokalnego

Możesz użyć funkcji DeviceManager i Renderer , aby rozpocząć renderowanie strumieni z aparatu lokalnego. Ten strumień nie zostanie wysłany do innych uczestników; jest to lokalny kanał informacyjny w wersji zapoznawczej. Jest to akcja asynchroniczna.

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

Umieść połączenie 1:1 za pomocą kamery wideo

Ostrzeżenie

Obecnie obsługiwany jest tylko jeden wychodzący lokalny strumień wideo Aby umieścić połączenie z wideo, musisz wyliczyć lokalne kamery przy użyciu interfejsu deviceManager getCameras API. Po wybraniu żądanego aparatu użyj go, aby skonstruować LocalVideoStream wystąpienie i przekazać je jako videoOptions element w localVideoStream tablicy do call metody. Po nawiązaniu połączenia automatycznie rozpocznie wysyłanie strumienia wideo z wybranego aparatu do innych uczestników.

Uwaga

Ze względu na obawy dotyczące prywatności klip wideo nie zostanie udostępniony wywołaniu, jeśli nie jest on w wersji zapoznawczej lokalnie. Aby uzyskać więcej szczegółów, zobacz Podgląd aparatu lokalnego.

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

Uruchamianie i zatrzymywanie wysyłania lokalnego wideo

Aby rozpocząć film wideo, musisz wyliczyć kamery przy użyciu interfejsu getCameraList API w deviceManager obiekcie. Następnie utwórz nowe wystąpienie przekazywania żądanego LocalVideoStream aparatu i przekaż je do interfejsu startVideo API jako 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();

Po pomyślnym rozpoczęciu wysyłania wideo LocalVideoStream wystąpienie zostanie dodane do localVideoStreams kolekcji w wystąpieniu wywołania.

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

Aby zatrzymać lokalne wideo, przekaż LocalVideoStream wystąpienie dostępne w localVideoStreams kolekcji:

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

Możesz przełączyć się na inne urządzenie aparatu, gdy wideo jest wysyłane przez wywołanie switchSource wystąpienia LocalVideoStream :

currentLocalVideoStream.switchSource(source).get();

Renderowanie strumieni wideo uczestnika zdalnego

Aby wyświetlić listę strumieni wideo i strumieni udostępniania ekranu uczestników zdalnych, sprawdź videoStreams kolekcje:

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

Aby renderować element RemoteVideoStream od uczestnika zdalnego, musisz zasubskrybować OnVideoStreamsUpdated wydarzenie.

W ramach zdarzenia zmiana isAvailable właściwości na true wskazuje, że uczestnik zdalny wysyła obecnie strumień. Gdy tak się stanie, utwórz nowe wystąpienie Rendererklasy , a następnie utwórz nowe RendererView przy użyciu asynchronicznego createView interfejsu API i dołącz go view.target w dowolnym miejscu w interfejsie użytkownika aplikacji.

Za każdym razem, gdy dostępność strumienia zdalnego zmieni się, możesz zniszczyć cały moduł renderujący, określone RendererView lub zachować je, ale spowoduje to wyświetlenie pustej ramki wideo.

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

Właściwości zdalnego strumienia wideo

Zdalny strumień wideo ma kilka właściwości

  • Id - Identyfikator zdalnego strumienia wideo
int id = remoteVideoStream.getId();
  • MediaStreamType - Może to być "Wideo" lub "ScreenSharing"
MediaStreamType type = remoteVideoStream.getMediaStreamType();
  • isAvailable — Wskazuje, czy punkt końcowy uczestnika zdalnego aktywnie wysyła strumień
boolean availability = remoteVideoStream.isAvailable();

Metody i właściwości modułu renderowania

Obiekt renderatora po interfejsach API

  • VideoStreamRendererView Utwórz wystąpienie, które można później dołączyć w interfejsie użytkownika aplikacji w celu renderowania zdalnego strumienia wideo.
// Create a view for a video stream
VideoStreamRendererView.createView()
  • Usuwanie modułu renderowania i wszystkich VideoStreamRendererView skojarzonych z tym modułem renderowania. Wywołanie wywołania po usunięciu wszystkich skojarzonych widoków z interfejsu użytkownika.
VideoStreamRenderer.dispose()
  • StreamSize - rozmiar (szerokość/wysokość) zdalnego strumienia wideo
StreamSize renderStreamSize = VideoStreamRenderer.getSize();
int width = renderStreamSize.getWidth();
int height = renderStreamSize.getHeight();

Metody i właściwości elementu RendererView

Podczas tworzenia VideoStreamRendererView obiektu można określić ScalingMode właściwości i mirrored , które będą stosowane do tego widoku: tryb skalowania może mieć wartość "CROP" | "FIT"

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

Utworzony element RendererView można następnie dołączyć do interfejsu użytkownika aplikacji przy użyciu następującego fragmentu kodu:

layout.addView(rendererView);

Później możesz zaktualizować tryb skalowania, wywołując updateScalingMode interfejs API w obiekcie RendererView za pomocą jednego z elementów ScalingMode.CROP | ScalingMode.FIT jako argument.

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

Konfigurowanie systemu

Wykonaj następujące kroki, aby skonfigurować system.

Tworzenie projektu Xcode

W programie Xcode utwórz nowy projekt systemu iOS i wybierz szablon Aplikacja z jednym widokiem. W tym artykule jest używana struktura SwiftUI, dlatego należy ustawić wartość Language na Swift i ustawić wartość Interface na SwiftUI.

Nie zamierzasz tworzyć testów w tym artykule. Możesz wyczyścić pole wyboru Uwzględnij testy .

Zrzut ekranu przedstawiający okno tworzenia projektu w programie Xcode.

Instalowanie pakietu i zależności przy użyciu narzędzia CocoaPods

  1. Utwórz plik Podfile dla aplikacji, podobnie jak w tym przykładzie:

    platform :ios, '13.0'
    use_frameworks!
    target 'AzureCommunicationCallingSample' do
        pod 'AzureCommunicationCalling', '~> 1.0.0'
    end
    
  2. Uruchom program pod install.

  3. Otwórz .xcworkspace za pomocą programu Xcode.

Żądanie dostępu do mikrofonu

Aby uzyskać dostęp do mikrofonu urządzenia, należy zaktualizować listę właściwości informacji aplikacji przy użyciu polecenia NSMicrophoneUsageDescription. Ustaw skojarzona wartość na ciąg zawarty w oknie dialogowym używanym przez system do żądania dostępu od użytkownika.

Kliknij prawym przyciskiem myszy wpis Info.plist drzewa projektu, a następnie wybierz pozycję Otwórz jako>kod źródłowy. Dodaj następujące wiersze w sekcji najwyższego poziomu <dict> , a następnie zapisz plik.

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

Konfigurowanie struktury aplikacji

Otwórz plik projektu ContentView.swift . Dodaj deklarację import na początku pliku, aby zaimportować bibliotekę AzureCommunicationCalling . Ponadto zaimportuj plik AVFoundation. Potrzebne są żądania uprawnień dźwięku w kodzie.

import AzureCommunicationCalling
import AVFoundation

Inicjowanie klasy CallAgent

Aby utworzyć CallAgent wystąpienie z CallClientklasy , należy użyć callClient.createCallAgent metody, która asynchronicznie zwraca CallAgent obiekt po zainicjowaniu.

Aby utworzyć klienta wywołania, przekaż CommunicationTokenCredential obiekt:

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

CommunicationTokenCredential Przekaż utworzony obiekt do CallClient, a następnie ustaw nazwę wyświetlaną:

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

Zarządzanie urządzeniami

Aby rozpocząć korzystanie z wideo z funkcją Wywoływanie, musisz wiedzieć, jak zarządzać urządzeniami. Urządzenia umożliwiają sterowanie tym, co przesyła dźwięk i wideo do wywołania.

DeviceManager Umożliwia wyliczanie urządzeń lokalnych, które mogą być używane w wywołaniu do przesyłania strumieni audio lub wideo. Umożliwia również zażądanie od użytkownika uprawnień dostępu do mikrofonu lub kamery. Dostęp do obiektu można uzyskać deviceManager callClient .

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

Wyliczanie urządzeń lokalnych

Aby uzyskać dostęp do urządzeń lokalnych, możesz użyć metod wyliczania w Menedżerze urządzeń. Wyliczenie jest akcją synchroniczną.

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

Uzyskiwanie podglądu aparatu lokalnego

Możesz użyć Renderer polecenia , aby rozpocząć renderowanie strumienia z aparatu lokalnego. Ten strumień nie zostanie wysłany do innych uczestników; jest to lokalny kanał informacyjny w wersji zapoznawczej. Jest to akcja asynchroniczna.

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

Pobieranie właściwości podglądu aparatu lokalnego

Program renderujący ma zestaw właściwości i metod, które umożliwiają sterowanie renderowaniem.

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

Umieść połączenie 1:1 za pomocą wideo

Aby uzyskać wystąpienie menedżera urządzeń, zobacz sekcję dotyczącą zarządzania urządzeniami.

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

Renderowanie strumieni wideo uczestnika zdalnego

Uczestnicy zdalni mogą inicjować udostępnianie wideo lub ekranu podczas rozmowy.

Obsługa udostępniania wideo lub udostępniania ekranu strumieni zdalnych uczestników

Aby wyświetlić listę strumieni uczestników zdalnych, sprawdź videoStreams kolekcje.

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

Pobieranie właściwości zdalnego strumienia wideo

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

Renderowanie strumieni uczestników zdalnych

Aby rozpocząć renderowanie strumieni uczestników zdalnych, użyj następującego kodu.

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)

Uzyskiwanie zdalnych metod i właściwości modułu renderowania wideo

// [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()

Konfigurowanie systemu

Wykonaj następujące kroki, aby skonfigurować system.

Tworzenie projektu programu Visual Studio

W przypadku aplikacji platforma uniwersalna systemu Windows w programie Visual Studio 2022 utwórz nowy projekt Pusta aplikacja (uniwersalny system Windows). Po wprowadzeniu nazwy projektu możesz wybrać dowolny zestaw Windows SDK nowszy niż 10.0.17763.0.

W przypadku aplikacji WinUI 3 utwórz nowy projekt z pustą aplikacją, spakowanym szablonem (WinUI 3 w programie Desktop), aby skonfigurować jednostronicową aplikację WinUI 3. Wymagany jest zestaw Windows App SDK w wersji 1.3 lub nowszej.

Instalowanie pakietu i zależności przy użyciu Menedżer pakietów NuGet

Interfejsy API i biblioteki zestawu SDK wywołujących są publicznie dostępne za pośrednictwem pakietu NuGet.

Aby znaleźć, pobrać i zainstalować pakiet NuGet zestawu SDK wywołującego:

  1. Otwórz Menedżer pakietów NuGet, wybierając pozycję Narzędzia>NuGet Menedżer pakietów> Zarządzanie pakietami NuGet dla rozwiązania.
  2. Wybierz pozycję Przeglądaj, a następnie wprowadź ciąg Azure.Communication.Calling.WindowsClient w polu wyszukiwania.
  3. Upewnij się, że pole wyboru Uwzględnij wersję wstępną zostało zaznaczone.
  4. Wybierz pakiet Azure.Communication.Calling.WindowsClient, a następnie wybierz pozycję Azure.Communication.Calling.WindowsClient 1.4.0-beta.1 lub nowszą wersję.
  5. Zaznacz pole wyboru odpowiadające projektowi usług Azure Communication Services w okienku po prawej stronie.
  6. Wybierz Zainstaluj.

Żądanie dostępu do mikrofonu

Aplikacja wymaga dostępu do aparatu, aby działał prawidłowo. W aplikacjach platformy UWP funkcja aparatu powinna być zadeklarowana w pliku manifestu aplikacji.

W poniższych krokach przedstawiono sposób, w jaki to osiągnąć.

  1. Solution Explorer Na panelu kliknij dwukrotnie plik z .appxmanifest rozszerzeniem.
  2. Kliknij kartę Capabilities .
  3. Camera Zaznacz pole wyboru z listy możliwości.

Tworzenie przycisków interfejsu użytkownika w celu umieszczania i zawieszania się wywołania

Ta prosta przykładowa aplikacja zawiera dwa przyciski. Jeden do umieszczenia połączenia, a drugi, aby zawiesić umieszczone połączenie. W poniższych krokach przedstawiono sposób dodawania tych przycisków do aplikacji.

  1. Solution Explorer Na panelu kliknij dwukrotnie plik o nazwie MainPage.xaml dla platformy UWP lub MainWindows.xaml winUI 3.
  2. Na panelu centralnym wyszukaj kod XAML w obszarze podglądu interfejsu użytkownika.
  3. Zmodyfikuj kod XAML, korzystając z następującego fragmentu:
<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>

Konfigurowanie aplikacji przy użyciu wywoływanych interfejsów API zestawu SDK

Interfejsy API wywołującego zestawu SDK znajdują się w dwóch różnych przestrzeniach nazw. Poniższe kroki informują kompilator języka C# o tych przestrzeniach nazw, dzięki czemu funkcja IntelliSense programu Visual Studio może pomóc w tworzeniu kodu.

  1. Solution Explorer W panelu kliknij strzałkę po lewej stronie pliku o nazwie MainPage.xaml dla platformy UWP lub MainWindows.xaml winUI 3.
  2. Kliknij dwukrotnie plik o nazwie MainPage.xaml.cs lub MainWindows.xaml.cs.
  3. Dodaj następujące polecenia w dolnej części bieżących using instrukcji.
using Azure.Communication.Calling.WindowsClient;

Zachowaj MainPage.xaml.cs lub MainWindows.xaml.cs otwórz. Następne kroki spowodują dodanie do niego większej liczby kodu.

Zezwalaj na interakcje aplikacji

Przyciski interfejsu użytkownika dodane wcześniej muszą działać na górze umieszczonego CommunicationCallelementu . Oznacza to, że element członkowski CommunicationCall danych powinien zostać dodany do MainPage klasy lub MainWindow . Ponadto aby umożliwić tworzenie operacji asynchronicznej w celu pomyślnego wykonania CallAgent , CallAgent należy również dodać element członkowski danych do tej samej klasy.

Dodaj następujące składowe danych do MainPage klasy or MainWindow :

CallAgent callAgent;
CommunicationCall call;

Tworzenie procedur obsługi przycisków

Wcześniej do kodu XAML dodano dwa przyciski interfejsu użytkownika. Poniższy kod dodaje programy obsługi do wykonania po wybraniu przycisku przez użytkownika. Poniższy kod powinien zostać dodany po elementach członkowskich danych z poprzedniej sekcji.

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

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

Model obiektów

Następujące klasy i interfejsy obsługują niektóre główne funkcje biblioteki klienta wywołujących usługi Azure Communication Services dla platformy UWP.

Nazwa/nazwisko opis
CallClient Jest CallClient to główny punkt wejścia do biblioteki wywołującej klienta.
CallAgent Element służy do uruchamiania CallAgent i dołączania wywołań.
CommunicationCall Służy CommunicationCall do zarządzania umieszczonymi lub sprzężonym wywołaniami.
CommunicationTokenCredential Element CommunicationTokenCredential jest używany jako poświadczenie tokenu w celu utworzenia wystąpienia elementu CallAgent.
CallAgentOptions Zawiera CallAgentOptions informacje identyfikujące obiekt wywołujący.
HangupOptions Informuje, HangupOptions czy połączenie powinno zostać zakończone dla wszystkich jego uczestników.

Rejestrowanie programu obsługi schematu wideo

Składnik interfejsu użytkownika, taki jak MediaElement języka XAML lub MediaPlayerElement, potrzebuje aplikacji rejestrującej konfigurację renderowania lokalnych i zdalnych kanałów informacyjnych wideo. Dodaj następującą zawartość między tagami Package elementu Package.appxmanifest:

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

Inicjowanie klasy CallAgent

Aby utworzyć CallAgent wystąpienie z CallClientklasy , należy użyć CallClient.CreateCallAgentAsync metody, która asynchronicznie zwraca CallAgent obiekt po zainicjowaniu.

Aby utworzyć CallAgentobiekt , należy przekazać CallTokenCredential obiekt i CallAgentOptions obiekt. Należy pamiętać, że CallTokenCredential zgłasza błąd w przypadku przekazania źle sformułowanego tokenu.

Poniższy kod powinien zostać dodany wewnątrz funkcji pomocnika i wywoływany w inicjalizacji aplikacji.

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;

Zmień element <AUTHENTICATION_TOKEN> przy użyciu prawidłowego tokenu poświadczeń dla zasobu. Jeśli token poświadczeń musi być źródłowy, zapoznaj się z dokumentacją tokenu dostępu użytkownika.

Umieść połączenie 1:1 za pomocą kamery wideo

Obiekty potrzebne do utworzenia obiektu CallAgent są teraz gotowe. Nadszedł czas, aby asynchronicznie utworzyć i umieścić CallAgent połączenie wideo.

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

Podgląd aparatu lokalnego

Opcjonalnie można skonfigurować podgląd aparatu lokalnego. Wideo można renderować za pomocą programu MediaPlayerElement:

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

Aby zainicjować lokalną wersję zapoznawcza MediaPlayerElement:

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

Renderowanie strumienia zdalnego aparatu

Skonfiguruj nawet program obsługi w odpowiedzi na OnCallsUpdated zdarzenie:

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

Uruchom renderowanie zdalnego strumienia wideo w usłudze 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;
    }
}

Kończ połączenie

Po umieszczeniu HangupAsync wywołania należy użyć metody CommunicationCall obiektu w celu zawieszenia wywołania.

Należy również użyć wystąpienia HangupOptions , aby poinformować, czy połączenie musi zostać zakończone dla wszystkich jego uczestników.

W pliku HangupButton_Clicknależy dodać następujący kod.

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

Uruchamianie kodu

Upewnij się, że program Visual Studio skompiluje aplikację dla x64x86 , lub ARM64, a następnie naciśnij polecenie F5 , aby rozpocząć uruchamianie aplikacji. Następnie kliknij CommunicationCall przycisk, aby umieścić wywołanie do zdefiniowanej metody wywoływanej.

Pamiętaj, że przy pierwszym uruchomieniu aplikacji system monituje użytkownika o udzielenie dostępu do mikrofonu.

Następne kroki