Compartilhar via


Gerenciar vídeo durante chamadas

Saiba como gerenciar chamadas em vídeo com os SDKS dos Serviços de Comunicação do Azure. Vamos aprender como gerenciar o recebimento e o envio de vídeos em uma chamada.

Pré-requisitos

Instalar o SDK

Use o comando npm install para instalar o SDK Comum e de Chamada dos Serviços de Comunicação do Azure para JavaScript:

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

Inicializar objetos necessários

Uma instância CallClient é necessária para a maioria das operações de chamada. Ao criar uma instância de CallClient, você pode configurá-la com opções personalizadas, como uma instância de Logger.

Com a instância de CallClient, você pode criar uma instância de CallAgent chamando o createCallAgent. Esse método retorna de modo assíncrono um objeto de instância CallAgent.

O método createCallAgent usa CommunicationTokenCredential como um argumento. Ele aceita um token de acesso do usuário.

Você pode usar o método getDeviceManager na instância CallClient para acessar o 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()

Gerenciar a conectividade do SDK com a infraestrutura da Microsoft

A instância Call Agent ajuda você a gerenciar chamadas (para ingressar ou iniciar chamadas). Para trabalhar, o SDK de chamada precisa se conectar à infraestrutura da Microsoft para receber notificações de chamadas de entrada e coordenar outros detalhes da chamada. O seu Call Agent tem dois estados possíveis:

Conectado: um valor connectionStatue Call Agent de Connected significa que o SDK do cliente está conectado e capaz de receber notificações da infraestrutura da Microsoft.

Desconectado: um valor connectionStatue Call Agent de Disconnected declara que há um problema que está impedindo o SDK de se conectar corretamente. Call Agent deve ser recriado.

  • invalidToken: se um token tiver expirado ou for inválido, a instância Call Agent se desconecta com este erro.
  • connectionIssue: caso haja um problema de conexão do cliente com a infraestrutura da Microsoft, depois de muitas tentativas, Call Agent expõe o erro connectionIssue.

Você pode verificar se o local Call Agent está conectado à infraestrutura da Microsoft inspecionando o valor atual da propriedade connectionState. Durante uma chamada ativa, você pode ouvir o evento connectionStateChanged para determinar se Call Agent é alterado do estado Conectado ao 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);

Gerenciamento de dispositivos

Para começar a usar vídeo com o Calling SDK, você precisa ser capaz de gerenciar dispositivos. Os dispositivos permitem controlar o que transmite Áudio e Vídeo para a chamada.

Use o deviceManager para enumerar dispositivos locais que podem transmitir seus fluxos de áudio e vídeo em uma ligação. Você também pode usar o deviceManager para solicitar permissão de acesso aos microfones e câmeras do dispositivo local.

Você pode acessar o deviceManager chamando o método callClient.getDeviceManager():

const deviceManager = await callClient.getDeviceManager();

Obter dispositivos locais

Para acessar dispositivos locais, você pode usar os deviceManager métodos de enumeração getCameras() e getMicrophones. Esses métodos são ações assíncronas.

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

Defina os dispositivos padrão

Depois de saber quais dispositivos estão disponíveis para uso, você pode definir dispositivos padrão para microfone, alto-falante e câmera. Se os padrões do cliente não forem definidos, o SDK dos Serviços de Comunicação usará os padrões do sistema operacional.

Microfone

Acesse o dispositivo usado

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

Configurando o dispositivo para uso

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

Palestrante

Acesse o dispositivo usado

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

Configurando o dispositivo para uso

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

Câmera

Acesse o dispositivo usado

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

Configurando o dispositivo para uso

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

Cada CallAgent pode escolher seu próprio microfone e alto-falantes em seu DeviceManager associado. Recomendamos que diferentes CallAgents usem microfones e alto-falantes diferentes. Eles não deveriam compartilhar os mesmos microfones nem alto-falantes. Se o compartilhamento ocorrer, o Diagnóstico de Microfone Voltado para o Usuário (UFD) pode ser ativado, e o microfone pode parar de funcionar dependendo do navegador e do sistema operacional.

Transmissão de vídeo local

Para que os usuários enviem vídeo em uma chamada, você deve criar um LocalVideoStream objeto.

const localVideoStream = new LocalVideoStream(camera);

A câmera passada como parâmetro é um VideoDeviceInfo objeto retornado pelo deviceManager.getCameras() método.

Um LocalVideoStream tem as propriedades a seguir:

  • source é a informação do dispositivo.

    const source = localVideoStream.source;
    
  • mediaStreamType pode ser Video, ScreenSharing, ou RawMedia.

    const type: MediaStreamType = localVideoStream.mediaStreamType;
    

