Guia de início rápido: adicionar chamada de vídeo individual como usuário do Teams ao seu aplicativo

Comece a usar os Serviços de Comunicação do Azure com o SDK de Chamada dos Serviços de Comunicação para adicionar uma chamada de voz e vídeo individual ao seu aplicativo. Você aprenderá como iniciar e responder a uma chamada usando os Serviços de Comunicação do Azure que chamam o SDK do JavaScript.

Exemplo de código

Para pular para o final, baixe este guia de início rápido como um exemplo no GitHub.

Pré-requisitos

Configurando

Criar um novo aplicativo do Node.js

Abra o terminal ou a janela de comando Criar um diretório para seu aplicativo e navegue até o diretório.

mkdir calling-quickstart && cd calling-quickstart

Execute npm init -y para criar um arquivo package.json com as configurações padrão.

npm init -y

Instalar o pacote

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

Importante

Este início rápido usa a última versão do SDK de Chamada dos Serviços de Comunicação do Azure.

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

Configurar o framework de aplicativos

Este guia de início rápido usa o webpack para agrupar os ativos do aplicativo. Execute o seguinte comando para instalar os pacotes npm webpack, webpack-cli e webpack-dev-server e listá-los como dependências de desenvolvimento no package.json:

npm install copy-webpack-plugin@^11.0.0 webpack@^5.88.2 webpack-cli@^5.1.4 webpack-dev-server@^4.15.1 --save-dev

Crie um arquivo index.html no diretório raiz do projeto. Usaremos esse arquivo para configurar um layout básico que permitirá que o usuário faça uma chamada de vídeo 1:1.

O código é o seguinte:

<!-- index.html -->
<!DOCTYPE html>
<html>
    <head>
        <title>Azure Communication Services - Teams Calling Web Application</title>
    </head>
    <body>
        <h4>Azure Communication Services - Teams Calling Web Application</h4>
        <input id="user-access-token"
            type="text"
            placeholder="User access token"
            style="margin-bottom:1em; width: 500px;"/>
        <button id="initialize-teams-call-agent" type="button">Login</button>
        <br>
        <br>
        <input id="callee-teams-user-id"
            type="text"
            placeholder="Microsoft Teams callee's id (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)"
            style="margin-bottom:1em; width: 500px; display: block;"/>
        <button id="start-call-button" type="button" disabled="true">Start Call</button>
        <button id="hangup-call-button" type="button" disabled="true">Hang up Call</button>
        <button id="accept-call-button" type="button" disabled="true">Accept Call</button>
        <button id="start-video-button" type="button" disabled="true">Start Video</button>
        <button id="stop-video-button" type="button" disabled="true">Stop Video</button>
        <br>
        <br>
        <div id="connectedLabel" style="color: #13bb13;" hidden>Call is connected!</div>
        <br>
        <div id="remoteVideoContainer" style="width: 40%;" hidden>Remote participants' video streams:</div>
        <br>
        <div id="localVideoContainer" style="width: 30%;" hidden>Local video stream:</div>
        <!-- points to the bundle generated from client.js -->
        <script src="./main.js"></script>
    </body>
</html>

Modelo de objeto do SDK Web de chamada de Serviços de Comunicação do Azure

As seguintes classes e interfaces cuidam de alguns dos principais recursos do SDK de Chamada dos Serviços de Comunicação do Azure:

Nome Descrição
CallClient O ponto de entrada principal para o SDK de Chamada.
AzureCommunicationTokenCredential Implementa a interfaceCommunicationTokenCredential, que é usada para instanciarteamsCallAgent.
TeamsCallAgent Usado para iniciar e gerenciar chamadas do Teams.
DeviceManager Usado para gerenciar dispositivos de mídia.
TeamsCall Usado para representar uma Chamada do Teams
LocalVideoStream Usado para criar um fluxo de vídeo local para um dispositivo de câmera no sistema local.
RemoteParticipant Usado para representar um participante remoto na Chamada
RemoteVideoStream Usado para representar um fluxo de vídeo remoto de um Participante Remoto.

Crie um arquivo no diretório raiz do projeto chamado index.js para conter a lógica do aplicativo deste guia de início rápido. Adicione o seguinte código ao index.js:

// Make sure to install the necessary dependencies
const { CallClient, VideoStreamRenderer, LocalVideoStream } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential } = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");
// Set the log level and output
setLogLevel('verbose');
AzureLogger.log = (...args) => {
    console.log(...args);
};
// Calling web sdk objects
let teamsCallAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let calleeTeamsUserId = document.getElementById('callee-teams-user-id');
let initializeCallAgentButton = document.getElementById('initialize-teams-call-agent');
let startCallButton = document.getElementById('start-call-button');
let hangUpCallButton = document.getElementById('hangup-call-button');
let acceptCallButton = document.getElementById('accept-call-button');
let startVideoButton = document.getElementById('start-video-button');
let stopVideoButton = document.getElementById('stop-video-button');
let connectedLabel = document.getElementById('connectedLabel');
let remoteVideoContainer = document.getElementById('remoteVideoContainer');
let localVideoContainer = document.getElementById('localVideoContainer');
/**
 * Create an instance of CallClient. Initialize a TeamsCallAgent instance with a CommunicationUserCredential via created CallClient. TeamsCallAgent enables us to make outgoing calls and receive incoming calls. 
 * You can then use the CallClient.getDeviceManager() API instance to get the DeviceManager.
 */
initializeCallAgentButton.onclick = async () => {
    try {
        const callClient = new CallClient(); 
        tokenCredential = new AzureCommunicationTokenCredential(userAccessToken.value.trim());
        teamsCallAgent = await callClient.createTeamsCallAgent(tokenCredential)
        // Set up a camera device to use.
        deviceManager = await callClient.getDeviceManager();
        await deviceManager.askDevicePermission({ video: true });
        await deviceManager.askDevicePermission({ audio: true });
        // Listen for an incoming call to accept.
        teamsCallAgent.on('incomingCall', async (args) => {
            try {
                incomingCall = args.incomingCall;
                acceptCallButton.disabled = false;
                startCallButton.disabled = true;
            } catch (error) {
                console.error(error);
            }
        });
        startCallButton.disabled = false;
        initializeCallAgentButton.disabled = true;
    } catch(error) {
        console.error(error);
    }
}
/**
 * Place a 1:1 outgoing video call to a user
 * Add an event listener to initiate a call when the `startCallButton` is selected.
 * Enumerate local cameras using the deviceManager `getCameraList` API.
 * In this quickstart, we're using the first camera in the collection. Once the desired camera is selected, a
 * LocalVideoStream instance will be constructed and passed within `videoOptions` as an item within the
 * localVideoStream array to the call method. When the call connects, your application will be sending a video stream to the other participant. 
 */
startCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = teamsCallAgent.startCall({ microsoftTeamsUserId: calleeTeamsUserId.value.trim() }, { videoOptions: videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
/**
 * Accepting an incoming call with a video
 * Add an event listener to accept a call when the `acceptCallButton` is selected.
 * You can accept incoming calls after subscribing to the `TeamsCallAgent.on('incomingCall')` event.
 * You can pass the local video stream to accept the call with the following code.
 */
acceptCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = await incomingCall.accept({ videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a call obj.
// Listen for property changes and collection udpates.
subscribeToCall = (call) => {
    try {
        // Inspect the initial call.id value.
        console.log(`Call Id: ${call.id}`);
        //Subsribe to call's 'idChanged' event for value changes.
        call.on('idChanged', () => {
            console.log(`Call ID changed: ${call.id}`); 
        });
        // Inspect the initial call.state value.
        console.log(`Call state: ${call.state}`);
        // Subscribe to call's 'stateChanged' event for value changes.
        call.on('stateChanged', async () => {
            console.log(`Call state changed: ${call.state}`);
            if(call.state === 'Connected') {
                connectedLabel.hidden = false;
                acceptCallButton.disabled = true;
                startCallButton.disabled = true;
                hangUpCallButton.disabled = false;
                startVideoButton.disabled = false;
                stopVideoButton.disabled = false;
            } else if (call.state === 'Disconnected') {
                connectedLabel.hidden = true;
                startCallButton.disabled = false;
                hangUpCallButton.disabled = true;
                startVideoButton.disabled = true;
                stopVideoButton.disabled = true;
                console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
            }   
        });
        call.on('isLocalVideoStartedChanged', () => {
            console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
        });
        console.log(`isLocalVideoStarted: ${call.isLocalVideoStarted}`);
        call.localVideoStreams.forEach(async (lvs) => {
            localVideoStream = lvs;
            await displayLocalVideoStream();
        });
        call.on('localVideoStreamsUpdated', e => {
            e.added.forEach(async (lvs) => {
                localVideoStream = lvs;
                await displayLocalVideoStream();
            });
            e.removed.forEach(lvs => {
               removeLocalVideoStream();
            });
        });
        
        // Inspect the call's current remote participants and subscribe to them.
        call.remoteParticipants.forEach(remoteParticipant => {
            subscribeToRemoteParticipant(remoteParticipant);
        });
        // Subscribe to the call's 'remoteParticipantsUpdated' event to be
        // notified when new participants are added to the call or removed from the call.
        call.on('remoteParticipantsUpdated', e => {
            // Subscribe to new remote participants that are added to the call.
            e.added.forEach(remoteParticipant => {
                subscribeToRemoteParticipant(remoteParticipant)
            });
            // Unsubscribe from participants that are removed from the call
            e.removed.forEach(remoteParticipant => {
                console.log('Remote participant removed from the call.');
            });
        });
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a remote participant obj.
// Listen for property changes and collection udpates.
subscribeToRemoteParticipant = (remoteParticipant) => {
    try {
        // Inspect the initial remoteParticipant.state value.
        console.log(`Remote participant state: ${remoteParticipant.state}`);
        // Subscribe to remoteParticipant's 'stateChanged' event for value changes.
        remoteParticipant.on('stateChanged', () => {
            console.log(`Remote participant state changed: ${remoteParticipant.state}`);
        });
        // Inspect the remoteParticipants's current videoStreams and subscribe to them.
        remoteParticipant.videoStreams.forEach(remoteVideoStream => {
            subscribeToRemoteVideoStream(remoteVideoStream)
        });
        // Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
        // notified when the remoteParticiapant adds new videoStreams and removes video streams.
        remoteParticipant.on('videoStreamsUpdated', e => {
            // Subscribe to newly added remote participant's video streams.
            e.added.forEach(remoteVideoStream => {
                subscribeToRemoteVideoStream(remoteVideoStream)
            });
            // Unsubscribe from newly removed remote participants' video streams.
            e.removed.forEach(remoteVideoStream => {
                console.log('Remote participant video stream was removed.');
            })
        });
    } catch (error) {
        console.error(error);
    }
}
/**
 * Subscribe to a remote participant's remote video stream obj.
 * You have to subscribe to the 'isAvailableChanged' event to render the remoteVideoStream. If the 'isAvailable' property
 * changes to 'true' a remote participant is sending a stream. Whenever the availability of a remote stream changes
 * you can choose to destroy the whole 'Renderer' a specific 'RendererView' or keep them. Displaying RendererView without a video stream will result in a blank video frame. 
 */
subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    // Create a video stream renderer for the remote video stream.
    let videoStreamRenderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    const renderVideo = async () => {
        try {
            // Create a renderer view for the remote video stream.
            view = await videoStreamRenderer.createView();
            // Attach the renderer view to the UI.
            remoteVideoContainer.hidden = false;
            remoteVideoContainer.appendChild(view.target);
        } catch (e) {
            console.warn(`Failed to createView, reason=${e.message}, code=${e.code}`);
        }	
    }
    
    remoteVideoStream.on('isAvailableChanged', async () => {
        // Participant has switched video on.
        if (remoteVideoStream.isAvailable) {
            await renderVideo();
        // Participant has switched video off.
        } else {
            if (view) {
                view.dispose();
                view = undefined;
            }
        }
    });
    // Participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        await renderVideo();
    }
}
// Start your local video stream.
// This will send your local video stream to remote participants so they can view it.
startVideoButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        await call.startVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
// Stop your local video stream.
// This will stop your local video stream from being sent to remote participants.
stopVideoButton.onclick = async () => {
    try {
        await call.stopVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
/**
 * To render a LocalVideoStream, you need to create a new instance of VideoStreamRenderer, and then
 * create a new VideoStreamRendererView instance using the asynchronous createView() method.
 * You may then attach view.target to any UI element. 
 */
// Create a local video stream for your camera device
createLocalVideoStream = async () => {
    const camera = (await deviceManager.getCameras())[0];
    if (camera) {
        return new LocalVideoStream(camera);
    } else {
        console.error(`No camera device found on the system`);
    }
}
// Display your local video stream preview in your UI
displayLocalVideoStream = async () => {
    try {
        localVideoStreamRenderer = new VideoStreamRenderer(localVideoStream);
        const view = await localVideoStreamRenderer.createView();
        localVideoContainer.hidden = false;
        localVideoContainer.appendChild(view.target);
    } catch (error) {
        console.error(error);
    } 
}
// Remove your local video stream preview from your UI
removeLocalVideoStream = async() => {
    try {
        localVideoStreamRenderer.dispose();
        localVideoContainer.hidden = true;
    } catch (error) {
        console.error(error);
    } 
}
// End the current call
hangUpCallButton.addEventListener("click", async () => {
    // end the current call
    await call.hangUp();
});

Adicionar o código do servidor local do webpack

Crie um arquivo no diretório-raiz do projeto chamado webpack.config.js para conter a lógica do servidor local deste início rápido. Adicione o seguinte código ao webpack.config.js:

const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
    mode: 'development',
    entry: './index.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
    },
    devServer: {
        static: {
            directory: path.join(__dirname, './')
        },
    },
    plugins: [
        new CopyPlugin({
            patterns: [
                './index.html'
            ]
        }),
    ]
};

Executar o código

Use webpack-dev-server para criar e executar o seu aplicativo. Execute o seguinte comando para empacotar o host de aplicativos em um servidor Web local:

`npx webpack serve --config webpack.config.js`

Abra o navegador e, em duas guias, navegue até http://localhost:8080/. As guias devem mostrar um resultado semelhante à seguinte imagem: Captura de tela com duas guia na exibição padrão. Cada guia será usada por um usuário do Teams diferente.

Na primeira guia, insira um token de acesso de usuário válido. Na segunda guia, insira outro token de acesso de usuário válido diferente. Confira a documentação do token de acesso do usuário se você ainda não tem tokens de acesso disponíveis para uso. Em ambas as guias, clique nos botões "inicializar Agente de Chamada". As guias devem mostrar o resultado semelhante à seguinte imagem: Captura de tela com etapas para inicializar cada usuário do Teams na guia do navegador.

Na primeira guia, insira a identidade do usuário dos Serviços de Comunicação do Azure da segunda guia e selecione o botão "Iniciar chamada". A primeira guia iniciará a chamada de saída para a segunda guia, e o botão da segunda guia "Aceitar Chamada" será habilitado: Captura de tela mostrando a experiência quando os usuários do Teams inicializam o SDK, as etapas para iniciar uma chamada para o segundo usuário e a forma de aceitar a chamada.

Na segunda guia, selecione o botão "Aceitar Chamada". A chamada será atendida e conectada. As guias devem mostrar um resultado semelhante à seguinte imagem: Captura de tela com duas guias, com uma chamada em andamento entre dois usuários do Teams, cada um conectado em uma guia individual.

Ambas as guias agora são bem-sucedidas em uma chamada de vídeo individual. Ambos os usuários podem ouvir o áudio e ver a transmissão de vídeo do outro.

Comece a usar os Serviços de Comunicação do Azure com o SDK de Chamada dos Serviços de Comunicação para adicionar uma chamada de voz e vídeo individual ao seu aplicativo. Você aprenderá a iniciar e atender uma chamada usando o SDK de Chamada dos Serviços de Comunicação do Azure para Windows.

Exemplo de código

Para pular para o final, baixe este guia de início rápido como um exemplo no GitHub.

Pré-requisitos

Para concluir este tutorial, você precisará dos seguintes pré-requisitos:

Configurando

Criação do projeto

No Visual Studio, crie um novo projeto com o modelo Aplicativo em branco (Universal do Windows) para configurar um aplicativo UWP (Plataforma Universal do Windows de página única).

Captura de tela mostrando a janela Novo projeto UWP no Visual Studio.

Instalar o pacote

Selecione seu projeto com o botão direito e vá para Manage Nuget Packages para instalar Azure.Communication.Calling.WindowsClient 1.2.0-beta.1 ou superior. Certifique-se de que Incluir Pré-lançamento esteja marcado.

Solicitar acesso

Vá para Package.appxmanifest e selecione Capabilities. Verifique Internet (Client) e Internet (Client & Server) para obter acesso de entrada e saída à internet. Verifique Microphone para acessar o feed de áudio do microfone e Webcam para acessar o feed de vídeo da câmera.

Captura de tela mostrando a solicitação de acesso à internet e ao microfone do Visual Studio.

Configurar o framework de aplicativos

Precisamos configurar um layout básico para anexar nossa lógica. Para fazer uma chamada de saída, precisamos de um TextBox para fornecer a ID de usuário do receptor. Também precisamos de um botão Start/Join call e de um botão Hang up. Uma caixa de seleção Mute e BackgroundBlur também são incluídas neste exemplo para demonstrar os recursos de estados de áudio e efeitos de vídeo.

Abra o MainPage.xaml do seu projeto e adicione o nó Grid ao seu Page:

<Page
    x:Class="CallingQuickstart.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CallingQuickstart"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Width="800" Height="600">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="16*"/>
            <RowDefinition Height="30*"/>
            <RowDefinition Height="200*"/>
            <RowDefinition Height="60*"/>
            <RowDefinition Height="16*"/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="1" x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="10,10,10,10" />

        <Grid x:Name="AppTitleBar" Background="LightSeaGreen">
            <TextBlock x:Name="QuickstartTitle" Text="Calling Quickstart sample title bar" Style="{StaticResource CaptionTextBlockStyle}" Padding="7,7,0,0"/>
        </Grid>

        <Grid Grid.Row="2">
            <Grid.RowDefinitions>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <MediaPlayerElement x:Name="LocalVideo" HorizontalAlignment="Center" Stretch="UniformToFill" Grid.Column="0" VerticalAlignment="Center" AutoPlay="True" />
            <MediaPlayerElement x:Name="RemoteVideo" HorizontalAlignment="Center" Stretch="UniformToFill" Grid.Column="1" VerticalAlignment="Center" AutoPlay="True" />
        </Grid>
        <StackPanel Grid.Row="3" Orientation="Vertical" Grid.RowSpan="2">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" VerticalAlignment="Center" Margin="10,0,0,0" Height="40" Width="123"/>
                <Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" VerticalAlignment="Center" Margin="10,0,0,0" Height="40" Width="123"/>
                <CheckBox x:Name="MuteLocal" Content="Mute" Margin="10,0,0,0" Click="MuteLocal_Click" Width="74"/>
            </StackPanel>
        </StackPanel>
        <TextBox Grid.Row="5" x:Name="Stats" Text="" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="0,2,0,0" BorderThickness="2" IsReadOnly="True" Foreground="LightSlateGray" />
    </Grid>
</Page>

Abra o MainPage.xaml.cs e substitua o conteúdo pela seguinte implementação:

using Azure.Communication.Calling.WindowsClient;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Core;
using Windows.Media.Core;
using Windows.Networking.PushNotifications;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace CallingQuickstart
{
    public sealed partial class MainPage : Page
    {
        private const string authToken = "<AUTHENTICATION_TOKEN>";

        private CallClient callClient;
        private CallTokenRefreshOptions callTokenRefreshOptions = new CallTokenRefreshOptions(false);
        private TeamsCallAgent teamsCallAgent;
        private TeamsCommunicationCall teamsCall;

        private LocalOutgoingAudioStream micStream;
        private LocalOutgoingVideoStream cameraStream;

        #region Page initialization
        public MainPage()
        {
            this.InitializeComponent();
            // Additional UI customization code goes here
        }

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
        }
        #endregion

        #region UI event handlers
        private async void CallButton_Click(object sender, RoutedEventArgs e)
        {
            // Start a call
        }

        private async void HangupButton_Click(object sender, RoutedEventArgs e)
        {
            // Hang up a call
        }

        private async void MuteLocal_Click(object sender, RoutedEventArgs e)
        {
            // Toggle mute/unmute audio state of a call
        }
        #endregion

        #region API event handlers
        private async void OnIncomingCallAsync(object sender, TeamsIncomingCallReceivedEventArgs args)
        {
            // Handle incoming call event
        }

        private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            // Handle connected and disconnected state change of a call
        }
        #endregion
    }
}

Modelo de objeto

A tabela a seguir lista as classes e interfaces que lidam com alguns dos principais recursos do SDK de Chamadas dos Serviços de Comunicação do Azure:

Nome Descrição
CallClient O CallClient é o ponto de entrada principal do SDK de Chamada.
TeamsCallAgent O TeamsCallAgent é usado para iniciar e gerenciar chamadas.
TeamsCommunicationCall O TeamsCommunicationCall é usado para gerenciar uma chamada em andamento.
CallTokenCredential O CallTokenCredential é usado como a credencial de token de segurança para criar uma instância TeamsCallAgent.
CallIdentifier O CallIdentifier é usado para representar a identidade do usuário, que pode ser uma das seguintes opções: MicrosoftTeamsUserCallIdentifier, UserCallIdentifier, PhoneNumberCallIdentifier etc.

Autenticar o cliente

Inicializar uma instância TeamsCallAgent com um Token de Acesso do Usuário que nos permite fazer e receber chamadas e, opcionalmente, obter uma instância DeviceManager para consultar as configurações do dispositivo cliente.

No código, substitua <AUTHENTICATION_TOKEN> por um Token de Acesso de Usuário. Confira a documentação do token de acesso do usuário se você ainda não tiver um token disponível.

Adicione a função InitCallAgentAndDeviceManagerAsync, que inicializa o SDK. Esse auxiliar pode ser personalizado para atender aos requisitos do seu aplicativo.

        private async Task InitCallAgentAndDeviceManagerAsync()
        {
            this.callClient = new CallClient(new CallClientOptions() {
                Diagnostics = new CallDiagnosticsOptions() { 
                    AppName = "CallingQuickstart",
                    AppVersion="1.0",
                    Tags = new[] { "Calling", "CTE", "Windows" }
                    }
                });

            // Set up local video stream using the first camera enumerated
            var deviceManager = await this.callClient.GetDeviceManagerAsync();
            var camera = deviceManager?.Cameras?.FirstOrDefault();
            var mic = deviceManager?.Microphones?.FirstOrDefault();
            micStream = new LocalOutgoingAudioStream();

            var tokenCredential = new CallTokenCredential(authToken, callTokenRefreshOptions);

            this.teamsCallAgent = await this.callClient.CreateTeamsCallAgentAsync(tokenCredential);
            this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;
        }

Iniciar uma chamada

Adicione a implementação ao CallButton_Click para iniciar vários tipos de chamadas com o objeto teamsCallAgent que criamos e conecte os manipuladores de eventos RemoteParticipantsUpdated e StateChanged no objeto TeamsCommunicationCall.

        private async void CallButton_Click(object sender, RoutedEventArgs e)
        {
            var callString = CalleeTextBox.Text.Trim();

            teamsCall = await StartCteCallAsync(callString);
            if (teamsCall != null)
            {
                teamsCall.StateChanged += OnStateChangedAsync;
            }
        }

Encerrar uma chamada

Finalize a chamada atual quando o botão Hang up for clicado. Adicione a implementação ao HangupButton_Click para encerrar uma chamada e interromper a visualização e os fluxos de vídeo.

        private async void HangupButton_Click(object sender, RoutedEventArgs e)
        {
            var teamsCall = this.teamsCallAgent?.Calls?.FirstOrDefault();
            if (teamsCall != null)
            {
                await teamsCall.HangUpAsync(new HangUpOptions() { ForEveryone = false });
            }
        }

Ativar/desativar mudo no áudio

Ative o mudo do áudio de saída quando o botão Mute for clicado. Adicione a implementação ao MuteLocal_Click para ativar o mudo da chamada.

        private async void MuteLocal_Click(object sender, RoutedEventArgs e)
        {
            var muteCheckbox = sender as CheckBox;

            if (muteCheckbox != null)
            {
                var teamsCall = this.teamsCallAgent?.Calls?.FirstOrDefault();
                if (teamsCall != null)
                {
                    if ((bool)muteCheckbox.IsChecked)
                    {
                        await teamsCall.MuteOutgoingAudioAsync();
                    }
                    else
                    {
                        await teamsCall.UnmuteOutgoingAudioAsync();
                    }
                }

                // Update the UI to reflect the state
            }
        }

Inicie a chamada

Depois que um objeto StartTeamsCallOptions é obtido, TeamsCallAgent pode ser usado para iniciar a chamada do Teams:

        private async Task<TeamsCommunicationCall> StartCteCallAsync(string cteCallee)
        {
            var options = new StartTeamsCallOptions();
            var teamsCall = await this.teamsCallAgent.StartCallAsync( new MicrosoftTeamsUserCallIdentifier(cteCallee), options);
            return call;
        }

Aceitar uma chamada de entrada

O coletor de eventos TeamsIncomingCallReceived é configurado no auxiliar de inicialização do SDK InitCallAgentAndDeviceManagerAsync.

    this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;

O aplicativo tem a oportunidade de configurar como a chamada de entrada deve ser aceita, como tipos de fluxo de áudio e vídeo.

        private async void OnIncomingCallAsync(object sender, TeamsIncomingCallReceivedEventArgs args)
        {
            var teamsIncomingCall = args.IncomingCall;

            var acceptteamsCallOptions = new AcceptTeamsCallOptions() { };

            teamsCall = await teamsIncomingCall.AcceptAsync(acceptteamsCallOptions);
            teamsCall.StateChanged += OnStateChangedAsync;
        }

Ingressar em uma chamada do Teams

O usuário também pode ingressar em uma chamada existente transmitindo um link

TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
JoinTeamsCallOptions options = new JoinTeamsCallOptions();
TeamsCall call = await teamsCallAgent.JoinAsync(link, options);

Monitorar e responder ao evento de alteração de estado da chamada

O evento StateChanged no objeto TeamsCommunicationCall é disparado quando uma transação em andamento chama de um estado para outro. O aplicativo tem as oportunidades de refletir as alterações de estado na interface do usuário ou inserir lógicas de negócios.

        private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            var teamsCall = sender as TeamsCommunicationCall;

            if (teamsCall != null)
            {
                var state = teamsCall.State;

                // Update the UI

                switch (state)
                {
                    case CallState.Connected:
                        {
                            await teamsCall.StartAudioAsync(micStream);

                            break;
                        }
                    case CallState.Disconnected:
                        {
                            teamsCall.StateChanged -= OnStateChangedAsync;

                            teamsCall.Dispose();

                            break;
                        }
                    default: break;
                }
            }
        }

Executar o código

Você pode criar e executar o código no Visual Studio. Para plataformas de solução, oferecemos suporte a ARM64, x64 e x86.

Você pode fazer uma chamada de saída fornecendo uma ID de usuário no campo de texto e clicando no botão Start Call/Join. Ligar para 8:echo123 conecta você a um bot de eco. Esse recurso é ótimo para começar e verificar se seus dispositivos de áudio estão funcionando.

Captura de tela mostrando a execução do aplicativo de início rápido UWP

Comece a usar os Serviços de Comunicação do Azure com o SDK de Chamada dos Serviços de Comunicação para adicionar uma chamada de voz e vídeo individual ao seu aplicativo. Você aprenderá como iniciar e atender uma chamada usando o SDK de Chamada dos Serviços de Comunicação do Azure para Java.

Exemplo de código

Para pular para o final, baixe este guia de início rápido como um exemplo no GitHub.

Pré-requisitos

Configurando

Criar um aplicativo Android com uma atividade vazia

No Android Studio, selecione Iniciar um novo projeto do Android Studio.

Captura de tela que mostra o botão 'Iniciar um novo Projeto do Android Studio' selecionado no Android Studio.

Selecione o modelo de projeto "Atividade Vazia" em "Telefone e Tablet".

Captura de tela que mostra a opção 'Atividade Vazia' selecionada na Tela do Modelo do Projeto.

Selecione o SDK Mínimo de "API 26: Android 8.0 (Oreo)" ou superior.

Captura de tela que mostra a opção 'Atividade Vazia' selecionada na Tela do Modelo do Projeto 2.

Instalar o pacote

Localize o nível de projeto build.gradle e certifique-se de adicionar mavenCentral() à lista de repositórios em buildscript e allprojects

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

Em seguida, no nível do módulo build.gradle, adicione as linhas a seguir às seções do Android e às dependências

android {
    ...
    packagingOptions {
        pickFirst  'META-INF/*'
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

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

Adicionar permissões ao manifesto do aplicativo

Para solicitar as permissões necessárias para fazer uma chamada, elas devem ser declaradas no Manifesto do Aplicativo (app/src/main/AndroidManifest.xml). Substitua o conteúdo do arquivo pelo seguinte código:

    <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.contoso.ctequickstart">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <!--Our Calling SDK depends on the Apache HTTP SDK.
When targeting Android SDK 28+, this library needs to be explicitly referenced.
See https://developer.android.com/about/versions/pie/android-9.0-changes-28#apache-p-->
        <uses-library android:name="org.apache.http.legacy" android:required="false"/>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
    

Configurar o layout do aplicativo

Duas entradas são necessárias: uma entrada de texto para a ID do receptor e um botão para realizar a chamada. Essas entradas podem ser adicionadas através do designer ou editando o layout xml. Crie um botão com uma ID de call_button e uma entrada de texto de callee_id. Navegue até (app/src/main/res/layout/activity_main.xml) e substitua o conteúdo do arquivo pelo seguinte código:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/call_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:text="Call"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <EditText
        android:id="@+id/callee_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Callee Id"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toTopOf="@+id/call_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Criar as associações e o scaffolding da atividade principal

Com o layout criado, as associações podem ser adicionadas, bem como o scaffolding básico da atividade. A atividade lida com a solicitação de permissões de tempo de execução, a criação do agente de chamada de equipes e a colocação da chamada quando o botão é pressionado. Cada um é abordado na sua própria seção. O método onCreate foi substituído para invocar getAllPermissions e createTeamsAgent e adicionar as associações para o botão de chamada. Esse evento ocorre apenas uma vez quando a atividade é criada. Para saber mais, em onCreate, confira o guia Noções básicas do ciclo de vida de atividade.

Navegue para MainActivity.java e substitua o conteúdo pelo seguinte código:

package com.contoso.ctequickstart;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.media.AudioManager;
import android.Manifest;
import android.content.pm.PackageManager;

import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.azure.android.communication.common.CommunicationUserIdentifier;
import com.azure.android.communication.common.CommunicationTokenCredential;
import com.azure.android.communication.calling.TeamsCallAgent;
import com.azure.android.communication.calling.CallClient;
import com.azure.android.communication.calling.StartTeamsCallOptions;


import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    
    private TeamsCallAgent teamsCallAgent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getAllPermissions();
        createTeamsAgent();
        
        // Bind call button to call `startCall`
        Button callButton = findViewById(R.id.call_button);
        callButton.setOnClickListener(l -> startCall());
        
        setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
    }

    /**
     * Request each required permission if the app doesn't already have it.
     */
    private void getAllPermissions() {
        // See section on requesting permissions
    }

    /**
      * Create the call agent for placing calls
      */
    private void createTeamsAgent() {
        // See section on creating the call agent
    }

    /**
     * Place a call to the callee id provided in `callee_id` text input.
     */
    private void startCall() {
        // See section on starting the call
    }
}

Solicitar permissões no runtime

Para o Android 6.0 e superior (nível da API 23) e targetSdkVersion 23 ou superior, as permissões são concedidas no runtime em vez de quando o aplicativo é instalado. Para suportá-lo, getAllPermissions pode ser implementado para chamar ActivityCompat.checkSelfPermission e ActivityCompat.requestPermissions em cada permissão necessária.

/**
 * Request each required permission if the app doesn't already have it.
 */
private void getAllPermissions() {
    String[] requiredPermissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE};
    ArrayList<String> permissionsToAskFor = new ArrayList<>();
    for (String permission : requiredPermissions) {
        if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            permissionsToAskFor.add(permission);
        }
    }
    if (!permissionsToAskFor.isEmpty()) {
        ActivityCompat.requestPermissions(this, permissionsToAskFor.toArray(new String[0]), 1);
    }
}

