Управление видео во время звонков
Узнайте, как управлять видеозвонками с помощью пакетов SDK Службы коммуникации Azure. Мы узнаем, как управлять получением и отправкой видео в рамках звонка.
Необходимые компоненты
- Учетная запись Azure с активной подпиской. Создайте учетную запись бесплатно .
- Развернутый ресурс Служб коммуникации. Создайте ресурс Служб коммуникации.
- Маркер доступа пользователя для включения клиента для вызовов. Дополнительные сведения см. в статье о создании маркеров доступа и управлении ими.
- Необязательно. Выполните краткое руководство по добавлению голосовых вызовов в приложение
Установка пакета SDK
npm install
Используйте команду для установки пакета SDK Службы коммуникации Azure Common and Calling SDK для JavaScript:
npm install @azure/communication-common --save
npm install @azure/communication-calling --save
Инициализация обязательных объектов
Экземпляр CallClient
требуется для большинства операций вызова. При создании нового CallClient
экземпляра его можно настроить с помощью пользовательских параметров, таких как Logger
экземпляр.
С помощью экземпляра CallClient
можно создать CallAgent
экземпляр, вызвав его createCallAgent
. Этот метод асинхронно возвращает объект экземпляра CallAgent
.
Метод createCallAgent
использует CommunicationTokenCredential
в качестве аргумента. Он принимает маркер доступа пользователя.
Можно применить метод getDeviceManager
для экземпляра CallClient
, чтобы получить доступ к deviceManager
.
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()
Управление подключением пакета SDK к инфраструктуре Майкрософт
Экземпляр Call Agent
помогает управлять вызовами (для присоединения или запуска вызовов). Для работы пакета SDK для вызова необходимо подключиться к инфраструктуре Майкрософт для получения уведомлений о входящих звонках и координации других сведений о вызове. У вас Call Agent
есть два возможных состояния:
Подключено — Call Agent
значение Connected
connectionStatue означает, что клиентский пакет SDK подключен и способен получать уведомления из инфраструктуры Майкрософт.
Отключено — Call Agent
значение connectionStatue состояний Disconnected
возникает проблема, которая препятствует правильному подключению пакета SDK. Call Agent
необходимо повторно создать.
invalidToken
: если срок действия маркера истек или является недопустимымCall Agent
экземпляром, отключается с этой ошибкой.connectionIssue
: если возникла проблема с клиентом, подключающимся к инфраструктуре Майкрософт, после многих повторных попытокCall Agent
возникаетconnectionIssue
ошибка.
Вы можете проверить, подключен ли локальный сервер Call Agent
к инфраструктуре Майкрософт, проверив текущее значение connectionState
свойства. Во время активного вызова можно прослушивать connectionStateChanged
событие, чтобы определить, изменяются ли Call Agent
изменения из подключенного к отключенном состоянию.
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);
Управление устройствами
Чтобы начать использовать видео с пакетом SDK для вызовов, необходимо иметь возможность управлять устройствами. Устройства позволяют управлять тем, что передает звук и видео в звонок.
deviceManager
С помощью функции можно перечислить локальные устройства, которые могут передавать аудио- и видеопотоки в вызове. Вы также можете запросить deviceManager
разрешение на доступ к микрофонам и камерам локального устройства.
Доступ к deviceManager
осуществляется путем вызова метода callClient.getDeviceManager()
:
const deviceManager = await callClient.getDeviceManager();
Получение локальных устройств
Для доступа к локальным устройствам можно использовать deviceManager
методы getCameras()
перечисления и getMicrophones
. Эти методы являются асинхронными действиями.
// 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...]
Настройка устройств по умолчанию
После того как вы узнаете, какие устройства доступны для использования, можно настроить устройства по умолчанию для микрофона, динамика и камеры. Если значения по умолчанию для клиента не заданы, пакет SDK служб коммуникации использует значения по умолчанию операционной системы.
Микрофон
Доступ к используемому устройству
// Get the microphone device that is being used.
const defaultMicrophone = deviceManager.selectedMicrophone;
Настройка используемого устройства
// Set the microphone device to use.
await deviceManager.selectMicrophone(localMicrophones[0]);
Докладчик
Доступ к используемому устройству
// Get the speaker device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;
Настройка используемого устройства
// Set the speaker device to use.
await deviceManager.selectSpeaker(localSpeakers[0]);
Камера
Доступ к используемому устройству
// Get the camera device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;
Настройка используемого устройства
// Set the speaker device to use.
await deviceManager.selectSpeaker(localCameras[0]);
Каждый CallAgent
может выбрать свой микрофон и динамики на его связанном DeviceManager
устройстве. Рекомендуется использовать разные CallAgents
микрофоны и динамики. Они не должны совместно использовать одни и те же микрофоны, ни динамики. Если общий доступ происходит, то может быть активирована диагностика взаимодействия с пользователем с микрофоном, а микрофон перестает работать в зависимости от браузера или ос.
Локальный видеопоток
Чтобы отправить видео в вызове, необходимо создать LocalVideoStream
объект.
const localVideoStream = new LocalVideoStream(camera);
Камера, передаваемая в качестве параметра, является одним из VideoDeviceInfo
объектов, возвращаемых методом deviceManager.getCameras()
.
A LocalVideoStream
имеет следующие свойства:
source
: сведения об устройстве.
const source = localVideoStream.source;
mediaStreamType
: может бытьVideo
,ScreenSharing
илиRawMedia
.
const type: MediaStreamType = localVideoStream.mediaStreamType;
Предварительный просмотр изображения с локальной камеры
С помощью deviceManager
и VideoStreamRenderer
вы можете реализовать отрисовку потоков из локальной камеры.
LocalVideoStream
После создания используйте его для настройкиVideoStreamRenderer
. VideoStreamRenderer
После создания метода вызовите его createView()
метод, чтобы получить представление, которое можно добавить в качестве дочернего элемента на страницу.
Этот поток не отправляется другим участникам; это локальный веб-канал предварительной версии.
// 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);
Остановка локальной предварительной версии
Чтобы остановить локальный вызов предварительной версии, удалите представление, полученное из .VideoStreamRenderer
После удаления VideoStreamRenderer удалите представление из html-дерева, вызвав removeChild()
метод из узла DOM, содержащего предварительную версию.
// To stop viewing local camera preview
view.dispose();
htmlElement.removeChild(view.target);
Запрос разрешений на доступ к камере и микрофону
Приложение не может использовать камеру или микрофон без разрешений. С помощью deviceManager можно предложить пользователю предоставить разрешения камеры и (или) микрофона:
const result = await deviceManager.askDevicePermission({audio: true, video: true});
После разрешения обещания метод возвращает объект, указывающий DeviceAccess
, были ли audio
предоставлены разрешения и video
разрешения:
console.log(result.audio);
console.log(result.video);
Примечания.
videoDevicesUpdated
событие возникает при подключении и отмене подключения видеоустройств.audioDevicesUpdated
событие возникает при подключении звуковых устройств.- При создании DeviceManager сначала он не знает о каких-либо устройствах, если разрешения еще не предоставлены, поэтому изначально его имя устройства пусто, и оно не содержит подробных сведений об устройстве. Если мы вызовем API DeviceManager.askPermission(), пользователю будет предложено получить доступ к устройству. Когда пользователь выбирает "разрешить", чтобы предоставить доступ к диспетчеру устройств, узнает об устройствах в системе, обновите его списки устройств и выпустите события audioDevicesUpdated и videoDevicesUpdated. Если пользователь обновляет страницу и создает диспетчер устройств, диспетчер устройств может узнать об устройствах, так как пользователь предоставил ранее доступ. Он имеет свои списки устройств, заполненные изначально, и он не выдает события audioDevicesUpdated или videoDevicesUpdated.
- Перечисление или выбор динамиков не поддерживается в Android Chrome, iOS Safari и macOS Safari.
Размещение звонка с видеокамерой
Внимание
В настоящее время поддерживается только один исходящий локальный видеопоток.
Чтобы осуществить видеозвонок, необходимо перечислить локальные камеры с помощью метода getCameras()
в deviceManager
.
После выбора камеры используйте ее для создания экземпляра LocalVideoStream
.
Передайте его в videoOptions
виде элемента в localVideoStream
массиве методу CallAgent
startCall
.
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);
- Вы также можете присоединиться к вызову с помощью
CallAgent.join()
API и принять и позвонить с помощьюCall.Accept()
API. - Когда вызов будет осуществлен, автоматически начнется отправка видеопотока с выбранной камеры другим участникам вызова.
Запуск и остановка отправки локального видео во время вызова
Начало видео
Чтобы запустить видео во время вызова, необходимо перечислить камеры с помощью getCameras
метода объекта deviceManager
.
Затем создайте новый экземпляр LocalVideoStream
с требуемой камерой, а затем передайте LocalVideoStream
объект в startVideo
метод существующего объекта вызова:
const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
await call.startVideo(localVideoStream);
Остановка видео
После успешной отправки видео LocalVideoStream
экземпляр типа Video
добавляется в коллекцию localVideoStreams
в экземпляре вызова.
Поиск видеопотока в объекте Call
const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'Video'} );
Остановите локальное видео , чтобы остановить локальное видео во время вызова, передайте localVideoStream
экземпляр, используемый для видео, в метод Call
stopVideo из :
await call.stopVideo(localVideoStream);
Вы можете переключиться на другое устройство камеры при наличии активного LocalVideoStream, вызвав switchSource
на этом LocalVideoStream
экземпляре:
const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);
Если указанное видеоустройство недоступно:
- При вызове, если видео отключено, и вы начинаете видео с помощью
call.startVideo()
, этот метод выдаетSourceUnavailableError
диагностику,cameraStartFailed
которая сталкивается с пользователем, имеет значение true. - Вызов
localVideoStream.switchSource()
метода вызываетcameraStartFailed
значение true. В нашем руководстве по диагностике звонков содержатся дополнительные сведения о том, как диагностировать связанные с вызовом проблемы.
Чтобы проверить, включена ли или отключена локальная видео, можно использовать Call
метод isLocalVideoStarted
, который возвращает значение true или false:
// Check if local video is on or off
call.isLocalVideoStarted;
Чтобы прослушивать изменения в локальном видео, вы можете подписаться и отменить подписку на событие isLocalVideoStartedChanged:
// Subscribe to local video event
call.on('isLocalVideoStartedChanged', () => {
// Callback();
});
// Unsubscribe from local video event
call.off('isLocalVideoStartedChanged', () => {
// Callback();
});
Запуск и остановка общего доступа к экрану во время звонка
Чтобы запустить общий доступ к экрану во время вызова, можно использовать асинхронный метод startScreenSharing()
в объекте Call
:
Начальный общий доступ к экрану
// Start screen sharing
await call.startScreenSharing();
Примечание. Отправка общего доступа к экрану поддерживается только в классическом браузере.
Поиск общего доступа к экрану в коллекции LocalVideoStream
После успешной ScreenSharing
отправки общего доступа LocalVideoStream
к экрану экземпляр типа добавляется в коллекцию localVideoStreams
экземпляра вызова.
const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing'} );
Остановка общего доступа к экрану
Чтобы остановить общий доступ к экранам во время вызова, можно использовать асинхронный api stoptScreenSharing:
// Stop screen sharing
await call.stopScreenSharing();
Проверка состояния общего доступа к экрану
Чтобы проверить, включен ли общий доступ к экрану, можно использовать APIScreenSharingOn, который возвращает значение true или false:
// Check if screen sharing is on or off
call.isScreenSharingOn;
Чтобы слушать изменения в общей папке экрана, вы можете подписаться и отменить подписку на событие IsScreenSharingOnChanged:
// Subscribe to screen share event
call.on('isScreenSharingOnChanged', () => {
// Callback();
});
// Unsubscribe from screen share event
call.off('isScreenSharingOnChanged', () => {
// Callback();
});
Внимание
Эта функция Службы коммуникации Azure сейчас доступна в предварительной версии.
Предварительные версии API и пакеты SDK предоставляются без соглашения об уровне обслуживания. Рекомендуется не использовать их для рабочих нагрузок. Некоторые функции могут не поддерживаться или могут иметь ограниченные возможности.
Дополнительные сведения см . в дополнительных условиях использования для предварительных версий Microsoft Azure.
Предварительная версия локального общего экрана доступна в общедоступной предварительной версии и доступна в составе версии 1.15.1-beta.1+.
Предварительная версия общей папки локального экрана
Вы можете начать VideoStreamRenderer
отрисовку потоков из общей папки локального экрана, чтобы увидеть, что вы отправляете в виде потока общего доступа к экранам.
// 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);
}
});
Отрисовка потоков видео и экранов удаленного участника
Чтобы отобразить видео или общий доступ к экрану удаленного участника, сначала необходимо получить ссылку на RemoteVideoStream, которую вы хотите отобразить.
Это можно сделать, перейдя через массив или видеопоток (videoStreams
) объекта RemoteParticipant
. Доступ к коллекции удаленных участников осуществляется через Call
объект.
const remoteVideoStream = call.remoteParticipants[0].videoStreams[0];
const streamType = remoteVideoStream.mediaStreamType;
Для отрисовки RemoteVideoStream
необходимо подписаться на его isAvailableChanged
событие. isAvailable
Если свойство изменяетсяtrue
, удаленный участник отправляет видеопоток.
Когда это произойдет, создайте новый экземпляр VideoStreamRenderer
, а затем новый экземпляр VideoStreamRendererView
с помощью асинхронного метода createView
.
Затем вы сможете присоединить view.target
к любому элементу пользовательского интерфейса.
При каждом изменении доступности удаленного потока можно уничтожить все VideoStreamRenderer
или конкретное VideoStreamRendererView
.
Если вы решите сохранить их, то в представлении отображается пустой кадр видео.
// 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}`);
});
}
CSS для стилизации загрузочного спиннера по удаленному видеопотоку.
.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); }
}
Качество удаленного видео
Пакет SDK Службы коммуникации Azure WebJS предоставляет функцию с именем "Оптимальное число видео" (OVC), начиная с версии 1.15.1.
Эта функция может использоваться для информирования приложений во время выполнения о том, сколько входящих видео от разных участников может быть оптимально отрисовано в данный момент в групповом вызове (2+ участники).
Эта функция предоставляет свойство optimalVideoCount
, которое динамически изменяется во время вызова на основе сетевых и аппаратных возможностей локальной конечной точки. Значение сведений о optimalVideoCount
том, сколько видео из разных приложений-участников должно отображаться в данный момент. Приложения должны обрабатывать эти изменения и обновлять количество отрисованных видео соответствующим образом в соответствии с рекомендацией. Между каждым обновлением существует период разбиение (около 10 с).
optimalVideoCount
Использование функции — это функция вызова. Необходимо ссылаться на функцию OptimalVideoCount
с помощью feature
метода Call
объекта. Затем вы можете задать прослушиватель с помощью on
метода уведомления при изменении оптимальной OptimalVideoCountCallFeature
версииVideoCount. Чтобы отменить подписку из изменений, можно вызвать off
метод. Текущее максимальное количество входящих видео , которое можно отобразить, равно 16. Для правильной поддержки 16 входящих видео компьютер должен иметь mimum из 16 ГБ ОЗУ и 4-ядра или больше ЦП, который не старше 3 лет.
const optimalVideoCountFeature = call.feature(Features.OptimalVideoCount);
optimalVideoCountFeature.on('optimalVideoCountChanged', () => {
const localOptimalVideoCountVariable = optimalVideoCountFeature.optimalVideoCount;
})
Пример использования: приложение должно подписаться на изменения оптимального количества видео в групповых вызовах. Изменение оптимального количества видео можно обрабатывать путем создания нового отрисовщика (createView
метода) или удаления представлений (dispose
) и обновления макета приложения соответствующим образом.
Свойства удаленного видеопотока
Удаленные потоки видео имеют следующие свойства:
const id: number = remoteVideoStream.id;
id
— идентификатор удаленного видеопотока;
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
mediaStreamType
может иметь значениеVideo
илиScreenSharing
;
const isAvailable: boolean = remoteVideoStream.isAvailable;
isAvailable
: определяет, активно ли конечная точка удаленного участника отправляет поток.
const isReceiving: boolean = remoteVideoStream.isReceiving;
isReceiving
:Сообщает приложению, если данные удаленного видеопотока получены или нет.
Флаг переходит к
false
следующим сценариям:- Удаленный участник, работающий в мобильном браузере, переносит приложение браузера в фон.
- Удаленный участник или пользователь, получающий видео, имеет проблему с сетью, которая влияет на качество видео резко.
- Удаленный участник, работающий в macOS/iOS Safari, выбирает "Приостановить" в адресной строке.
- Удаленный участник имеет сетевое отключение.
- Удаленный участник на мобильном устройстве убивает или завершает браузер.
- Удаленный участник на мобильном устройстве или на рабочем столе блокирует свое устройство. Этот сценарий также применяется, если удаленный участник находится на настольном компьютере и переходит в спящий режим.
Флаг переходит к
true
следующим сценариям:- Удаленный участник, работающий в мобильном браузере и имеющий фон браузера, возвращает его на передний план.
- Удаленный участник, работающий в macOS/iOS Safari, выбирает "Возобновить" в адресной строке после приостановки видео.
- Удаленный участник повторно подключается к сети после временного отключения.
- Удаленный участник на мобильном устройстве разблокирует свое устройство и вернется к вызову в своем мобильном браузере.
Эта функция улучшает взаимодействие с пользователем для отрисовки удаленных видеопотоков.
Вы можете отобразить спиннер загрузки по удаленному видеопотоку при изменении флага false. Вам не нужно реализовать спиннер загрузки, но загрузка спиннер является наиболее распространенным использованием для лучшего взаимодействия с пользователем.
const size: StreamSize = remoteVideoStream.size;
size
: размер потока с информацией о ширине и высоте видео.
Методы и свойства VideoStreamRenderer
await videoStreamRenderer.createView();
VideoStreamRendererView
Создайте экземпляр, который можно подключить в пользовательском интерфейсе приложения для отрисовки удаленного видеопотока, используйте асинхронный createView()
метод, он разрешает, когда поток готов к отрисовке и возвращает объект со target
свойством, представляющим video
элемент, который может быть вставлен в любое место в дереве DOM.
videoStreamRenderer.dispose();
videoStreamRenderer
Удаление и все связанные VideoStreamRendererView
экземпляры.
Методы и свойства VideoStreamRendererView
При создании VideoStreamRendererView
можно указать свойства scalingMode
и isMirrored
. scalingMode
может иметь значение Stretch
, Crop
или Fit
. Если задано значение isMirrored
, преобразованный для просмотра поток зеркально отражается по вертикали.
const videoStreamRendererView: VideoStreamRendererView = await videoStreamRenderer.createView({ scalingMode, isMirrored });
Каждый экземпляр VideoStreamRendererView
имеет свойство target
, которое представляет область отрисовки. Подключите это свойство к пользовательскому интерфейсу приложения:
htmlElement.appendChild(view.target);
Чтобы обновить scalingMode
, вызовите метод updateScalingMode
:
view.updateScalingMode('Crop');
Отправка видеопотоков из двух разных камер в одном вызове с одного и того же классического устройства.
Внимание
Эта функция Службы коммуникации Azure сейчас доступна в предварительной версии.
Предварительные версии API и пакеты SDK предоставляются без соглашения об уровне обслуживания. Рекомендуется не использовать их для рабочих нагрузок. Некоторые функции могут не поддерживаться или могут иметь ограниченные возможности.
Дополнительные сведения см . в дополнительных условиях использования для предварительных версий Microsoft Azure.
Отправка видеопотоков из двух разных камер в одном вызове поддерживается в рамках версии 1.17.1-beta.1+ в поддерживаемых браузерах на настольных компьютерах.
- Видеопотоки можно отправлять из двух разных камер из одной вкладки браузера рабочего стола или приложения в одном вызове с помощью следующего фрагмента кода:
// 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();
Ограничения:
- Это необходимо сделать с двумя разными экземплярами с использованием разных
CallAgent
удостоверений. Фрагмент кода показывает, что используются два агента вызова, каждый из которых имеет собственный объект Call. - В примере кода оба callAgents присоединяются к одному вызову (одинаковые идентификаторы вызова). Вы также можете присоединиться к разным звонкам с каждым агентом и отправить одно видео на один звонок и другое видео на другом вызове.
- Отправка одной и той же камеры в CallAgent не поддерживается. Они должны быть двумя разными камерами.
- Отправка двух разных камер с одним CallAgent в настоящее время не поддерживается.
- В macOS Safari эффекты фонового размытия видео (из @azure/communication-effects)них можно применять только к одной камере, а не одновременно.
Установка пакета SDK
Найдите файл уровня build.gradle
проекта и добавьте mavenCentral()
в список репозиториев в buildscript
разделе и allprojects
:
buildscript {
repositories {
...
mavenCentral()
...
}
}
allprojects {
repositories {
...
mavenCentral()
...
}
}
Затем в файле уровня build.gradle
модуля добавьте в раздел следующие строки dependencies
:
dependencies {
...
implementation 'com.azure.android:azure-communication-calling:1.0.0'
...
}
Инициализация обязательных объектов
Чтобы создать CallAgent
экземпляр, необходимо вызвать createCallAgent
метод в экземпляре CallClient
. Этот вызов асинхронно возвращает объект экземпляра CallAgent
.
Метод createCallAgent
принимает CommunicationUserCredential
в качестве аргумента, который инкапсулирует маркер доступа.
Чтобы получить доступ DeviceManager
, сначала необходимо создать callAgent
экземпляр. Затем можно использовать 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();
Чтобы задать отображаемое имя для вызывающей стороны, используйте следующий альтернативный метод:
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();
Управление устройствами
Чтобы начать работу с видео с вызовами, необходимо знать, как управлять устройствами. Устройства позволяют управлять тем, что передает звук и видео в звонок.
DeviceManager
позволяет получить список локальных устройств, которые можно использовать в вызове для передачи аудио- и видеопотоков. Вы также можете запрашивать у пользователя разрешение на доступ к его микрофону и камере через собственный API браузера.
Доступ к deviceManager
осуществляется путем вызова метода callClient.getDeviceManager()
.
Context appContext = this.getApplicationContext();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
Перечисление локальных устройств
Для доступа к локальным устройствам можно использовать методы перечисления из Диспетчера устройств. Действие перечисления выполняется асинхронно.
// Get a list of available video devices for use.
List<VideoDeviceInfo> localCameras = deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]
Предварительный просмотр изображения с локальной камеры
С помощью DeviceManager
и Renderer
вы можете реализовать отрисовку потоков из локальной камеры. Этот поток не отправляется другим участникам, а используется как локальный канал предварительного просмотра. Это действие выполняется асинхронно.
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);
Осуществление персонального вызова с использованием видеокамеры
Предупреждение
В настоящее время поддерживается только один исходящий локальный видеопоток. Чтобы осуществить вызов с поддержкой видео, необходимо перечислить локальные камеры через API deviceManager
getCameras
.
После выбора нужной камеры создайте на ее основе экземпляр LocalVideoStream
и передайте его в videoOptions
в качестве элемента массива localVideoStream
в метод call
.
После подключения вызова он автоматически начнет отправлять видеопоток из выбранной камеры другим участникам.
Примечание.
Для защиты конфиденциальности видео не будет передаваться участникам вызова, если не просматривается локально. Дополнительные сведения см. в разделе Предварительный просмотр изображения с локальной камеры.
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);
Запуск и остановка отправки локального видео
Чтобы запустить видео, нужно перечислить камеры с помощью API getCameraList
для объекта deviceManager
. Затем создайте новый экземпляр LocalVideoStream
, передав ему данные о нужной камере, и передайте этот экземпляр в качестве аргумента в API startVideo
:
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();
После успешного запуска отправки видео экземпляр LocalVideoStream
будет добавлен в коллекцию localVideoStreams
в экземпляре вызова.
List<LocalVideoStream> videoStreams = call.getLocalVideoStreams();
LocalVideoStream currentLocalVideoStream = videoStreams.get(0); // Please make sure there are VideoStreams in the list before calling get(0).
Чтобы остановить отправку локального видео, передайте экземпляр LocalVideoStream
, доступный в коллекции localVideoStreams
:
call.stopVideo(appContext, currentLocalVideoStream).get();
Вы можете переключиться на другое устройство камеры, не прекращая передачу видео, вызвав switchSource
для экземпляра LocalVideoStream
:
currentLocalVideoStream.switchSource(source).get();
Отрисовка видеопотоков удаленных участников
Чтобы получить список видеопотоков и потоков демонстрации экрана, получаемых от удаленных участников, проверьте коллекции videoStreams
:
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
Чтобы реализовать отрисовку RemoteVideoStream
, полученного от удаленного участника, нужно подписаться на событие OnVideoStreamsUpdated
.
В этом событии изменение значения свойства isAvailable
на true указывает на то, что удаленный участник в настоящий момент отправляет поток. Обнаружив такое изменение, создайте новый экземпляр Renderer
, а затем создайте новое представление RendererView
с помощью асинхронного API createView
и подключите view.target
к любому элементу пользовательского интерфейса в приложении.
При каждом изменении состояния доступности удаленного потока вы можете удалить отрисовщик или определенное представление RendererView
либо оставить все как есть, но это приведет к отрисовке пустого видеокадра.
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();
}
}
}
Свойства удаленного видеопотока
Удаленный видеопоток имеет несколько свойств:
Id
— идентификатор удаленного видеопотока;
int id = remoteVideoStream.getId();
MediaStreamType
— принимает значения Video или ScreenSharing;
MediaStreamType type = remoteVideoStream.getMediaStreamType();
isAvailable
— указывает на то, что конечная точка этого удаленного участника активно отправляет поток данных.
boolean availability = remoteVideoStream.isAvailable();
Методы и свойства отрисовщика
Объект отрисовщика имеет следующие API:
- Создайте экземпляр
VideoStreamRendererView
, который позже можно будет подключить к пользовательскому интерфейсу приложения для отрисовке удаленного видеопотока.
// Create a view for a video stream
VideoStreamRendererView.createView()
- Удалите отрисовщик и все
VideoStreamRendererView
, связанные с ним. Вызывается при удалении всех связанных представлений из пользовательского интерфейса.
VideoStreamRenderer.dispose()
StreamSize
— размер (ширина и высота) удаленного видеопотока;
StreamSize renderStreamSize = VideoStreamRenderer.getSize();
int width = renderStreamSize.getWidth();
int height = renderStreamSize.getHeight();
Методы и свойства RendererView
При создании VideoStreamRendererView
можно указать свойства ScalingMode
и mirrored
, которые будут применяться к этому представлению. Поддерживаются режимы масштабирования CROP и FIT.
VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteVideoStream, appContext);
VideoStreamRendererView rendererView = remoteVideoRenderer.createView(new CreateViewOptions(ScalingMode.Fit));
Созданный RendererView можно затем подключить к пользовательскому интерфейсу приложения, используя следующий фрагмент кода:
layout.addView(rendererView);
Вы можете позже изменить режим масштабирования, вызвав API updateScalingMode
для объекта RendererView с аргументом ScalingMode.CROP или ScalingMode.FIT.
// Update the scale mode for this view.
rendererView.updateScalingMode(ScalingMode.CROP)
Настройка системы
Выполните следующие действия, чтобы настроить систему.
Создайте проект Xcode
В Xcode создайте новый проект iOS и выберите шаблон Single View App (Приложение с одним представлением). В этой статье используется платформа SwiftUI, поэтому необходимо задать для языка значение Swift и задать для интерфейса значение SwiftUI.
Вы не собираетесь создавать тесты в этой статье. Снимите флажок "Включить тесты ".
Установка пакета и зависимостей с помощью CocoaPods
Создайте Podfile для приложения, как показано в следующем примере:
platform :ios, '13.0' use_frameworks! target 'AzureCommunicationCallingSample' do pod 'AzureCommunicationCalling', '~> 1.0.0' end
Запустите
pod install
.Откройте
.xcworkspace
с помощью Xcode.
Запрос доступа к микрофону
Чтобы получить доступ к микрофону устройства, необходимо обновить список свойств приложения с помощью NSMicrophoneUsageDescription
. Задайте связанное значение строке, включенной в диалоговое окно, которое система использует для запроса доступа от пользователя.
Щелкните правой кнопкой мыши запись Info.plist дерева проекта и выберите "Открыть как>исходный код". Добавьте в раздел верхнего уровня <dict>
следующие строки, а затем сохраните файл.
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>
Настройка платформы приложения
Откройте файл проекта ContentView.swift
. import
Добавьте объявление в начало файла для импорта библиотекиAzureCommunicationCalling
. Кроме того, импортируйте AVFoundation
. Он необходим для запросов на разрешение звука в коде.
import AzureCommunicationCalling
import AVFoundation
Инициализация CallAgent
Чтобы создать экземпляр CallAgent
из CallClient
, необходимо использовать метод callClient.createCallAgent
, который асинхронно возвращает объект CallAgent
после его инициализации.
Чтобы создать клиент вызова, передайте 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)
}
CommunicationTokenCredential
Передайте созданный CallClient
объект и задайте отображаемое имя:
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")
}
})
Управление устройствами
Чтобы начать работу с видео с вызовами, необходимо знать, как управлять устройствами. Устройства позволяют управлять тем, что передает звук и видео в звонок.
DeviceManager
позволяет получить список локальных устройств, которые можно использовать в вызове для передачи аудио- и видеопотоков. Он также позволяет запросить у пользователя разрешение на доступ к микрофону или камере. Вы можете получить доступ к 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")
}
}
Перечисление локальных устройств
Для доступа к локальным устройствам можно использовать методы перечисления из диспетчера устройств. Действие перечисления выполняется асинхронно.
// enumerate local cameras
var localCameras = deviceManager.cameras // [VideoDeviceInfo, VideoDeviceInfo...]
Предварительный просмотр изображения с локальной камеры
С помощью Renderer
можно начать отрисовку потока с локальной камеры. Этот поток не отправляется другим участникам, а используется как локальный канал предварительного просмотра. Это действие выполняется асинхронно.
let camera: VideoDeviceInfo = self.deviceManager!.cameras.first!
let localVideoStream = LocalVideoStream(camera: camera)
let localRenderer = try! VideoStreamRenderer(localVideoStream: localVideoStream)
self.view = try! localRenderer.createView()
Получение свойств предварительного просмотра изображения с локальной камеры
Отрисовщик предоставляет ряд свойств и методов для управления отрисовкой.
// 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()
Осуществление персонального вызова с поддержкой видео
Чтобы получить экземпляр диспетчера устройств, см. раздел об управлении устройствами.
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")
}
}
Отрисовка видеопотоков удаленных участников
Удаленные участники могут предоставить общий доступ к видео или экрану во время вызова.
Обработка потоков для совместного использования видео или экрана удаленных участников
Чтобы получить список потоков от удаленных участников, изучите коллекции videoStreams
.
var remoteParticipantVideoStream = call.remoteParticipants[0].videoStreams[0]
Получение свойства удаленного видеопотока
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
Отрисовка потоков удаленного участника
Чтобы начать отрисовку потоков удаленных участников, используйте следующий код.
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)
Получение методов и свойств отрисовщика удаленного видео
// [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()
Настройка системы
Выполните следующие действия, чтобы настроить систему.
Создание проекта Visual Studio
Для приложения универсальная платформа Windows в Visual Studio 2022 создайте проект пустого приложения (универсального приложения Windows). После ввода имени проекта вы можете выбрать любой пакет SDK для Windows позже 10.0.17763.0.
Для приложения WinUI 3 создайте проект с шаблоном "Пустое приложение" (WinUI 3 в классическом приложении) для настройки одностраничного приложения WinUI 3. Требуется пакет SDK для приложений Windows версии 1.3 или более поздней.
Установка пакета и зависимостей с помощью NuGet диспетчер пакетов
Api и библиотеки пакета SDK для вызовов общедоступны через пакет NuGet.
Чтобы найти, скачать и установить пакет NuGet пакета Sdk для вызовов:
- Откройте nuGet диспетчер пакетов, выбрав инструменты>NuGet диспетчер пакетов> Manage NuGet Packages for Solution.
- Выберите "Обзор" и введите Azure.Communication.Calling.WindowsClient в поле поиска.
- Убедитесь, что установлен флажок "Включить предварительную версию ".
- Выберите пакет Azure.Communication.Calling.WindowsClient, а затем выберите Azure.Communication.Calling.WindowsClient 1.4.0-beta.1 или более позднюю версию.
- Установите флажок, соответствующий проекту Службы коммуникации Azure на правой панели.
- Выберите Установить.
Запрос доступа к микрофону
Приложению требуется доступ к камере для правильного выполнения. В приложениях UWP возможность камеры должна быть объявлена в файле манифеста приложения.
В следующих шагах показано, как это сделать.
- На панели
Solution Explorer
дважды щелкните файл с расширением.appxmanifest
. - Щелкните вкладку
Capabilities
. - Установите флажок
Camera
в списке возможностей.
Создание кнопок пользовательского интерфейса для совершения и завершения вызова
Это простое пример приложения содержит две кнопки. одну для выполнения вызова, а другую для его завершения. Следующие шаги демонстрируют, как добавить такие кнопки в приложение.
Solution Explorer
На панели дважды щелкните файл с именемMainPage.xaml
UWP илиMainWindows.xaml
WinUI 3.- На центральной панели найдите код XAML в предварительной версии пользовательского интерфейса.
- Измените код XAML следующим фрагментом:
<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>
Настройка приложения с помощью API пакета SDK для вызовов
API пакета SDK для вызовов находятся в двух разных пространствах имен. С помощью следующих шагов можно сообщить компилятору C# об этих пространствах имен, что позволяет IntelliSense в Visual Studio упростить разработку кода.
Solution Explorer
На панели щелкните стрелку в левой части файла с именемMainPage.xaml
UWP илиMainWindows.xaml
WinUI 3.- Дважды щелкните файл с именем
MainPage.xaml.cs
илиMainWindows.xaml.cs
. - Добавьте следующие команды в конце текущих инструкций
using
.
using Azure.Communication.Calling.WindowsClient;
Сохранение MainPage.xaml.cs
или MainWindows.xaml.cs
открытие. Вы добавите в него дополнительный код с помощью последующих действий.
Разрешение взаимодействий с приложением
Добавленные ранее кнопки пользовательского интерфейса должны работать на основе размещенного вызова (CommunicationCall
). Это означает, что CommunicationCall
член данных должен быть добавлен в MainPage
класс или MainWindow
класс.
Кроме того, чтобы обеспечить успешное выполнение асинхронной операции для создания CallAgent
, элемент данных CallAgent
также необходимо добавить в тот же класс.
Добавьте в или MainWindow
класс следующие элементы MainPage
данных:
CallAgent callAgent;
CommunicationCall call;
Создание обработчиков кнопок
Ранее в код XAML были добавлены две кнопки пользовательского интерфейса. Следующий код добавляет обработчики, которые будут выполняться при нажатии кнопки. Следующий код нужно добавить после элементов данных из предыдущего раздела.
private async void CallButton_Click(object sender, RoutedEventArgs e)
{
// Start call
}
private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
// End the current call
}
Объектная модель
Следующие классы и интерфейсы реализуют некоторые основные функции клиентской библиотеки вызовов в Службах коммуникации Azure для UWP:
Имя | Описание |
---|---|
CallClient |
Это CallClient основная точка входа в клиентную библиотеку вызовов. |
CallAgent |
Используется CallAgent для запуска и присоединения вызовов. |
CommunicationCall |
Используется CommunicationCall для управления размещенными или присоединенными вызовами. |
CommunicationTokenCredential |
Используется CommunicationTokenCredential в качестве учетных данных маркера для создания экземпляра CallAgent . |
CallAgentOptions |
Содержит CallAgentOptions сведения для идентификации вызывающего объекта. |
HangupOptions |
Сообщает HangupOptions , следует ли прервать звонок всем участникам. |
Регистрация обработчика схемы видео
Компонент пользовательского интерфейса, например MediaElement или MediaPlayerElement XAML, требуется, чтобы приложение зарегистрировало конфигурацию для отрисовки локальных и удаленных видеопотоков.
Добавьте следующее содержимое между тегами Package
Package.appxmanifest
:
<Extensions>
<Extension Category="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>RtmMvrUap.dll</Path>
<ActivatableClass ActivatableClassId="VideoN.VideoSchemeHandler" ThreadingModel="both" />
</InProcessServer>
</Extension>
</Extensions>
Инициализация CallAgent
Чтобы создать CallAgent
экземпляр, CallClient
необходимо использовать CallClient.CreateCallAgentAsync
метод, который асинхронно возвращает CallAgent
объект после инициализации.
Для создания CallAgent
необходимо передать объект CallTokenCredential
и объект CallAgentOptions
. Помните, что CallTokenCredential
выдает исключение при передаче неправильного маркера.
Следующий код должен быть добавлен внутри вспомогательной функции, которую необходимо вызвать в инициализации приложения.
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;
Измените <AUTHENTICATION_TOKEN>
допустимый маркер учетных данных для ресурса. Если вам необходимо получить маркер учетных данных, см. документацию по маркеру доступа пользователя.
Осуществление персонального вызова с использованием видеокамеры
Объекты, необходимые для создания CallAgent
, теперь готовы. Пришло время асинхронно создавать CallAgent
и размещать видеозвонок.
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 } }
};
}
Предварительный просмотр изображения с локальной камеры
При необходимости можно настроить предварительную версию локальной камеры. Видео можно отобразить с помощью MediaPlayerElement
:
<Grid>
<MediaPlayerElement x:Name="LocalVideo" AutoPlay="True" />
<MediaPlayerElement x:Name="RemoteVideo" AutoPlay="True" />
</Grid>
Чтобы инициализировать локальную предварительную версию 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);
}
}
Отрисовка потока удаленной камеры
Настройте даже обработчик в ответ на OnCallsUpdated
событие:
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;
}
}
Запуск отрисовки удаленного видеопотока в 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;
}
}
Завершение вызова
После совершения вызова используйте метод HangupAsync
объекта CommunicationCall
, чтобы завершить вызов.
Экземпляр HangupOptions
также следует использовать для сообщения всем участникам о необходимости завершения вызова.
Следующий код нужно добавить в HangupButton_Click
.
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;
}
}
}
}
Выполнение кода
Убедитесь, что Visual Studio создает приложение для x64
, x86
или ARM64
нажмите, F5
чтобы запустить приложение. После этого нажмите кнопку CommunicationCall
, чтобы совершить вызов к определенному вызываемому.
Помните, что при первом запуске приложения система предложит пользователю предоставить доступ к микрофону.