Visualização da câmera local

Você pode usar deviceManager e VideoStreamRenderer para começar a renderizar fluxos da sua câmera local.

Depois de criar LocalVideoStream, use-o para configurarVideoStreamRenderer. Depois de criar o VideoStreamRenderer, chame seu método createView() para obter uma exibição que você pode adicionar como subelemento à sua página.

Esse fluxo não é enviado para outros participantes. É um feed de visualização 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);

Pare a visualização local

Para interromper a chamada de visualização local, descarte a visualização derivada do VideoStreamRenderer. Depois que o VideoStreamRenderer for descartado, remova a visualização da árvore html chamando o método removeChild() do nó DOM que contém sua visualização.

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

Solicitar permissão de acesso à câmera e microfone

Um aplicativo não pode usar a câmera ou o microfone sem permissões. Você pode usar o deviceManager para solicitar que um usuário conceda permissões de câmera e/ou microfone:

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

Assim que a promessa for resolvida, o método retorna com um objeto DeviceAccess que indica se as permissões audio e video foram concedidas:

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

Observações

  • videoDevicesUpdated evento é acionado quando os dispositivos de vídeo estão conectados/desconectados.
  • audioDevicesUpdated evento é acionado quando dispositivos de áudio são conectados.
  • Quando você cria DeviceManagerpela primeira vez, ele não sabe sobre nenhum dispositivo se as permissões ainda não foram concedidas. Inicialmente, o nome do dispositivo está vazio e não contém informações detalhadas do dispositivo. Você precisa chamar DeviceManager.askPermission(), que solicitará ao usuário o acesso ao dispositivo. Quando o usuário permite o acesso, o gerenciador de dispositivos obtém informações sobre os dispositivos no sistema, atualiza as listas de dispositivos e envia os eventos audioDevicesUpdated e videoDevicesUpdated. Se um usuário atualizar a página e criar um gerenciador de dispositivos, o gerenciador de dispositivos aprenderá sobre dispositivos porque o usuário concedeu acesso anteriormente. Ele tem suas listas de dispositivos preenchidas inicialmente e não emite audioDevicesUpdated nem videoDevicesUpdated eventos.
  • Não há suporte para enumeração/seleção do locutor no Android Chrome, no iOS Safari nem no macOS Safari.

Realizar uma chamada com a câmera de vídeo

Importante

Atualmente, há suporte para somente uma transmissão de vídeo local de saída.

Para fazer uma chamada de vídeo, você precisa enumerar as câmeras locais usando o método getCameras() em deviceManager.

Depois de selecionar uma câmera, use-a para construir uma LocalVideoStream instância. Passe-o dentro de videoOptions como um item dentro da matriz localVideoStream para o 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);
  • Você também pode ingressar em uma videochamada com a API CallAgent.join() e aceitar e fazer chamadas com vídeo usando a API Call.Accept().
  • Quando a sua chamada for conectada, ela começará automaticamente o envio de um fluxo de vídeo da câmera selecionada para os outro participante.

Iniciar e parar de enviar vídeo local durante uma chamada

Iniciar vídeo

Para iniciar um vídeo durante uma chamada, você precisa enumerar as câmeras usando o método getCameras no objeto deviceManager. Em seguida, crie uma instância do LocalVideoStream com a câmera desejada e passe o objeto LocalVideoStream para o método startVideo de um objeto de chamada existente:

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

Parar Vídeo

Depois de começar a enviar vídeo com êxito, uma instância LocalVideoStream do tipo Video será adicionada à coleção localVideoStreams em uma instância de chamada.

Encontre o stream de vídeo no objeto Call

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

Interromper o vídeo local Para interromper o vídeo local durante uma chamada, passe a instância localVideoStream que está sendo usada para vídeo para o método stopVideo do Call:

await call.stopVideo(localVideoStream);

Você pode mudar para um dispositivo de câmera diferente enquanto tiver um LocalVideoStream ativo invocando switchSource nessa LocalVideoStream instância:

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

Se o dispositivo de vídeo especificado não estiver disponível:

  • Durante uma chamada, se o vídeo estiver desativado e você iniciar o vídeo usando call.startVideo(), esse método gerará um SourceUnavailableError e cameraStartFailed diagnóstico voltado para o usuário será definido como verdadeiro.
  • Uma chamada para o método localVideoStream.switchSource() fará com que cameraStartFailed seja definido como verdadeiro. Nosso guia de Diagnóstico de chamada fornece informações adicionais sobre como diagnosticar problemas relacionados à chamada.

Para verificar se o vídeo local está ligado ou desligado você pode usar o Call método isLocalVideoStarted, que retorna verdadeiro ou falso:

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