Observação

Ao projetar o seu aplicativo, leve em consideração quando essas permissões devem ser solicitadas. As permissões devem ser solicitadas quando elas forem necessárias e não com antecedência. Para obter mais informações, confira o Guia de Permissões do Android.

Modelo de objeto

As seguintes classes e interfaces cuidam de alguns dos principais recursos do SDK de Chamada da Interface do Usuário dos Serviços de Comunicação do Azure:

Nome Descrição
CallClient O CallClient é o ponto de entrada principal do SDK de Chamada.
TeamsCallAgent O TeamsCallAgent é usado para iniciar e gerenciar chamadas.
TeamsCall O TeamsCall usado para representar uma chamada do Teams.
CommunicationTokenCredential O CommunicationTokenCredential é usado como credencial de token para instanciar o TeamsCallAgent.
CommunicationIdentifier O CommunicationIdentifier é usado como um tipo diferente de participante que pode fazer parte de uma chamada.

Criar um agente do token de acesso do usuário

Com um token de usuário, um agente de chamada autenticado pode criar uma instância. Geralmente, esse token é gerado a partir de um serviço com autenticação específica para o aplicativo. Para saber mais sobre tokens de acesso do usuário, confira o guia Tokens de acesso do usuário.

Para o guia de início rápido, substitua <User_Access_Token> por um token de acesso de usuário gerado para o recurso do Serviço de Comunicação do Azure.


