Administración de vídeo durante llamadas
Aprenda a administrar videollamadas con los SDK de Azure Communication Services. Aquí se va a aprender a administrar la recepción y el envío de vídeo en una llamada.
Requisitos previos
- Una cuenta de Azure con una suscripción activa. Cree una cuenta gratuita.
- Un recurso de Communication Services implementado. Cree un recurso de Communication Services.
- Un token de acceso de usuario para habilitar el cliente de llamada. Para más información, consulte Inicio rápido: Creación y administración de tokens de acceso.
- Opcional: Realice el inicio rápido para agregar llamadas de voz a la aplicación
Instalación del SDK
Use el comando npm install
para instalar los SDK comunes y de llamada de Azure Communication Services para JavaScript:
npm install @azure/communication-common --save
npm install @azure/communication-calling --save
Inicialización de los objetos necesarios
Se requiere una instancia de CallClient
para la mayoría de las operaciones de llamada. Al crear una nueva instancia de CallClient
, puede configurarla con opciones personalizadas, como una instancia de Logger
.
Con la instancia de CallClient
, puede crear una instancia de CallAgent
llamando al createCallAgent
. Este método devuelve un objeto de instancia CallAgent
de manera asincrónica.
El método createCallAgent
utiliza CommunicationTokenCredential
como argumento. Acepta un token de acceso de usuario.
Puede usar el método getDeviceManager
en la instancia de CallClient
para acceder a 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()
Cuál es la mejor forma de administrar la conectividad del SDK en la infraestructura de Microsoft
La instancia de Call Agent
le ayuda a administrar llamadas (para unirse o iniciar llamadas). Para trabajar con el SDK de llamadas, debe conectarse a la infraestructura de Microsoft para obtener notificaciones de llamadas entrantes y coordinar otros detalles de la llamada. Call Agent
tiene dos posibles estados:
Conectado: un valor connectionStatue Call Agent
con estado Connected
significa que el SDK de cliente está conectado y es capaz de recibir notificaciones de la infraestructura de Microsoft.
Desconectado: un valor connectionStatue Call Agent
con estado Disconnected
indica que hay un problema que impide que el SDK se conecte correctamente. Call Agent
se debe volver a crear.
invalidToken
: si un token ha expirado o no es válido, la instancia deCall Agent
se desconecta con este error.connectionIssue
: si hay un problema cuando el cliente se conecta a la infraestructura de Microsoft, después de muchos reintentos,Call Agent
expone el errorconnectionIssue
.
Para comprobar si el Call Agent
local está conectado a la infraestructura de Microsoft, inspeccione el valor actual de la propiedad connectionState
. Durante una llamada activa, puede escuchar el evento connectionStateChanged
para determinar si Call Agent
cambia de Conectado a Desconectado.
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);
Administración de dispositivos
Para empezar a usar vídeo con el SDK de llamadas, debe poder administrar dispositivos. Los dispositivos permiten controlar lo que transmite audio y vídeo a la llamada.
Con el deviceManager
, puede enumerar dispositivos locales que puedan transmitir secuencias de audio y vídeo en una llamada. También puede usar el deviceManager
para solicitar permiso para acceder a los micrófonos y las cámaras del dispositivo local.
Puede llamar al método callClient.getDeviceManager()
para acceder a deviceManager
.
const deviceManager = await callClient.getDeviceManager();
Obtención de dispositivos locales
Para acceder a los dispositivos locales, puede usar los métodos de enumeración deviceManager
getCameras()
y getMicrophones
. Estos métodos son acciones asincrónicas.
// 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...]
Establecimiento de los dispositivos predeterminados
Una vez que sepa qué dispositivos están disponibles para usarse, puede establecer dispositivos predeterminados para micrófono, altavoz y cámara. Si no se establecen los valores predeterminados del cliente, el SDK de Communication Services utiliza los valores predeterminados del sistema operativo.
Microphone
Acceso al dispositivo usado
// Get the microphone device that is being used.
const defaultMicrophone = deviceManager.selectedMicrophone;
Configuración del dispositivo que se va a usar
// Set the microphone device to use.
await deviceManager.selectMicrophone(localMicrophones[0]);
Orador
Acceso al dispositivo usado
// Get the speaker device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;
Configuración del dispositivo que se va a usar
// Set the speaker device to use.
await deviceManager.selectSpeaker(localSpeakers[0]);
Camera
Acceso al dispositivo usado
// Get the camera device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;
Configuración del dispositivo que se va a usar
// Set the speaker device to use.
await deviceManager.selectSpeaker(localCameras[0]);
Cada CallAgent
puede elegir su propio micrófono y altavoces en su asociadaDeviceManager
. Se recomienda que diferentes CallAgents
usen diferentes micrófonos y altavoces. No deben compartir los mismos micrófonos ni altavoces. Si se produce el uso compartido, es posible que se desencadene el diagnóstico orientado al usuario del micrófono y el micrófono deje de funcionar en función del explorador o del sistema operativo.
Secuencia de vídeo local
Para poder enviar vídeo en una llamada, debe crear un objeto LocalVideoStream
.
const localVideoStream = new LocalVideoStream(camera);
La cámara pasada como parámetro es uno del objeto VideoDeviceInfo
devuelto por el método deviceManager.getCameras()
.
Un LocalVideoStream
tiene las siguientes propiedades:
source
: La información del dispositivo.
const source = localVideoStream.source;
mediaStreamType
Puede serVideo
,ScreenSharing
oRawMedia
.
const type: MediaStreamType = localVideoStream.mediaStreamType;
Vista previa de la cámara local
Puede usar deviceManager
y VideoStreamRenderer
para empezar a representar secuencias desde la cámara local.
Una vez creado un LocalVideoStream
, úselo para configurarVideoStreamRenderer
. Una vez creado el VideoStreamRenderer
, llame a su método createView()
para obtener una vista que puede agregar como elemento secundario a la página.
Esta secuencia no se envía a otros participantes; es una fuente de vista previa local.
// 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);
Detención de la versión preliminar local
Para detener la llamada de vista previa local, elimine en la vista derivada del VideoStreamRenderer
.
Una vez eliminado VideoStreamRenderer, quite la vista del árbol html llamando al método removeChild()
del nodo DOM que contiene la vista previa.
// To stop viewing local camera preview
view.dispose();
htmlElement.removeChild(view.target);
Solicitud de permiso para acceder a la cámara o micrófono
Una aplicación no puede usar la cámara o el micrófono sin permisos. Puede usar deviceManager para solicitar a un usuario que conceda permisos de cámara o micrófono:
const result = await deviceManager.askDevicePermission({audio: true, video: true});
Una vez resuelta la promesa, el método devuelve con un objeto DeviceAccess
que indica si se concedieron los permisos audio
y video
:
console.log(result.audio);
console.log(result.video);
Notas
videoDevicesUpdated
evento se desencadena cuando los dispositivos de vídeo están conectados o desconectados.audioDevicesUpdated
evento se desencadena cuando los dispositivos de vídeo están conectados.- Cuando se crea DeviceManager, al principio no sabe sobre ningún dispositivo si aún no se conceden permisos, por lo que inicialmente su nombre de dispositivo está vacío y no contiene información detallada del dispositivo. Si después llamamos a la API DeviceManager.askPermission(), se le pedirá al usuario acceso al dispositivo. Cuando el usuario selecciona "permitir" para conceder acceso al administrador de dispositivos aprende sobre los dispositivos del sistema, actualícelo y emita los eventos "audioDevicesUpdated" y "videoDevicesUpdated". Si un usuario actualiza la página y crea un administrador de dispositivos, el administrador de dispositivos puede obtener información sobre los dispositivos porque el usuario concedió el acceso previamente. Tiene sus listas de dispositivos rellenadas inicialmente y no emite eventos "audioDevicesUpdated" ni "videoDevicesUpdated".
- La enumeración o selección del altavoz no se admite en Android Chrome, iOS Safari ni macOS Safari.
Realización de una llamada con videocámara
Importante
Actualmente,. solo se admite una secuencia de vídeo local saliente.
Para realizar una llamada de vídeo, tiene que enumerar las cámaras locales mediante el método getCameras()
de deviceManager
.
Después de seleccionar una cámara, úsela para construir una instancia de LocalVideoStream
.
Páselo en videoOptions
como un elemento de la matriz localVideoStream
al método startCall
de CallAgent
.
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);
- También puede unirse a una llamada con vídeo con
CallAgent.join()
la API y aceptar y llamar a con vídeo con laCall.Accept()
API. - Cuando la llamada se conecta, iniciará automáticamente el envío de una secuencia de vídeo desde la cámara seleccionada hasta los demás participantes.
Iniciar y detener el envío de vídeo local durante una llamada
Iniciar vídeo
Para iniciar un vídeo durante una llamada, tiene que enumerar las cámaras con el método getCameras
en el objeto deviceManager
.
Luego, cree una instancia de LocalVideoStream
con la cámara deseada y luego pase el objeto LocalVideoStream
al método startVideo
de un objeto de llamada existente:
const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
await call.startVideo(localVideoStream);
Detener vídeo
Después de empezar a enviar el vídeo correctamente, se agrega una instancia LocalVideoStream
de tipo Video
a la colección localVideoStreams
en una instancia de llamada.
Búsqueda de la secuencia de vídeo en el objeto Call
const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'Video'} );
Detención del vídeo local Para detener el vídeo local mientras se realiza una llamada, pase la instancia localVideoStream
que se usa para el vídeo al método stopVideo del Call
:
await call.stopVideo(localVideoStream);
Puede cambiar a otro dispositivo de cámara mientras tiene un LocalVideoStream activo invocando switchSource
en esa instancia LocalVideoStream
:
const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);
Si el dispositivo de vídeo especificado no está disponible:
- Mientras está en una llamada, si el vídeo está desactivado e inicia el vídeo con
call.startVideo()
, este método inicia unaSourceUnavailableError
ycameraStartFailed
diagnóstico orientado al usuario se establecerá en true. - Una llamada al método
localVideoStream.switchSource()
hace quecameraStartFailed
se establezca en true. Nuestra guía de diagnóstico de llamadas proporciona información adicional sobre cómo diagnosticar problemas relacionados con las llamadas.
Para comprobar si el vídeo local está activado o desactivado, puede usar el método Call
isLocalVideoStarted
, que devuelve true o false:
// Check if local video is on or off
call.isLocalVideoStarted;
Para escuchar los cambios en el vídeo local, puede suscribirse y cancelar la suscripción al evento isLocalVideoStartedChanged:
// Subscribe to local video event
call.on('isLocalVideoStartedChanged', () => {
// Callback();
});
// Unsubscribe from local video event
call.off('isLocalVideoStartedChanged', () => {
// Callback();
});
Iniciar y detener el uso compartido de la pantalla durante una llamada
Para iniciar el uso compartido de pantalla durante una llamada, puede usar el método asincrónico startScreenSharing()
en un objeto Call
:
Inicio del uso compartido de pantalla
// Start screen sharing
await call.startScreenSharing();
Nota: El envío de recursos compartidos de pantalla solo se admite en el explorador de escritorio.
Búsqueda del uso compartido de pantalla en la colección de LocalVideoStream
Después de empezar a enviar correctamente el uso compartido de pantalla, se agrega una instancia LocalVideoStream
de tipo ScreenSharing
a la colección localVideoStreams
en la instancia de llamada.
const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing'} );
Detención del uso compartido de pantalla
Para detener el uso compartido de pantalla mientras se realiza una llamada, puede usar la API asincrónica stoptScreenSharing:
// Stop screen sharing
await call.stopScreenSharing();
Comprobación del estado del uso compartido de pantalla
Para comprobar si el uso compartido de pantalla está activado o desactivado, puede usar isScreenSharingOn API, que devuelve verdadero o falso:
// Check if screen sharing is on or off
call.isScreenSharingOn;
Para escuchar los cambios en el recurso compartido de pantalla, puede suscribirse y cancelar la suscripción al evento isScreenSharingOnChanged:
// Subscribe to screen share event
call.on('isScreenSharingOnChanged', () => {
// Callback();
});
// Unsubscribe from screen share event
call.off('isScreenSharingOnChanged', () => {
// Callback();
});
Importante
Esta característica de Azure Communication Services se encuentra actualmente en versión preliminar.
Las API y los SDK en versión preliminar se proporcionan sin contrato de nivel de servicio. Se recomienda no usarlos para las cargas de trabajo de producción. Es posible que algunas características no sean compatibles o que sus funcionalidades estén limitadas.
Para obtener más información, consulte Términos de uso complementarios para las Versiones preliminares de Microsoft Azure.
La versión preliminar del recurso compartido de pantalla local está en versión preliminar pública y está disponible como parte de la versión 1.15.1-beta.1+.
Vista previa del recurso compartido de pantalla local
Puede usar un VideoStreamRenderer
para empezar a representar secuencias desde el recurso compartido de pantalla local para que pueda ver lo que envía como secuencia de uso compartido de pantalla.
// 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);
}
});
Representación de secuencias de vídeo o pantallas de participantes remotos
Para representar un vídeo de participante remoto o un uso compartido de pantalla, el primer paso es obtener una referencia en RemoteVideoStream que desea representar.
Esto se puede hacer pasando por la matriz o secuencia de vídeo (videoStreams
) del RemoteParticipant
. Se accede a la colección de participantes remotos a través del objeto Call
.
const remoteVideoStream = call.remoteParticipants[0].videoStreams[0];
const streamType = remoteVideoStream.mediaStreamType;
Para representar RemoteVideoStream
, debe suscribirse a su evento isAvailableChanged
. Si la propiedad isAvailable
cambia a true
, un participante remoto envía una secuencia de vídeo.
Cuando esto suceda, cree una instancia de VideoStreamRenderer
y, luego, cree una instancia de VideoStreamRendererView
nueva a través del método createView
asincrónico.
Después puede adjuntar view.target
a cualquier elemento de la interfaz de usuario.
Siempre que cambie la disponibilidad de una secuencia remota, puede destruir todo el VideoStreamRenderer
o un VideoStreamRendererView
específico.
Si decide mantenerlos, la vista muestra un fotograma de vídeo en blanco.
// 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 para aplicar un estilo al indicador giratorio de carga a través de la secuencia de vídeo remota.
.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); }
}
Calidad de vídeo remoto
El SDK de WebJS de Azure Communication Services proporciona una característica denominada Recuento óptimo de vídeos (OVC), a partir de la versión 1.15.1.
Esta característica se puede usar para informar a las aplicaciones en tiempo de ejecución sobre cuántos vídeos entrantes de diferentes participantes se pueden representar de forma óptima en un momento dado en una llamada grupal (2+ participantes).
Esta característica expone una propiedad optimalVideoCount
que cambia dinámicamente durante la llamada en función de las funcionalidades de red y hardware de un punto de conexión local. El valor de optimalVideoCount
detalla cuántos vídeos de la aplicación participante diferente deben representarse en un momento dado. Las aplicaciones deben controlar estos cambios y actualizar el número de vídeos representados en consecuencia a la recomendación. Hay un período de rebote (alrededor de 10 s) entre cada actualización.
Uso La característica optimalVideoCount
de una característica de llamada. Debe hacer referencia a la característica OptimalVideoCount
a través del método feature
del objeto Call
. A continuación, puede establecer un agente de escucha a través del método on
del OptimalVideoCountCallFeature
que se notificará cuando cambie el valor de optimalVideoCount. Para cancelar la suscripción de los cambios, puede llamar al método off
. El número máximo actual de vídeos entrantes que se pueden representar es 16. Para admitir correctamente 16 vídeos entrantes, el equipo debe tener un mínimo de 16 GB de RAM y una CPU de 4 núcleos o superior que no tenga más de 3 años.
const optimalVideoCountFeature = call.feature(Features.OptimalVideoCount);
optimalVideoCountFeature.on('optimalVideoCountChanged', () => {
const localOptimalVideoCountVariable = optimalVideoCountFeature.optimalVideoCount;
})
Uso de ejemplo: la aplicación debe suscribirse a los cambios del recuento óptimo de vídeos en las llamadas grupales. Se puede controlar un cambio en el recuento de vídeos óptimo mediante la creación de un nuevo representador (método createView
) o eliminar vistas (dispose
) y actualizar el diseño de la aplicación en consecuencia.
Propiedades de secuencias de vídeo remotas
Las secuencias de vídeo remotas tienen las propiedades siguientes:
const id: number = remoteVideoStream.id;
id
: el identificador de una secuencia de vídeo remota.
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
mediaStreamType
: puede serVideo
oScreenSharing
.
const isAvailable: boolean = remoteVideoStream.isAvailable;
isAvailable
: Define si un punto de conexión de participante remoto envía activamente una secuencia.
const isReceiving: boolean = remoteVideoStream.isReceiving;
isReceiving
:Informa a la aplicación si se reciben o no datos de secuencias de vídeo remotos.
La marca se mueve a
false
en los siguientes escenarios:- Un participante remoto que se encuentra en el explorador móvil lleva la aplicación del explorador al fondo.
- Un participante remoto o el usuario que recibe el vídeo tiene un problema de red que afecta drásticamente a la calidad del vídeo.
- Un participante remoto que esté en macOS o iOS Safari selecciona "Pausar" en su barra de direcciones.
- Un participante remoto tiene una desconexión de red.
- Un participante remoto en dispositivos móviles termina o finaliza el explorador.
- Un participante remoto en dispositivos móviles o de escritorio bloquea su dispositivo. Este escenario también se aplica si el participante remoto está en un equipo de escritorio y va a dormir.
La marca se mueve a
true
en los siguientes escenarios:- Un participante remoto que está en el explorador móvil y tiene su navegador en segundo plano lo devuelve a primer plano.
- Un participante remoto que está en macOS o iOS Safari selecciona "Reanudar" en su barra de direcciones después de haber pausado su vídeo.
- Un participante remoto se vuelve a conectar a la red después de una desconexión temporal.
- Un participante remoto en el dispositivo móvil desbloquea su dispositivo y vuelve a la llamada en su navegador móvil.
Esta característica mejora la experiencia del usuario para representar secuencias de vídeo remotas.
Puede mostrar un indicador giratorio de carga a través de la secuencia de vídeo remota cuando la marca isReceiving cambie a false. No es necesario implementar el indicador giratorio de carga, pero un indicador giratorio de carga es el uso más común para mejorar la experiencia del usuario.
const size: StreamSize = remoteVideoStream.size;
size
: tamaño del flujo con información sobre el ancho y el alto del vídeo.
Métodos y propiedades de VideoStreamRenderer
await videoStreamRenderer.createView();
Cree una instancia de VideoStreamRendererView
que se pueda adjuntar en la interfaz de usuario de la aplicación para representar la secuencia de vídeo remota, usar el método createView()
asincrónico, se resuelve cuando la secuencia está lista para representarse y devuelve un objeto con la propiedad target
que representa el elemento video
que se puede insertar en cualquier parte del árbol DOM.
videoStreamRenderer.dispose();
Elimine videoStreamRenderer
y todas las instancias de VideoStreamRendererView
asociadas.
Métodos y propiedades de VideoStreamRenderer
Cuando crea un objeto VideoStreamRendererView
, puede especificar las propiedades scalingMode
y isMirrored
. scalingMode
puede ser Stretch
, Crop
o Fit
. Si se especifica isMirrored
, la secuencia representada se voltea verticalmente.
const videoStreamRendererView: VideoStreamRendererView = await videoStreamRenderer.createView({ scalingMode, isMirrored });
Toda instancia de VideoStreamRendererView
determinada tiene una propiedad target
que representa la superficie de representación. Adjunte esta propiedad en la interfaz de usuario de la aplicación:
htmlElement.appendChild(view.target);
Puede actualizar scalingMode
mediante la llamada al método updateScalingMode
:
view.updateScalingMode('Crop');
Envíe secuencias de vídeo de dos cámaras diferentes, en la misma llamada desde el mismo dispositivo de escritorio.
Importante
Esta característica de Azure Communication Services se encuentra actualmente en versión preliminar.
Las API y los SDK en versión preliminar se proporcionan sin contrato de nivel de servicio. Se recomienda no usarlos para las cargas de trabajo de producción. Es posible que algunas características no sean compatibles o que sus funcionalidades estén limitadas.
Para obtener más información, consulte Términos de uso complementarios para las Versiones preliminares de Microsoft Azure.
Enviar secuencias de vídeo desde dos cámaras diferentes en la misma llamada se admite como parte de la versión 1.17.1-beta.1+ en exploradores compatibles con escritorio.
- Puede enviar secuencias de vídeo de dos cámaras diferentes desde una única pestaña o aplicación del navegador de escritorio, en la misma llamada, con el siguiente fragmento de código:
// 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();
Limitaciones:
- Esto debe hacerse con dos instancias
CallAgent
diferentes mediante identidades diferentes. El fragmento de código muestra dos agentes de llamada que se usan, cada uno con su propio objeto Call. - En el ejemplo de código, ambos CallAgents se unen a la misma llamada (los mismos identificadores de llamada). También puede unirse a diferentes llamadas con cada agente y enviar un vídeo en una llamada y otro vídeo en la otra llamada.
- No se admite el envío de la misma cámara en CallAgent. Deben ser dos cámaras diferentes.
- Actualmente no se admite el envío de dos cámaras diferentes con un CallAgent.
- En macOS Safari, los efectos de vídeo de desenfoque de fondo (de @azure/communication-effects), solo se pueden aplicar a una cámara y no ambas a la vez.
Instalación del SDK
Busque el archivo build.gradle
de nivel de proyecto y agregue mavenCentral()
a la lista de repositorios en buildscript
y allprojects
:
buildscript {
repositories {
...
mavenCentral()
...
}
}
allprojects {
repositories {
...
mavenCentral()
...
}
}
Luego, en el archivo build.gradle
de nivel de módulo, agregue las siguientes líneas a la sección dependencies
:
dependencies {
...
implementation 'com.azure.android:azure-communication-calling:1.0.0'
...
}
Inicialización de los objetos necesarios
Para crear una instancia de CallAgent
, debe llamar al método createCallAgent
en una instancia de CallClient
. Esta llamada devuelve un objeto de instancia de CallAgent
de manera asincrónica.
El método createCallAgent
toma CommunicationUserCredential
como argumento, que encapsula un token de acceso.
Para acceder a DeviceManager
, primero debe crear una instancia de callAgent
. A continuación, puede usar el método CallClient.getDeviceManager
para obtener 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();
Para establecer un nombre para mostrar para el autor de la llamada, use este método alternativo:
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();
Administración de dispositivos
Para empezar a usar vídeo con llamadas, debe saber cómo administrar dispositivos. Los dispositivos permiten controlar lo que transmite audio y vídeo a la llamada.
DeviceManager
permite enumerar los dispositivos locales que se pueden usar en una llamada para transmitir las secuencias de audio o vídeo. También permite solicitar permiso a un usuario para acceder a su micrófono y cámara mediante la API nativa del explorador.
Puede llamar al método callClient.getDeviceManager()
para acceder a deviceManager
.
Context appContext = this.getApplicationContext();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
Enumeración de los dispositivos locales
Para acceder a los dispositivos locales, puede usar métodos de enumeración en el Administrador de dispositivos. La enumeración es una acción sincrónica.
// Get a list of available video devices for use.
List<VideoDeviceInfo> localCameras = deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]
Vista previa de la cámara local
Puede usar DeviceManager
y Renderer
para empezar a representar secuencias desde la cámara local. Esta secuencia no se enviará a los demás participantes, porque es una fuente de vista previa local. Se trata de una acción asincrónica.
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);
Realización de una llamada 1:1 con videocámara
Advertencia
Actualmente, solo se admite una secuencia de vídeo local saliente. Para realizar una llamada con vídeo, debe enumerar las cámaras locales mediante la API deviceManager
getCameras
.
Una vez que seleccione la cámara deseada, úsela para crear una instancia de LocalVideoStream
y pásela a videoOptions
como elemento de la matriz localVideoStream
a un método call
.
Una vez que la llamada se conecta, iniciará automáticamente el envío de una secuencia de vídeo desde la cámara seleccionada hasta los demás participantes.
Nota:
Debido a cuestiones de privacidad, el vídeo no se compartirá con la llamada si no se muestra como una versión preliminar localmente. Consulte Versión preliminar de la cámara local para obtener más detalles.
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);
Inicio y detención del envío de vídeo local
Para iniciar un vídeo, tiene que enumerar las cámaras con la API getCameraList
en el objeto deviceManager
. Luego, cree una instancia de LocalVideoStream
pasando la cámara deseada y pásela a la API startVideo
como un argumento:
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();
Una vez que logre empezar a enviar vídeo, se agregará una instancia de LocalVideoStream
a la colección localVideoStreams
en la instancia de llamada.
List<LocalVideoStream> videoStreams = call.getLocalVideoStreams();
LocalVideoStream currentLocalVideoStream = videoStreams.get(0); // Please make sure there are VideoStreams in the list before calling get(0).
Para detener el vídeo local, pase la instancia LocalVideoStream
disponible en la colección localVideoStreams
:
call.stopVideo(appContext, currentLocalVideoStream).get();
Puede cambiar a un dispositivo de cámara distinto mientras se envía vídeo si invoca switchSource
en una instancia de LocalVideoStream
:
currentLocalVideoStream.switchSource(source).get();
Representación de secuencias de vídeo de participantes remotos
Para una lista de las secuencias de vídeo y de las secuencias de uso compartido de pantalla de participantes remotos, revise las colecciones 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
Para representar una RemoteVideoStream
de un participante remoto, debe suscribirse a un evento OnVideoStreamsUpdated
.
En el evento, el cambio de la propiedad isAvailable
a true indica que el participante remoto está enviando una secuencia actualmente. Si es el caso, cree una nueva instancia de Renderer
y, a continuación, cree una nueva instancia de RendererView
con la API de createView
asincrónica y adjunte view.target
en cualquier parte de la interfaz de usuario de la aplicación.
Cada vez que cambia la disponibilidad de una secuencia remota, puede elegir destruir todo el representador, un RendererView
específico o mantenerlos, pero esto hará que el fotograma del vídeo se vea en blanco.
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();
}
}
}
Propiedades de secuencias de vídeo remotas
La secuencia de vídeo remota tiene un par de propiedades
Id
: id. de una secuencia de vídeo remota
int id = remoteVideoStream.getId();
MediaStreamType
: puede ser "Video" o "ScreenSharing"
MediaStreamType type = remoteVideoStream.getMediaStreamType();
isAvailable
: indica si el punto de conexión del participante remoto envía secuencias de manera activa
boolean availability = remoteVideoStream.isAvailable();
Métodos y propiedades del representador
Objeto del representador que sigue a las API
- Cree una instancia de
VideoStreamRendererView
que se pueda adjuntar posteriormente en la interfaz de usuario de la aplicación para representar la secuencia de vídeo remota.
// Create a view for a video stream
VideoStreamRendererView.createView()
- Eliminación del representador y de todos los elementos
VideoStreamRendererView
asociados a este representador. Se llamará cuando se hayan quitado todas las vistas asociadas de la interfaz de usuario.
VideoStreamRenderer.dispose()
StreamSize
: tamaño (ancho y alto) de una secuencia de vídeo remota
StreamSize renderStreamSize = VideoStreamRenderer.getSize();
int width = renderStreamSize.getWidth();
int height = renderStreamSize.getHeight();
Métodos y propiedades de RendererView
Al crear un objeto VideoStreamRendererView
, puede especificar las propiedades ScalingMode
y mirrored
que se aplicarán a esta vista: el modo de escalado puede ser "CROP" | "FIT".
VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteVideoStream, appContext);
VideoStreamRendererView rendererView = remoteVideoRenderer.createView(new CreateViewOptions(ScalingMode.Fit));
A continuación, el elemento RendererView creado se puede asociar a la interfaz de usuario de la aplicación mediante el siguiente fragmento de código:
layout.addView(rendererView);
Posteriormente podrá actualizar el modo de escalado invocando la API updateScalingMode
en el objeto RendererView con ScalingMode.CROP | ScalingMode.FIT como argumento.
// Update the scale mode for this view.
rendererView.updateScalingMode(ScalingMode.CROP)
Configuración del sistema
Siga estos pasos para configurar el sistema.
Creación del proyecto de Xcode
En Xcode, cree un nuevo proyecto de iOS y seleccione la plantilla Aplicación de una vista. En este artículo se usa el marco SwiftUI, por lo que debe establecer el Lenguaje en Swift y la Interfaz en SwiftUI.
No va a crear pruebas en este artículo. Puede desactivar la casilla Incluir pruebas.
Instalación del paquete y las dependencias mediante CocoaPods
Cree un Podfile para la aplicación, como en este ejemplo:
platform :ios, '13.0' use_frameworks! target 'AzureCommunicationCallingSample' do pod 'AzureCommunicationCalling', '~> 1.0.0' end
Ejecute
pod install
.Abra
.xcworkspace
mediante Xcode.
Solicitud de acceso al micrófono
Para acceder al micrófono del dispositivo, debe actualizar la lista de propiedades de información de la aplicación mediante NSMicrophoneUsageDescription
. Establezca el valor asociado en una cadena que se incluye en el cuadro de diálogo empleado por el sistema para solicitar acceso al usuario.
Haga clic con el botón derecho en la entrada Info.plist del árbol del proyecto y seleccione Abrir como>Código fuente. Agregue las líneas siguientes a la sección <dict>
de nivel superior y guarde el archivo.
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>
Instalación del marco de la aplicación
Abra el archivo ContentView.swift
del proyecto. Agregue una declaración import
a la parte superior del archivo para importar la biblioteca AzureCommunicationCalling
. Además, importe AVFoundation
. Lo necesitará para las solicitudes de permiso de audio en el código.
import AzureCommunicationCalling
import AVFoundation
Inicialización de CallAgent
Para crear una instancia de CallAgent
a partir de CallClient
, debe usar el método callClient.createCallAgent
, que devuelve de manera asincrónica un objeto CallAgent
después de que se inicializa.
Para crear un cliente de llamada, pase un objeto 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)
}
Pase el objeto CommunicationTokenCredential
que ha creado a CallClient
y establezca el nombre para mostrar:
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")
}
})
Administrar los dispositivos
Para empezar a usar vídeo con llamadas, debe saber cómo administrar dispositivos. Los dispositivos permiten controlar lo que transmite audio y vídeo a la llamada.
DeviceManager
permite enumerar los dispositivos locales que se pueden usar en una llamada para transmitir secuencias de audio o vídeo. También permite solicitar permiso a un usuario para acceder al micrófono o a la cámara. Puede acceder a deviceManager
en el objeto 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")
}
}
Enumeración de los dispositivos locales
Para acceder a los dispositivos locales, puede usar métodos de enumeración en el administrador de dispositivos. La enumeración es una acción sincrónica.
// enumerate local cameras
var localCameras = deviceManager.cameras // [VideoDeviceInfo, VideoDeviceInfo...]
Obtención de una vista previa de la cámara local
Puede usar Renderer
para empezar a representar una secuencia desde su cámara local. Esta secuencia no se enviará a los demás participantes, porque es una fuente de vista previa local. Se trata de una acción asincrónica.
let camera: VideoDeviceInfo = self.deviceManager!.cameras.first!
let localVideoStream = LocalVideoStream(camera: camera)
let localRenderer = try! VideoStreamRenderer(localVideoStream: localVideoStream)
self.view = try! localRenderer.createView()
Obtención de las propiedades de la vista previa de la cámara local
El representador tiene un conjunto de propiedades y métodos que le permiten controlar la representación.
// 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()
Realización de una llamada 1:1 con vídeo
Para obtener una instancia del administrador de dispositivos, consulte la sección acerca de la administración de dispositivos.
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")
}
}
Representación de secuencias de vídeo de participantes remotos
Los participantes remotos pueden iniciar el uso compartido de pantalla o vídeo durante una llamada.
Control de las transmisiones de uso compartido de vídeo o de pantalla de participantes remotos
Para enumerar las transmisiones de los participantes remotos, inspeccione las colecciones videoStreams
.
var remoteParticipantVideoStream = call.remoteParticipants[0].videoStreams[0]
Obtención de las propiedades de las transmisiones de vídeo remotas
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
Representación de las transmisiones de participantes remotos
Para iniciar la representación de las transmisiones de los participantes remotos, use el código siguiente.
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)
Obtención de los métodos y propiedades del representador de vídeo
// [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()
Configuración del sistema
Siga estos pasos para configurar el sistema.
Creación del proyecto de Visual Studio
En el caso de una aplicación para la Plataforma universal de Windows, en Visual Studio 2022, cree un proyecto de Aplicación vacía (Universal Windows). Después de escribir el nombre del proyecto, puede elegir cualquier Windows SDK posterior a 10.0.17763.0.
En el caso de una aplicación WinUI 3, cree un nuevo proyecto con la plantilla Aplicación vacía, empaquetada (WinUI 3 en escritorio) para configurar una aplicación WinUI 3 de una sola página. Se requiere la versión 1.3 o posterior del SDK de aplicaciones de Windows.
Instalación del paquete y las dependencias mediante el Administrador de paquetes NuGet
Las API y bibliotecas de SDK de llamadas están disponibles públicamente a través de un paquete NuGet.
Para buscar, descargar e instalar el paquete NuGet del SDK de llamadas:
- Abra el Administrador de paquetes NuGet desde Herramientas>Administrador de paquetes NuGet>Administrar paquetes NuGet para la solución.
- Seleccione Explorar y, después, escriba Azure.Communication.Calling.WindowsClient en el cuadro de búsqueda.
- Asegúrese de que la casilla Incluir versión preliminar esté activada.
- Seleccione el paquete Azure.Communication.Calling.WindowsClient y, después, Azure.Communication.Calling.WindowsClient 1.4.0-beta.1 o una versión más reciente.
- Seleccione la casilla correspondiente al proyecto de Azure Communication Services en el panel derecho.
- Seleccione Instalar.
Solicitud de acceso al micrófono
La aplicación requiere acceso a la cámara para que se ejecute correctamente. En las aplicaciones para UWP, la funcionalidad de la cámara debe estar declarada en el archivo de manifiesto de la aplicación.
Los siguientes pasos ejemplifican cómo lograrlo.
- En el panel
Solution Explorer
, haga doble clic en el archivo con la extensión.appxmanifest
. - Haga clic en la pestaña
Capabilities
. - Active la casilla
Camera
de la lista de funcionalidades.
Creación de botones de UI para realizar la llamada y colgar
Esta sencilla aplicación de ejemplo incluye dos botones. Uno para realizar la llamada y otro para colgar una llamada realizada. Los pasos siguientes ejemplifican cómo agregar estos botones a la aplicación.
- En el
Solution Explorer
panel, haz doble clic en el archivo denominadoMainPage.xaml
para UWP, oMainWindows.xaml
en WinUI 3. - En el panel central, busque el código XAML en la versión preliminar de la interfaz de usuario.
- Reemplace el código XAML por el fragmento siguiente:
<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>
Configuración de la aplicación con las API de Calling SDK
Las API de Calling SDK se encuentran en dos espacios de nombres diferentes. Los pasos siguientes informan al compilador de C# sobre estos espacios de nombres, lo que permite a IntelliSense de Visual Studio ayudar con el desarrollo del código.
- En el panel
Solution Explorer
, haga clic en la flecha del lado izquierdo del archivo denominadoMainPage.xaml
para UWP, oMainWindows.xaml
para WinUI 3. - Haga doble clic en el archivo denominado
MainPage.xaml.cs
oMainWindows.xaml.cs
. - Agregue los siguientes comandos al final de las instrucciones
using
actuales.
using Azure.Communication.Calling.WindowsClient;
Mantenga abierto MainPage.xaml.cs
o MainWindows.xaml.cs
. En los pasos siguientes se le agregará más código.
Habilitación de interacciones de aplicaciones
Los botones de la interfaz de usuario agregados anteriormente tienen que funcionar sobre un CommunicationCall
colocado. Esto significa que un miembro de datos CommunicationCall
debe agregarse a la clase MainPage
o MainWindow
.
Además, para permitir que la operación asincrónica que crea CallAgent
se complete correctamente, también tiene que agregar un miembro de datos CallAgent
a la misma clase.
Agregue los siguientes miembros de datos a la clase MainPage
o MainWindow
:
CallAgent callAgent;
CommunicationCall call;
Creación de controladores de botones
Anteriormente, se agregaron dos botones de interfaz de usuario al código XAML. En el código siguiente, se agregan los controladores que se ejecutarán cuando un usuario seleccione el botón. El código siguiente debe agregarse después de los miembros de datos de la sección anterior.
private async void CallButton_Click(object sender, RoutedEventArgs e)
{
// Start call
}
private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
// End the current call
}
Modelo de objetos
Las clases e interfaces siguientes controlan algunas de las características principales de la biblioteca cliente para llamadas de Azure Communication Services para UWP.
Nombre | Descripción |
---|---|
CallClient |
CallClient es el punto de entrada principal a la biblioteca cliente de llamadas. |
CallAgent |
CallAgent se usa para iniciar llamadas y unirse a estas. |
CommunicationCall |
CommunicationCall se usa para administrar las llamadas realizadas o a las que se ha unido. |
CommunicationTokenCredential |
CommunicationTokenCredential se usa como la credencial del token para crear una instancia de CallAgent . |
CallAgentOptions |
El CallAgentOptions contiene información para identificar al autor de la llamada. |
HangupOptions |
El HangupOptions informa sobre si se debe finalizar una llamada a todos los participantes. |
Registro del controlador de esquema de vídeo
Un componente de interfaz de usuario, como MediaElement o MediaPlayerElement de XAML, necesita que la aplicación registre una configuración para representar fuentes de vídeo locales y remotas.
Agregue el siguiente contenido entre las etiquetas Package
del Package.appxmanifest
:
<Extensions>
<Extension Category="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>RtmMvrUap.dll</Path>
<ActivatableClass ActivatableClassId="VideoN.VideoSchemeHandler" ThreadingModel="both" />
</InProcessServer>
</Extension>
</Extensions>
Inicialización de CallAgent
Para crear una instancia de CallAgent
a partir de CallClient
, tiene que usar el método CallClient.CreateCallAgentAsync
que devuelve de manera asincrónica un objeto CallAgent
una vez que se inicializa.
Para crear CallAgent
, tiene que pasar un objeto CallTokenCredential
y un objeto CallAgentOptions
. Tenga en cuenta que se produce un error de CallTokenCredential
si se pasa un token con formato incorrecto.
Se debe agregar dentro el código siguiente y se debe llamar a la función auxiliar en la inicialización de la aplicación.
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;
Cambie el <AUTHENTICATION_TOKEN>
con un token de credencial válido para el recurso. Consulte la documentación sobre tokens de acceso de usuario si es necesario proporcionar un token de credencial.
Realización de una llamada 1:1 con videocámara
Los objetos necesarios para crear CallAgent
ya están listos. Es el momento de crear CallAgent
de manera asincrónica y realizar una videollamada.
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 } }
};
}
Vista previa de la cámara local
Opcionalmente, podemos configurar la vista previa de la cámara local. El vídeo se puede representar a través de MediaPlayerElement
:
<Grid>
<MediaPlayerElement x:Name="LocalVideo" AutoPlay="True" />
<MediaPlayerElement x:Name="RemoteVideo" AutoPlay="True" />
</Grid>
Para inicializar la versión preliminar local 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);
}
}
Representar secuencias de cámara remota
Configure el controlador de eventos en respuesta al evento 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;
}
}
Inicie la representación de la secuencia del vídeo remoto en 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;
}
}
Finalizar una llamada
Una vez realizada una llamada, se debe usar el método HangupAsync
del objeto CommunicationCall
para colgar la llamada.
También se debe usar una instancia de HangupOptions
para informar sobre si la llamada se debe finalizar a todos sus participantes.
El código siguiente debe agregarse dentro de 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;
}
}
}
}
Ejecución del código
Asegúrese de que Visual Studio compila la aplicación para x64
, x86
o ARM64
, y presione F5
para empezar a ejecutar la aplicación. Después de esto, haga clic en el botón CommunicationCall
para realizar una llamada al destinatario definido.
Tenga en cuenta que la primera vez que se ejecute la aplicación, el sistema solicita al usuario que conceda acceso al micrófono.