Para ouvir as alterações no vídeo local, você pode assinar e cancelar a assinatura do evento isLocalVideoStartedChanged:

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

Iniciar e interromper o compartilhamento de tela durante uma chamada

Para iniciar o compartilhamento de tela durante uma chamada, você pode usar o método assíncrono startScreenSharing() em um objeto Call:

Iniciar compartilhamento de tela

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

Observação

O envio de compartilhamento de tela só tem suporte para navegadores da área de trabalho.

Encontre o compartilhamento de tela na coleção de LocalVideoStream

Depois de iniciar o envio de compartilhamento de tela com êxito, uma LocalVideoStream instância do tipo ScreenSharing é adicionada na coleção localVideoStreams da instância de chamada.

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

Interromper o compartilhamento de tela

Para interromper o compartilhamento de tela durante uma chamada, você pode usar o stoptScreenSharing de API assíncrona:

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

Verifique o status de compartilhamento de tela

Para verificar se o compartilhamento de tela está ativado ou desativado, você pode usar a API isScreenSharingOn, que retorna verdadeiro ou falso:

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

Para ouvir as alterações no compartilhamento de tela, você pode assinar e cancelar a assinatura do evento isScreenSharingOnChanged:

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

Importante

Este recurso dos Serviços de Comunicação do Azure estão atualmente em versão prévia. Os recursos em versão prévia estão disponíveis publicamente e podem ser usados por todos os clientes novos e existentes da Microsoft.

Versões prévias das APIs e dos SDKs são fornecidas sem um contrato de nível de serviço. É recomendável que você não as use para cargas de trabalho de produção. Alguns recursos podem não ter suporte ou recursos podem ser restritos.

Para obter mais informações, consulte Termos de Uso Complementares de Versões Prévias do Microsoft Azure.

A visualização do compartilhamento de tela local está em versão prévia pública e está disponível como parte da versão 1.15.1-beta.1+.

Visualização do compartilhamento de tela local

Você pode usar um VideoStreamRenderer para começar a renderizar streams do seu compartilhamento de tela local para poder ver o que está enviando como um stream de compartilhamento de tela.

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

Exibir fluxos de vídeo/compartilhamento de tela de participantes remotos

Para renderizar um vídeo de participante remoto ou compartilhamento de tela, a primeira etapa é obter uma referência do RemoteVideoStream que você deseja renderizar.

Você só pode renderizar um participante remoto passando pela matriz ou fluxo de vídeo (videoStreams) do RemoteParticipant. A coleção de participantes remotos é acessada através do objeto Call.

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

Para renderizar o RemoteVideoStream, você deve assinar o evento isAvailableChanged. Se a propriedade isAvailable mudar para true, um participante remoto estará enviando um stream de vídeo. Depois que isso acontecer, crie uma nova instância de VideoStreamRenderer, em seguida, crie uma nova VideoStreamRendererView instância usando o método assíncrono createView . Em seguida, é possível anexar view.target a qualquer elemento de interface do usuário.

Sempre que a disponibilidade de um fluxo remoto mudar, você poderá destruir o VideoStreamRenderer inteiro ou um específico VideoStreamRendererView. Se você decidir mantê-los, a visualização exibirá um quadro de vídeo em branco.

// 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 estilizar o controle giratório de carregamento no fluxo de vídeo remoto.

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

Qualidade do vídeo remoto

O SDK do WebJS dos Serviços de Comunicação do Azure fornece um recurso chamado Contagem de Vídeo Ideal (OVC), começando na versão 1.15.1.

Use esse recurso para informar os aplicativos em tempo de execução sobre quantos vídeos de entrada de diferentes participantes podem ser renderizados de maneira ideal em um determinado momento em uma chamada em grupo de dois (2) ou mais participantes.

Esse recurso expõe uma propriedade optimalVideoCount que está mudando dinamicamente durante a chamada com base nas funcionalidades de rede e hardware de um ponto de extremidade local. O valor de optimalVideoCount detalha quantos vídeos de aplicativos de participantes diferentes devem ser renderizados em um determinado momento. Os aplicativos devem lidar com essas alterações e atualizar o número de vídeos renderizados de acordo com a recomendação. Há um período de debounce de cerca de dez (10) segundos entre cada atualização.

Uso

O optimalVideoCount recurso é um recurso de chamada. Você precisa fazer referência ao recurso OptimalVideoCount por meio do método feature do objeto Call.

Você pode então definir um ouvinte por meio do método on do OptimalVideoCountCallFeature para ser notificado quando o optimalVideoCount for alterado. Para cancelar a assinatura das alterações, você pode chamar o método off.