/**
 * Create the teams call agent for placing calls
 */
private void createAgent() {
    String userToken = "<User_Access_Token>";

    try {
        CommunicationTokenCredential credential = new CommunicationTokenCredential(userToken);
        teamsCallAgent = new CallClient().createTeamsCallAgent(getApplicationContext(), credential).get();
    } catch (Exception ex) {
        Toast.makeText(getApplicationContext(), "Failed to create teams call agent.", Toast.LENGTH_SHORT).show();
    }
}

Iniciar uma chamada usando o agente de chamada

Fazer a chamada pode ser feito através do agente de chamada de equipes, e requer apenas fornecer uma lista de IDs de chamada e as opções de chamada. Para o início rápido, as opções de chamada padrão sem vídeo e um único identificador de computador chamado do texto de entrada são usados.

/**
 * Place a call to the callee id provided in `callee_id` text input.
 */
private void startCall() {
    EditText calleeIdView = findViewById(R.id.callee_id);
    String calleeId = calleeIdView.getText().toString();
    
    StartTeamsCallOptions options = new StartTeamsCallOptions();

    teamsCallAgent.startCall(
        getApplicationContext(),
        new MicrosoftTeamsUserCallIdentifier(calleeId),
        options);
}

Atender a uma chamada

Uma chamada pode ser aceita por meio do agente de chamada do Teams com apenas uma referência ao contexto atual.

