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
- Obtenha uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente.
- Você precisa ter o Node.js 18. Use o instalador MSI para instalá-lo.
- Crie um recurso ativo dos Serviços de Comunicação. Crie um recurso dos Serviços de Comunicação.
- Crie um Token de Acesso do Usuário para criar uma instância do cliente de chamada. Saiba como criar e gerenciar os tokens de acesso do usuário.
- Obtenha a ID de thread do Teams para operações de chamada usando o Explorador do Graph. Leia mais sobre como criar a ID do thread de chat.
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 updates.
subscribeToCall = (call) => {
try {
// Inspect the initial call.id value.
console.log(`Call Id: ${call.id}`);
//Subscribe 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 updates.
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 remoteParticipant 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:
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:
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:
Na segunda guia, selecione o botão "Aceitar Chamada". A chamada será atendida e conectada. As guias devem mostrar um resultado semelhante à seguinte imagem:
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:
- Uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente.
- Instale o Visual Studio 2022 com a carga de trabalho de desenvolvimento da Plataforma Universal do Windows.
- Um recurso dos Serviços de Comunicação implantado. Crie um recurso dos Serviços de Comunicação. Você precisa gravar sua cadeia de conexão nesse início rápido.
- Um Token de Acesso do Usuário para o seu Serviço de Comunicação do Azure.
- Obtenha a ID de thread do Teams para operações de chamada usando o Explorador do Graph. Leia mais sobre como criar a ID do thread de chat.
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).
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.
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.
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
- Uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente.
- Android Studio, para criar o seu aplicativo Android.
- Um recurso dos Serviços de Comunicação implantado. Crie um recurso dos Serviços de Comunicação. Você precisa gravar sua cadeia de conexão nesse início rápido.
- Um Token de Acesso do Usuário para o seu Serviço de Comunicação do Azure.
- Obtenha a ID de thread do Teams para operações de chamada usando o Explorador do Graph. Leia mais sobre como criar a ID do thread de chat.
Configurando
Criar um aplicativo Android com uma atividade vazia
No Android Studio, selecione Iniciar um novo projeto do Android Studio.
Selecione o modelo de projeto "Atividade Vazia" em "Telefone e Tablet".
Selecione o SDK Mínimo de "API 26: Android 8.0 (Oreo)" ou superior.
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ê.
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
- Obtenha uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente.
- Um Mac executando Xcode, juntamente com um certificado de desenvolvedor válido instalado em seu conjunto de chaves.
- Crie um recurso ativo dos Serviços de Comunicação. Crie um recurso dos Serviços de Comunicação. Você precisa gravar sua cadeia de conexão nesse início rápido.
- Um Token de Acesso do Usuário para o seu Serviço de Comunicação do Azure.
- Obtenha a ID de thread do Teams para operações de chamada usando o Explorador do Graph. Leia mais sobre como criar ID de thread de bate-papo
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.
Como instalar o CocoaPods
Use este guia para instalar o CocoaPods no Mac.
Instale o pacote e as dependências com o CocoaPods
Para criar um
Podfile
para seu aplicativo, abra o terminal, procure a pasta do projeto e execute pod init.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
Execute pod install.
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 RemoteParticipantObserver
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, didReceiveIncomingCall 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:
- Confira nosso exemplo de chamada da Web
- Conheça os recursos do SDK de Chamada
- Saiba mais sobre como a chamada funciona