O número máximo atual de vídeos de entrada que podem ser renderizados é 16. Para dar suporte adequado a 16 vídeos de entrada, o computador precisa de um mínimo de 16 GB de RAM e uma CPU de quatro (4) núcleos ou maior que tenha menos de três (3) anos de idade.

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

Uso de exemplo: o seu aplicativo assina as alterações da Contagem de Vídeo Ideal em chamadas de grupo. Uma alteração na contagem ideal de vídeos é tratada ao se criar um novo método de renderização createView ou ao descartar visões dispose e atualizar o layout do aplicativo de forma adequada.

Propriedades de fluxo de vídeo remoto

Os fluxos de vídeo remotos têm as seguintes propriedades:

const id: number = remoteVideoStream.id;
  • id: O ID de um fluxo de vídeo remoto.
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
  • mediaStreamType: pode ser Video ou ScreenSharing.
const isAvailable: boolean = remoteVideoStream.isAvailable;
  • isAvailable: Define se um ponto de conexão de participante remoto está enviando um fluxo ativamente.
const isReceiving: boolean = remoteVideoStream.isReceiving;
  • isReceiving:

    • Informa ao aplicativo se os dados de fluxo de vídeo remoto estão sendo recebidos ou não.

    • A sinalização passa para false nos seguintes cenários:

      • Um participante remoto que está no navegador móvel coloca o aplicativo do navegador em segundo plano.
      • Um participante remoto ou o usuário que recebe o vídeo tem um problema de rede que afeta drasticamente a qualidade do vídeo.
      • Um participante remoto que esteja no Safari do macOS/iOS seleciona "Pausar" na barra de endereços.
      • Um participante remoto está desconectado da rede.
      • Um participante remoto no celular mata ou encerra o navegador.
      • Um participante remoto no celular ou desktop bloqueia seu dispositivo. Esse cenário também se aplica se o participante remoto estiver em um computador desktop e entrar em suspensão.
    • A sinalização passa para true nos seguintes cenários:

      • Um participante remoto que esteja no navegador móvel e tenha colocado seu navegador em segundo plano o ativa novamente.
      • Um participante remoto que está no macOS/iOS Safari seleciona Retomar na barra de endereços após pausar o vídeo.
      • Um participante remoto se reconecta à rede após uma desconexão temporária.
      • Um participante remoto no celular desbloqueia seu dispositivo e retorna à chamada no navegador do celular.
    • Esse recurso aprimora a experiência do usuário para renderizar streamings de vídeo remotos.

    • Você poderá exibir um controle giratório de carregamento no streaming de vídeo remoto quando o sinalizador isReceiving mudar para false. Você não precisa implementar o botão giratório de carregamento, mas um botão giratório de carregamento é o uso mais comum para uma melhor experiência do usuário.

    const size: StreamSize = remoteVideoStream.size;
    
  • size: O tamanho do stream com informações sobre a largura e altura do vídeo.

Métodos e propriedades do VideoStreamRenderer

await videoStreamRenderer.createView();

Crie uma instância VideoStreamRendererView que pode ser anexada na UI da aplicação para renderizar o stream de vídeo remoto, use o método assíncrono createView(), ele resolve quando o stream está pronto para renderizar e retorna um objeto com target propriedade que representa video o elemento que pode ser inserido em qualquer lugar do Árvore DOM.

videoStreamRenderer.dispose();

Descarte videoStreamRenderer e todas as instâncias VideoStreamRendererView associadas.

Métodos e propriedades do VideoStreamRendererView

Ao criar um VideoStreamRendererView, você pode especificar as propriedades scalingMode e isMirrored. scalingMode pode ser Stretch, Crop, ou Fit. Se isMirrored for especificado, o fluxo renderizado será invertido verticalmente.

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

Cada instância VideoStreamRendererView específica tem uma propriedade target que representa a superfície de renderização. Anexe esta propriedade na interface do usuário do aplicativo:

htmlElement.appendChild(view.target);

É possível atualizar oscalingMode invocando o método updateScalingMode:

view.updateScalingMode('Crop');

Envie fluxos de vídeo de duas câmeras diferentes, na mesma chamada do mesmo dispositivo de área de trabalho.

Importante

Este recurso dos Serviços de Comunicação do Azure estão atualmente em versão prévia. Os recursos em versão prévia estão disponíveis publicamente e podem ser usados por todos os clientes novos e existentes da Microsoft.

Versões prévias das APIs e dos SDKs são fornecidas sem um contrato de nível de serviço. É recomendável que você não as use para cargas de trabalho de produção. Alguns recursos podem não ter suporte ou recursos podem ser restritos.