public void acceptACall(TeamsIncomingCall teamsIncomingCall){
	teamsIncomingCall.accept(this);
}

Ingressar em uma chamada do Teams

Um usuário pode ingressar em uma chamada existente transmitindo um link.

/**
 * Join a call using a teams meeting link.
 */
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
	TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
	TeamsCall call = teamsCallAgent.join(this, link);
}

Ingressar em uma chamada do Teams com opções

Também podemos ingressar em uma chamada existente com opções predefinidas, por exemplo, ativando o mudo.

/**
 * Join a call using a teams meeting link while muted.
 */
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
	TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
	OutgoingAudioOptions audioOptions = new OutgoingAudioOptions().setMuted(true);
	JoinTeamsCallOptions options = new JoinTeamsCallOptions().setAudioOptions(audioOptions);
	TeamsCall call = teamsCallAgent.join(this, link, options);
}

Configurar o ouvinte de chamadas de entrada

Para conseguir detectar chamadas de entrada e outras ações não feitas por esse usuário, os ouvintes precisam ser configurados.

private TeamsIncomingCall teamsincomingCall;
teamsCallAgent.addOnIncomingCallListener(this::handleIncomingCall);

private void handleIncomingCall(TeamsIncomingCall incomingCall) {
	this.teamsincomingCall = incomingCall;
}

