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

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 de Call 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 error connectionIssue.

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 deviceManagergetCameras() 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 ser Video, ScreenSharing o RawMedia.
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 CallAgentstartCall.

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 la Call.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 una SourceUnavailableError y cameraStartFailed diagnóstico orientado al usuario se establecerá en true.
  • Una llamada al método localVideoStream.switchSource() hace que cameraStartFailed 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 CallisLocalVideoStarted, 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();

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 ScreenSharinga 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 VideoStreamRendererViewespecí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.

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 ser Video o ScreenSharing.
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()
    ...
    }
}

A continuación, 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 deviceManagergetCameras. 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

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 inicio rápido se usa el marco SwiftUI, por lo que debe establecer el Lenguaje en Swift y establecer la interfaz en SwiftUI.

Durante este inicio rápido, no se van a crear pruebas. Puede desactivar la casilla Incluir pruebas.

Captura de pantalla que muestra la ventana para crear un proyecto en Xcode.

Instalación del paquete y las dependencias mediante CocoaPods

  1. 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
    
  2. Ejecute pod install.

  3. 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. Establecerá el valor asociado a una cadena que se va a incluir en el cuadro de diálogo que el sistema usa 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

Creación del proyecto de Visual Studio

En el caso de una aplicación para UWP, en Visual Studio 2022, cree un nuevo 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.

Los pasos siguientes demuestran cómo encontrar, descargar e instalar el paquete NuGet del SDK de llamada:

  1. Abra el Administrador de paquetes NuGet desde Herramientas>Administrador de paquetes NuGet>Administrar paquetes NuGet para la solución.
  2. Seleccione Examinar y, luego, escriba Azure.Communication.Calling.WindowsClient en el cuadro de búsqueda.
  3. Asegúrese de que la casilla Incluir versión preliminar esté activada.
  4. Seleccione el paquete Azure.Communication.Calling.WindowsClient y, luego, elija Azure.Communication.Calling.WindowsClient1.4.0-beta.1 o una versión más reciente.
  5. Active la casilla correspondiente al proyecto de Communication Services en la pestaña derecha.
  6. Seleccione el botón 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.

  1. En el panel Solution Explorer, haga doble clic en el archivo con la extensión .appxmanifest.
  2. Haga clic en la pestaña Capabilities.
  3. 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.

  1. En el Solution Explorer panel, haz doble clic en el archivo denominado MainPage.xaml para UWP, o MainWindows.xaml en WinUI 3.
  2. En el panel central, busque el código XAML en la versión preliminar de la interfaz de usuario.
  3. 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.

  1. En el panel Solution Explorer, haga clic en la flecha del lado izquierdo del archivo denominado MainPage.xaml para UWP, o MainWindows.xaml para WinUI 3.
  2. Haga doble clic en el archivo denominado MainPage.xaml.cs o MainWindows.xaml.cs.
  3. 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.

Pasos siguientes