Para obter mais informações, consulte Termos de Uso Complementares de Versões Prévias do Microsoft Azure.

O envio de streams de vídeo de duas câmeras diferentes na mesma chamada é compatível como parte da versão 1.17.1-beta.1+ em navegadores compatíveis com desktop.

Você pode enviar transmissões de vídeo de duas câmeras diferentes a partir de uma única guia/aplicativo do navegador da área de trabalho, na mesma chamada, com o seguinte trecho 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();

Limitações:

  • O envio de fluxos de vídeo deve ser feito com duas instâncias diferentes CallAgent usando identidades diferentes. O trecho de código mostra dois agentes de chamada sendo usados, cada um com seu próprio objeto Call.
  • No exemplo de código, ambos os CallAgents estão ingressando na mesma chamada (mesmas IDs de chamada). Você também pode participar de chamadas diferentes com cada agente e enviar um vídeo em uma das chamadas e um vídeo diferente em outra chamada.
  • Não há suporte para o envio da mesma câmera em ambos os CallAgents. Devem ser duas câmeras diferentes.
  • No momento, não há suporte para o envio de duas câmeras diferentes com um CallAgent.
  • No macOS Safari, efeitos de vídeo de desfoque de plano de fundo (de @azure/communication-effects), só podem ser aplicados a uma câmera e não a ambos ao mesmo tempo.

Instalar o SDK

Localize o arquivo build.gradle de nível do projeto e adicione mavenCentral() à lista de repositórios em buildscript e allprojects:

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

Em seguida, no arquivo build.gradle no nível do módulo, adicione as seguintes linhas à seção dependencies:

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

Inicializar objetos necessários

Para criar uma instância CallAgent, você precisa chamar o método createCallAgent em uma instância CallClient. Essa chamada retorna de forma assíncrona um objeto de instância CallAgent.

O método createCallAgent usa CommunicationUserCredential como argumento, que encapsula um token de acesso.

Para acessar DeviceManager, você deverá criar uma instância callAgent primeiro. Em seguida, você poderá usar o método CallClient.getDeviceManager para obter 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 definir um nome de exibição para o chamador, 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();

Gerenciamento de dispositivos

Para usar o vídeo com Chamada, você precisa gerenciar dispositivos. O uso de dispositivos permite controlar o que transmite Áudio e Vídeo para a chamada.

O DeviceManager objeto permite enumerar dispositivos locais a serem usados em uma chamada para transmitir seus fluxos de áudio/vídeo. Ele também permite que você solicite permissão de um usuário para acessar o microfone e a câmera usando a API do navegador nativo.

Para acessar deviceManager, chame o callClient.getDeviceManager() método.

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

Enumerar dispositivos locais

Para acessar dispositivos locais, use métodos de enumeração no Gerenciador de Dispositivos. A enumeração é uma ação síncrona.

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

Visualização da câmera local

Você pode usar DeviceManager e Renderer para começar a renderizar fluxos da sua câmera local. Esse fluxo não é enviado para outros participantes. É um feed de visualização local. Renderizar um fluxo é uma ação assíncrona.

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

Realizar uma chamada 1:1 com a câmera de vídeo

Aviso

Atualmente, há suporte para somente uma transmissão de vídeo local de saída. Para fazer uma chamada com vídeo, você deve enumerar câmeras locais usando a deviceManagergetCameras API. Depois de selecionar uma câmera, use-a para construir uma instância de LocalVideoStream e passá-la para videoOptions como um item na matriz localVideoStream para um método call. Depois que a chamada se conecta, ela começa automaticamente a enviar um fluxo de vídeo da câmera selecionada para outros participantes.

Observação

Devido a questões de privacidade, o vídeo não será compartilhado durante a chamada se não for visualizado localmente. Para obter mais informações, confira Visualização da Câmera local.

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

Iniciar e parar o envio do vídeo local

Para iniciar um vídeo, você deve enumerar câmeras usando a getCameraList operação no deviceManager objeto. Em seguida, crie uma nova instância de LocalVideoStream passando a câmera desejada e passe-a na API startVideo como um 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();

Depois que você começar a enviar vídeo com êxito, uma instância LocalVideoStream será adicionada à coleção localVideoStreams na instância de chamada.

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

Para parar o vídeo local, passe a instância LocalVideoStream disponível na coleção localVideoStreams:

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

Você poderá alternar para um dispositivo de câmera diferente enquanto o vídeo estiver sendo enviado invocando switchSource em uma instância LocalVideoStream:

currentLocalVideoStream.switchSource(source).get();

Exibir fluxos de vídeo de participantes remotos