Iniciar o aplicativo e chamar o bot de eco

Agora, o aplicativo pode ser iniciado usando o botão "Executar Aplicativo" na barra de ferramentas (Shift+F10). Chame 8:echo123 para verificar se é possível fazer chamadas. Uma mensagem pré-gravada é reproduzida e, em seguida, repete sua mensagem para você.

Captura de tela que mostra o aplicativo concluído.

Comece a usar os Serviços de Comunicação do Azure com o SDK de Chamada dos Serviços de Comunicação para adicionar uma chamada de voz e vídeo individual ao aplicativo. Você aprenderá a iniciar e atender uma chamada de vídeo usando o SDK de Chamada dos Serviços de Comunicação do Azure para iOS usando a identidade do Teams.

Exemplo de código

Para pular para o final, baixe este guia de início rápido como um exemplo no GitHub.

Pré-requisitos

Configurando

Como criar o projeto do Xcode

No Xcode, crie um projeto do iOS e selecione o modelo Aplicativo de Modo de Exibição Único. Este tutorial usa a estrutura SwiftUI. Portanto, você deve definir a Linguagem como Swift e a Interface do Usuário como SwiftUI. Você não criará testes durante este guia de início rápido. Fique à vontade para desmarcar Incluir Testes.

Captura de tela mostrando a janela Novo Projeto no Xcode.

Como instalar o CocoaPods

Use este guia para instalar o CocoaPods no Mac.

Instale o pacote e as dependências com o CocoaPods

  1. Para criar umPodfile para seu aplicativo, abra o terminal, procure a pasta do projeto e execute pod init.

  2. Adicione o seguinte código ao Podfile e salve. Consulte as versões do SDK com suporte.

platform :ios, '13.0'
use_frameworks!

target 'VideoCallingQuickstart' do
  pod 'AzureCommunicationCalling', '~> 2.10.0'
end
  1. Execute pod install.

  2. Abra o .xcworkspace com o Xcode.

Solicitar acesso ao microfone e à câmera

Para acessar o microfone e a câmera do dispositivo, você precisará atualizar a Lista de Propriedades de Informações do aplicativo com uma NSMicrophoneUsageDescription e uma NSCameraUsageDescription. Defina o valor associado a uma cadeia de caracteres incluída na caixa de diálogo que o sistema usa para solicitar acesso do usuário.

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

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

Configurar o framework de aplicativos

Abra o arquivo ContentView.swift do projeto e adicione uma declaração de importação à parte superior do arquivo para importar a biblioteca AzureCommunicationCalling e AVFoundation. O AVFoundation é usado para capturar a permissão de áudio do código.

import AzureCommunicationCalling
import AVFoundation

Modelo de objeto

As classes e as interfaces a seguir lidam com alguns dos principais recursos do SDK de Chamada dos Serviços de Comunicação do Azure para iOS.

Nome Descrição
CallClient O CallClient é o ponto de entrada principal do SDK de Chamada.
TeamsCallAgent O TeamsCallAgent é usado para iniciar e gerenciar chamadas.
TeamsIncomingCall O TeamsIncomingCall é usado para aceitar ou rejeitar chamadas de equipes de entrada.
CommunicationTokenCredential O CommunicationTokenCredential é usado como a credencial de token de segurança para criar uma instância TeamsCallAgent.
CommunicationIdentifier O CommunicationIdentifier é usado para representar a identidade do usuário, que pode ser uma das seguintes opções: CommunicationUserIdentifier, PhoneNumberIdentifier ou CallingApplication.

Criar o agente de chamada do Teams

Substitua a implementação do struct ContentView por alguns controles de interface do usuário simples que permitem que um usuário inicie e termine uma chamada. Anexaremos a lógica de negócios a esses controles neste guia de início rápido.

struct ContentView: View {
    @State var callee: String = ""
    @State var callClient: CallClient?
    @State var teamsCallAgent: TeamsCallAgent?
    @State var teamsCall: TeamsCall?
    @State var deviceManager: DeviceManager?
    @State var localVideoStream:[LocalVideoStream]?
    @State var teamsIncomingCall: TeamsIncomingCall?
    @State var sendingVideo:Bool = false
    @State var errorMessage:String = "Unknown"

    @State var remoteVideoStreamData:[Int32:RemoteVideoStreamData] = [:]
    @State var previewRenderer:VideoStreamRenderer? = nil
    @State var previewView:RendererView? = nil
    @State var remoteRenderer:VideoStreamRenderer? = nil
    @State var remoteViews:[RendererView] = []
    @State var remoteParticipant: RemoteParticipant?
    @State var remoteVideoSize:String = "Unknown"
    @State var isIncomingCall:Bool = false
    
    @State var callObserver:CallObserver?
    @State var remoteParticipantObserver:RemoteParticipantObserver?

    var body: some View {
        NavigationView {
            ZStack{
                Form {
                    Section {
                        TextField("Who would you like to call?", text: $callee)
                        Button(action: startCall) {
                            Text("Start Teams Call")
                        }.disabled(teamsCallAgent == nil)
                        Button(action: endCall) {
                            Text("End Teams Call")
                        }.disabled(teamsCall == nil)
                        Button(action: toggleLocalVideo) {
                            HStack {
                                Text(sendingVideo ? "Turn Off Video" : "Turn On Video")
                            }
                        }
                    }
                }
                // Show incoming call banner
                if (isIncomingCall) {
                    HStack() {
                        VStack {
                            Text("Incoming call")
                                .padding(10)
                                .frame(maxWidth: .infinity, alignment: .topLeading)
                        }
                        Button(action: answerIncomingCall) {
                            HStack {
                                Text("Answer")
                            }
                            .frame(width:80)
                            .padding(.vertical, 10)
                            .background(Color(.green))
                        }
                        Button(action: declineIncomingCall) {
                            HStack {
                                Text("Decline")
                            }
                            .frame(width:80)
                            .padding(.vertical, 10)
                            .background(Color(.red))
                        }
                    }
                    .frame(maxWidth: .infinity, alignment: .topLeading)
                    .padding(10)
                    .background(Color.gray)
                }
                ZStack{
                    VStack{
                        ForEach(remoteViews, id:\.self) { renderer in
                            ZStack{
                                VStack{
                                    RemoteVideoView(view: renderer)
                                        .frame(width: .infinity, height: .infinity)
                                        .background(Color(.lightGray))
                                }
                            }
                            Button(action: endCall) {
                                Text("End Call")
                            }.disabled(teamsCall == nil)
                            Button(action: toggleLocalVideo) {
                                HStack {
                                    Text(sendingVideo ? "Turn Off Video" : "Turn On Video")
                                }
                            }
                        }
                        
                    }.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                    VStack{
                        if(sendingVideo)
                        {
                            VStack{
                                PreviewVideoStream(view: previewView!)
                                    .frame(width: 135, height: 240)
                                    .background(Color(.lightGray))
                            }
                        }
                    }.frame(maxWidth:.infinity, maxHeight:.infinity,alignment: .bottomTrailing)
                }
            }
     .navigationBarTitle("Video Calling Quickstart")
        }.onAppear{
            // Authenticate the client
            
            // Initialize the TeamsCallAgent and access Device Manager
            
            // Ask for permissions
        }
    }
}

//Functions and Observers

struct PreviewVideoStream: UIViewRepresentable {
    let view:RendererView
    func makeUIView(context: Context) -> UIView {
        return view
    }
    func updateUIView(_ uiView: UIView, context: Context) {}
}

struct RemoteVideoView: UIViewRepresentable {
    let view:RendererView
    func makeUIView(context: Context) -> UIView {
        return view
    }
    func updateUIView(_ uiView: UIView, context: Context) {}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Autenticar o cliente

Para iniciar uma instância TeamsCallAgent, você precisa de um Token de Acesso do Usuário, o que nos permite fazer e receber chamadas. Confira a documentação do token de acesso de usuário se você ainda não tiver um token disponível.

Depois de obter o token, adicione o seguinte código ao retorno de chamada onAppear no ContentView.swift. Você precisa substituir <USER ACCESS TOKEN> por um token de acesso de usuário válido para o seu recurso.

var userCredential: CommunicationTokenCredential?
do {
    userCredential = try CommunicationTokenCredential(token: "<USER ACCESS TOKEN>")
} catch {
    print("ERROR: It was not possible to create user credential.")
    return
}

Inicializar o CallAgent do Teams e acessar o Gerenciador de Dispositivos

Para criar uma instância TeamsCallAgent a partir de um CallClient, use o método callClient.createTeamsCallAgent que retorna de forma assíncrona um objeto TeamsCallAgent depois de inicializado. O DeviceManager permite que você enumere os dispositivos locais que podem ser usados em uma chamada para transmitir os fluxos de áudio/vídeo. Ele também possibilita que você solicite permissão de um usuário para acessar o microfone/câmera.

self.callClient = CallClient()
let options = TeamsCallAgentOptions()
// Enable CallKit in the SDK
options.callKitOptions = CallKitOptions(with: createCXProvideConfiguration())
self.callClient?.createTeamsCallAgent(userCredential: userCredential, options: options) { (agent, error) in
    if error != nil {
        print("ERROR: It was not possible to create a Teams call agent.")
        return
    } else {
        self.teamsCallAgent = agent
        print("Teams Call agent successfully created.")
        self.teamsCallAgent!.delegate = teamsIncomingCallHandler
        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")
            }
        }
    }
}

Solicitar permissões

Precisamos adicionar o código a seguir ao retorno onAppear de chamada para solicitar permissões de áudio e vídeo.

AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
    if granted {
        AVCaptureDevice.requestAccess(for: .video) { (videoGranted) in
            /* NO OPERATION */
        }
    }
}

Realizar uma chamada de saída

O método startCall é definido como a ação que será executada ao tocar no botão Iniciar Chamada. Neste guia de início rápido, as chamadas de saída são somente áudio por padrão. Para iniciar uma chamada com vídeo, precisamos definir VideoOptions com LocalVideoStream e transmiti-lo com startCallOptions para definir as opções iniciais para a chamada.