Para listar os fluxos de vídeo e os fluxos de compartilhamento de tela de participantes remotos, inspecione as coleções 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 renderizar um RemoteVideoStream de um participante remoto, você precisa assinar um evento OnVideoStreamsUpdated.

Dentro do evento, a alteração da propriedade isAvailable para true indica que o participante remoto está enviando um fluxo no momento. Quando isso acontecer, crie uma instância de um Renderer, crie um RendererView usando a API createView assíncrona e anexe view.target em qualquer lugar da interface do usuário do seu aplicativo.

Sempre que a disponibilidade de um fluxo remoto muda, você pode optar por destruir o Renderer inteiro, um específico RendererView ou mantê-los, mas isso resulta na exibição de um quadro de vídeo em branco.

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

Propriedades de fluxo de vídeo remoto

O fluxo de vídeo remoto tem as seguintes propriedades:

  • Id - ID de um fluxo de vídeo remoto.

    int id = remoteVideoStream.getId();
    
  • MediaStreamType - Pode ser Video ou ScreenSharing.

    MediaStreamType type = remoteVideoStream.getMediaStreamType();
    
  • isAvailable - Indica se o endpoint do participante remoto está enviando stream ativamente.

    boolean availability = remoteVideoStream.isAvailable();
    

Métodos e propriedades do renderizador

O Renderer objeto usa os métodos a seguir.

  • Para renderizar o fluxo de vídeo remoto, crie uma VideoStreamRendererView instância que pode ser anexada posteriormente na interface do usuário do aplicativo.

    // Create a view for a video stream
    VideoStreamRendererView.createView()
    
  • Descarte o renderizador e todos os VideoStreamRendererView associados a esse renderizador. Chame depois de remover todas as exibições associadas da interface do usuário.

    VideoStreamRenderer.dispose()
    
  • Para definir o tamanho (largura/altura) de um fluxo de vídeo remoto, use StreamSize.

    StreamSize renderStreamSize = VideoStreamRenderer.getSize();
    int width = renderStreamSize.getWidth();
    int height = renderStreamSize.getHeight();
    

Métodos e propriedades do RendererView

Ao criar um VideoStreamRendererView, você pode especificar as propriedades ScalingMode e mirrored que se aplicam a essa exibição.

O modo de dimensionamento pode ser um dos CROP ou FIT.

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

O RendererView criado pode então ser anexado à interface do usuário do aplicativo usando o seguinte snippet:

layout.addView(rendererView);

Posteriormente, você pode atualizar o modo de dimensionamento usando a updateScalingMode operação no objeto RendererView com um argumento de ScalingMode.CROP ou ScalingMode.FIT.

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

Configure seu sistema

Siga essas etapas para configurar seu sistema.

Criar o projeto do Xcode

No Xcode, crie um projeto do iOS e selecione o modelo Aplicativo de Modo de Exibição Único. Como este artigo usa a estrutura SwiftUI, defina Linguagem como Swift e Interface como SwiftUI.

Você não criará testes neste artigo. Fique à vontade para limpar a caixa de seleção Incluir Testes.

Captura de tela que mostra a janela para a criação de um projeto no Xcode.

Instalar o pacote e as dependências usando o CocoaPods

  1. Crie um Podfile para seu aplicativo, como este exemplo:

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

  3. Abra o .xcworkspace usando o Xcode.

Solicitar acesso ao microfone

Para acessar o microfone do dispositivo, você precisa atualizar a lista de propriedades de informações do aplicativo usando NSMicrophoneUsageDescription. Defina o valor associado como uma cadeia de caracteres incluída na caixa de diálogo que é usada pelo sistema para solicitar o acesso do usuário.

Clique com o botão direito do mouse na entrada Info.plist da árvore de projeto e selecione Abrir Como>Código-Fonte. Adicione as linhas a seguir na seção do nível superior<dict>e, em seguida, salve o arquivo.

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

Configurar o framework de aplicativos

Abra o arquivo ContentView.swift do projeto. Adicione uma declaração import à parte superior do arquivo para importar a biblioteca AzureCommunicationCalling. Além disso, importeAVFoundation. Você precisa dele para solicitações de permissão de áudio no código.

import AzureCommunicationCalling
import AVFoundation

Inicialização do CallAgent

Para criar uma CallAgent instância de CallClient, você precisa usar um método callClient.createCallAgent que retorne um objeto CallAgent de modo assíncrono depois que ele for inicializado.

Para criar um cliente de chamada, passe um 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)
}

Transmita o objeto CommunicationTokenCredential que você criou para CallClient e defina o nome de exibição:

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

Gerenciar dispositivos

Para começar a usar o vídeo com a Chamada, você precisa saber como gerenciar dispositivos. Os dispositivos permitem controlar o que transmite Áudio e Vídeo para a chamada.

DeviceManagerPermite que você enumere os dispositivos locais que podem ser usados em uma chamada para transmitir os fluxos de áudio/vídeo. Ele também permite que você solicite permissão de um usuário para acessar um microfone ou câmera. Você pode acessar deviceManager no callClient objeto.

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

Enumerar dispositivos locais

Para acessar os dispositivos locais, você pode usar métodos de enumeração no Gerenciador de Dispositivos. A enumeração é uma ação síncrona.

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

Obter uma prévia da câmera local

Você pode usar o Renderer para começar a renderizar um fluxo da sua câmera local. Esse fluxo não é para outros participantes; é um feed de visualização local. Essa é uma ação assíncrona.

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

Obter as propriedades da pré- visualização da câmera local

O renderizador inclui um conjunto de propriedades e métodos que permitem controlar a renderização.

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

Fazer uma chamada 1:1 com vídeo

Para obter uma instância do gerenciador de dispositivos, consulte a seção sobregerenciar 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")
    }
}

Exibir fluxos de vídeo de participantes remotos

Os participantes remotos podem iniciar a chamda de vídeo ou o compartilhamento de tela durante uma chamada.

O Identificador do compartilhamento de vídeo ou fluxos de compartilhamento de tela dos participantes remotos

Para listar os fluxos dos participantes remotos, inspecione asvideoStreamscoleções de.

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

Obter as propriedades de fluxo de vídeo remoto

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

Exibir os fluxos de participantes remotos

Para iniciar a renderização de fluxos de participantes remotos, use o seguinte código.

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)

Obetr os métodos e propriedades do renderizador de vídeo remoto

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

Configure seu sistema

Siga essas etapas para configurar seu sistema.

Criar o projeto do Visual Studio

Para um aplicativo da Plataforma Universal do Windows, no Visual Studio 2022, crie um projeto Aplicativo em Branco (Universal do Windows). Depois de inserir o nome do projeto, fique à vontade para escolher qualquer SDK do Windows posterior a 10.0.17763.0.

Para um aplicativo do WinUI 3, crie um projeto com o modelo Aplicativo em Branco, Empacotado (WinUI 3 na Área de Trabalho) para configurar um aplicativo do WinUI 3 de página única. O SDK do Aplicativo do Windows versão 1.3 ou posterior é necessário.

Instalar o pacote e as dependências usando o Gerenciador de Pacotes do NuGet

As bibliotecas e as APIs do SDK de Chamada estão disponíveis publicamente por meio de um pacote NuGet.

Para localizar, baixar e instalar o pacote Calling SDK NuGet, faça o seguinte:

  1. Abra o Gerenciador de Pacotes do NuGet selecionando Ferramentas>Gerenciador de Pacotes do NuGet>Gerenciar de Pacotes do NuGet para Solução.
  2. Selecione Procurar e insira Azure.Communication.Calling.WindowsClient na caixa de pesquisa.
  3. Verifique se a caixa de seleção Incluir pré-lançamento está marcada.
  4. Selecione o pacote Azure.Communication.Calling.WindowsClient e, em seguida, selecione Azure.Communication.Calling.WindowsClient1.4.0-beta.1 ou uma versão mais recente.
  5. Marque a caixa de seleção que corresponde ao projeto dos Serviços de Comunicação do Azure no painel direito.
  6. Selecione Instalar.

Solicitar acesso ao microfone

O aplicativo requer acesso à câmera. Em aplicativos da Plataforma Universal do Windows (UWP), você precisa declarar a funcionalidade da câmera no arquivo de manifesto do aplicativo.

  1. Abra o projeto no Visual Studio.
  2. No painel Gerenciador de Soluções , clique duas vezes no arquivo com .appxmanifest extensão.
  3. Clique na guia Recursos .
  4. Marque a caixa de seleção Camera na lista de recursos.

Criar botões de interface do usuário para iniciar e desligar a chamada

Este aplicativo de exemplo contém dois botões. Um para iniciar a chamada e outro para desligar uma chamada iniciada.

  1. No painel Gerenciador de Soluções , clique duas vezes no arquivo nomeado MainPage.xaml para UWP ou MainWindows.xaml no WinUI 3.
  2. No painel central, procure o código XAML na versão prévia da interface do usuário.
  3. Modifique o código XAML usando o seguinte trecho:
<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>

Configuração do aplicativo com APIs do SDK de chamada

As APIs do SDK de chamada estão em dois namespaces diferentes.

Conclua as etapas a seguir para informar o compilador C# sobre esses namespaces, permitindo que o Intellisense do Visual Studio ajude no desenvolvimento de código.

  1. No painel Gerenciador de Soluções , clique na seta no lado esquerdo do arquivo nomeado MainPage.xaml para UWP ou MainWindows.xaml no WinUI 3.
  2. Clique duas vezes no arquivo nomeado MainPage.xaml.cs ou MainWindows.xaml.cs.
  3. Adicione os comandos a seguir na parte inferior das instruções using atuais.
using Azure.Communication.Calling.WindowsClient;

Mantenha MainPage.xaml.cs ou MainWindows.xaml.cs aberto. A próxima etapa adiciona mais código.

Habilitar interações de aplicativo

Os botões de interface do usuário que adicionamos precisam operar em cima de uma CommunicationCall realizada. Isso significa que você deve adicionar um CommunicationCall membro de dados à classe MainPage ou MainWindow. Você também precisa habilitar a operação assíncrona criando CallAgent para ter sucesso. Adicione um CallAgent membro de dados à mesma classe.

Adicione os seguintes membros de dados à classe MainPage ou MainWindow:

CallAgent callAgent;
CommunicationCall call;

Criar manipuladores de botão

Anteriormente, adicionamos dois botões de interface do usuário ao código XAML. O código a seguir adiciona os manipuladores a serem executados quando um usuário seleciona o botão.

Adicione o código a seguir após os membros de dados da seção 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 objeto

As classes e as interfaces a seguir administram alguns dos principais recursos da biblioteca de clientes de Chamada dos Serviços de Comunicação do Azure para UWP.

Nome Descrição
CallClient O CallClient é o ponto de entrada principal para a biblioteca de clientes de Chamada.
CallAgent O CallAgent é usado para iniciar e ingressar em chamadas.
CommunicationCall O CommunicationCall é usado para gerenciar chamadas iniciadas ou ingressadas.
CommunicationTokenCredential O CommunicationTokenCredential é usado como a credencial de token para instanciar o CallAgent.
CallAgentOptions O CallAgentOptions contém informações para identificar o chamador.
HangupOptions O HangupOptions informa se uma chamada deve ser encerrada para todos os participantes.

Registrar manipulador de esquema de vídeo

Um componente de IU, como o MediaElement ou MediaPlayerElement do XAML, exige que o aplicativo registre uma configuração para renderizar feeds de vídeo locais e remotos.

Adicione o seguinte conteúdo entre as tags Package do Package.appxmanifest:

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

Inicializar o CallAgent

Para criar uma instância CallAgent de CallClient, você deve usar o método CallClient.CreateCallAgentAsync que retorna de forma assíncrona um objeto CallAgent quando ele for inicializado.

Para criar CallAgent, você deve passar um objeto CallTokenCredential e um objeto CallAgentOptions. Tenha em mente que CallTokenCredential é lançado se um token malformado for passado.

Adicione o código a seguir dentro e a função auxiliar para que ele seja executado durante a inicialização.

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;

Altere o <AUTHENTICATION_TOKEN> com um token de credencial válido para o recurso. Para obter mais informações sobre como fornecer um token de credencial, consulte o token de acesso do usuário.

Realizar uma chamada 1:1 com a câmera de vídeo

Os objetos necessários para criar um CallAgent estão agora prontos. Em seguida, crie CallAgent e faça uma chamada de vídeo de forma assíncrona.

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 GetStartCallOptionsAsync();
    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 LocalOutgoingVideoStream(this.viceManager.Cameras.FirstOrDefault() as VideoDeviceDetails); // Create a default video stream

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

Visualização da câmera local

Opcionalmente, é possível configurar a versão prévia da câmera local. Você pode renderizar o vídeo por meio de MediaPlayerElement.

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

Para inicializar a versão prévia 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 selectedCamera = CameraList.SelectedItem as VideoDeviceDetails;
    cameraStream = new LocalOutgoingVideoStream(selectedCamera);

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

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

Renderizar o fluxo remoto de câmera

Configure o manipulador em resposta ao 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 a renderização do fluxo de vídeo remoto no 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;
    }
}

Encerrar uma chamada

Depois que uma chamada for feita, use o HangupAsync método do CommunicationCall objeto para desligar a chamada.

Use uma instância de HangupOptions para informar os participantes se a chamada deve ser encerrada.

Adicione o código a seguir dentro 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;
            }
        }
    }
}

Executar o código

  1. Verifique se o Visual Studio cria o aplicativo para x64, x86ou ARM64.
  2. Pressione F5 para começar a executar o aplicativo.
  3. Clique no botão CommunicationCall para fazer uma chamada ao destinatário definido.

Na primeira vez que o aplicativo é executado, o sistema solicita que o usuário conceda acesso ao microfone.

Próximas etapas