let startTeamsCallOptions = StartTeamsCallOptions()
if sendingVideo  {
    if self.localVideoStream == nil  {
        self.localVideoStream = [LocalVideoStream]()
    }
    let videoOptions = VideoOptions(localVideoStreams: localVideoStream!)
    startTeamsCallOptions.videoOptions = videoOptions
}
let callees: [CommunicationIdentifier] = [CommunicationUserIdentifier(self.callee)]
self.teamsCallAgent?.startCall(participants: callees, options: startTeamsCallOptions) { (call, error) in
    // Handle call object if successful or an error.
}

Ingressar em uma reunião do Teams

O método join permite que o usuário participe de uma reunião do Teams.

let joinTeamsCallOptions = JoinTeamsCallOptions()
if sendingVideo
{
    if self.localVideoStream == nil {
        self.localVideoStream = [LocalVideoStream]()
    }
    let videoOptions = VideoOptions(localVideoStreams: localVideoStream!)
    joinTeamsCallOptions.videoOptions = videoOptions
}

// Join the Teams meeting muted
if isMuted
{
    let outgoingAudioOptions = OutgoingAudioOptions()
    outgoingAudioOptions.muted = true
    joinTeamsCallOptions.outgoingAudioOptions = outgoingAudioOptions
}

let teamsMeetingLinkLocator = TeamsMeetingLinkLocator(meetingLink: "https://meeting_link")

self.teamsCallAgent?.join(with: teamsMeetingLinkLocator, options: joinTeamsCallOptions) { (call, error) in
    // Handle call object if successful or an error.
}

TeamsCallObserver e RemotePariticipantObserver são usados para gerenciar eventos durante a chamada e participantes remotos. Definiremos os observadores na função setTeamsCallAndObserver.

func setTeamsCallAndObserver(call:TeamsCall, error:Error?) {
    if (error == nil) {
        self.teamsCall = call
        self.teamsCallObserver = TeamsCallObserver(self)
        self.teamsCall!.delegate = self.teamsCallObserver
        // Attach a RemoteParticipant observer
        self.remoteParticipantObserver = RemoteParticipantObserver(self)
    } else {
        print("Failed to get teams call object")
    }
}

Responder a uma chamada de entrada

Para responder a uma chamada de entrada, implemente um TeamsIncomingCallHandler para exibir a faixa de chamada de entrada para responder à chamada ou recusá-la. Coloque a implementação a seguir em TeamsIncomingCallHandler.swift.

final class TeamsIncomingCallHandler: NSObject, TeamsCallAgentDelegate, TeamsIncomingCallDelegate {
    public var contentView: ContentView?
    private var teamsIncomingCall: TeamsIncomingCall?

    private static var instance: TeamsIncomingCallHandler?
    static func getOrCreateInstance() -> TeamsIncomingCallHandler {
        if let c = instance {
            return c
        }
        instance = TeamsIncomingCallHandler()
        return instance!
    }

    private override init() {}
    
    func teamsCallAgent(_ teamsCallAgent: TeamsCallAgent, didRecieveIncomingCall incomingCall: TeamsIncomingCall) {
        self.teamsIncomingCall = incomingCall
        self.teamsIncomingCall.delegate = self
        contentView?.showIncomingCallBanner(self.teamsIncomingCall!)
    }
    
    func teamsCallAgent(_ teamsCallAgent: TeamsCallAgent, didUpdateCalls args: TeamsCallsUpdatedEventArgs) {
        if let removedCall = args.removedCalls.first {
            contentView?.callRemoved(removedCall)
            self.teamsIncomingCall = nil
        }
    }
}

Precisamos criar uma instância de TeamsIncomingCallHandler adicionando o seguinte código ao retorno de chamada onAppear em ContentView.swift:

Defina um delegado para o TeamsCallAgent após a criação bem-sucedida do TeamsCallAgent:

self.teamsCallAgent!.delegate = incomingCallHandler

Quando houver uma chamada de entrada, o TeamsIncomingCallHandler chamará a função showIncomingCallBanner para exibir answer e o botão decline.

func showIncomingCallBanner(_ incomingCall: TeamsIncomingCall) {
    self.teamsIncomingCall = incomingCall
}

As ações anexadas a answer e decline são implementadas como o código abaixo. Para responder à chamada com vídeo, precisamos ativar o vídeo local e definir as opções de AcceptCallOptions com localVideoStream.

func answerIncomingCall() {
    let options = AcceptTeamsCallOptions()
    guard let teamsIncomingCall = self.teamsIncomingCall else {
      print("No active incoming call")
      return
    }

    guard let deviceManager = deviceManager else {
      print("No device manager instance")
      return
    }

    if self.localVideoStreams == nil {
        self.localVideoStreams = [LocalVideoStream]()
    }

    if sendingVideo
    {
        guard let camera = deviceManager.cameras.first else {
            // Handle failure
            return
        }
        self.localVideoStreams?.append( LocalVideoStream(camera: camera))
        let videoOptions = VideoOptions(localVideoStreams: localVideosStreams!)
        options.videoOptions = videoOptions
    }

    teamsIncomingCall.accept(options: options) { (call, error) in
        // Handle call object if successful or an error.
    }
}

func declineIncomingCall() {
    self.teamsIncomingCall?.reject { (error) in 
        // Handle if rejection was successfully or not.
    }
}

Assinar eventos

Podemos implementar uma classe TeamsCallObserver para assinar uma coleção de eventos para receber uma notificação de quando os valores forem alterados durante a chamada.

public class TeamsCallObserver: NSObject, TeamsCallDelegate, TeamsIncomingCallDelegate {
    private var owner: ContentView
    init(_ view:ContentView) {
            owner = view
    }
        
    public func teamsCall(_ teamsCall: TeamsCall, didChangeState args: PropertyChangedEventArgs) {
        if(teamsCall.state == CallState.connected) {
            initialCallParticipant()
        }
    }

    // render remote video streams when remote participant changes
    public func teamsCall(_ teamsCall: TeamsCall, didUpdateRemoteParticipant args: ParticipantsUpdatedEventArgs) {
        for participant in args.addedParticipants {
            participant.delegate = self.remoteParticipantObserver
        }
    }

    // Handle remote video streams when the call is connected
    public func initialCallParticipant() {
        for participant in owner.teamsCall.remoteParticipants {
            participant.delegate = self.remoteParticipantObserver
            for stream in participant.videoStreams {
                renderRemoteStream(stream)
            }
            owner.remoteParticipant = participant
        }
    }
}

Executar o código

Compile e execute seu aplicativo no simulador de iOS selecionando Produto > Executar ou usando o atalho de teclado (⌘-R).

Limpar os recursos

Se quiser limpar e remover uma assinatura dos Serviços de Comunicação, exclua o recurso ou o grupo de recursos. Excluir o grupo de recursos também exclui todos os recursos associados a ele. Saiba mais sobre como limpar recursos.

Próximas etapas

Para obter mais informações, consulte os seguintes artigos: