Inicio rápido: Incorporación de llamadas de vídeo 1:1 como usuario de Teams a la aplicación
Comience a usar Azure Communication Services con el SDK de llamadas de Azure Communication Services para agregar llamadas de voz y vídeo 1:1 a la aplicación. Aprenderá a iniciar llamadas y a responder a ellas con Calling SDK de Azure Communication Services para JavaScript.
Código de ejemplo
Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.
Requisitos previos
- Obtenga una cuenta de Azure con una suscripción activa. Cree una cuenta gratuita.
- Necesita tener Node.js 18. Puede usar el instalador msi para instalarlo.
- Cree un recurso activo de Communication Services. Cree un recurso de Communication Services.
- Cree un token de acceso de usuario para crear una instancia del cliente de llamada. Creación y administración de tokens de acceso.
- Obtenga el identificador de subproceso de Teams para las operaciones de llamadas mediante el Probador de Graph. Más información sobre cómo crear el identificador de subproceso de chat.
Instalación
Creación de una aplicación Node.js
Abra la ventana de comandos o el terminal, cree un nuevo directorio para la aplicación y vaya al directorio.
mkdir calling-quickstart && cd calling-quickstart
Ejecute npm init -y
para crear un archivo package.json con la configuración predeterminada.
npm init -y
Instalar el paquete
Use el comando npm install
para instalar el SDK de llamadas de Azure Communication Services para JavaScript.
Importante
En este inicio rápido se usa la versión del SDK de llamadas de Azure Communication Services más reciente.
npm install @azure/communication-common --save
npm install @azure/communication-calling --save
Instalación del marco de la aplicación
Esta guía de inicio rápido usa webpack para agrupar los recursos de la aplicación. Ejecute el siguiente comando para instalar los paquetes npm webpack
, webpack-cli
y webpack-dev-server
, y los enumera como dependencias de desarrollo en el archivo 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
Cree un archivo index.html
en el directorio raíz del proyecto. Este archivo lo usaremos para configurar un diseño básico que permitirá al usuario realizar una llamada de video 1:1.
Este es el código:
<!-- 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 objetos del SDK web de llamada de Azure Communication Services
Las siguientes clases e interfaces administran algunas de las características principales de Calling SDK de Azure Communication Services:
Nombre | Descripción |
---|---|
CallClient |
El punto de entrada principal al SDK de llamadas. |
AzureCommunicationTokenCredential |
Implementa la interfaz de CommunicationTokenCredential , que se usa para crear instancias de teamsCallAgent . |
TeamsCallAgent |
Se utiliza para iniciar y administrar las llamadas de Teams. |
DeviceManager |
Se usa para administrar dispositivos multimedia. |
TeamsCall |
Se usa para representar una llamada de Teams |
LocalVideoStream |
Se usa para crear una secuencia de vídeo local para un dispositivo de cámara en el sistema local. |
RemoteParticipant |
Se usa para representar a un participante remoto de la llamada. |
RemoteVideoStream |
Se usa para representar una secuencia de vídeo remota desde un participante remoto. |
Cree un archivo en el directorio raíz del proyecto denominado index.js
que contendrá la lógica de la aplicación para esta guía de inicio rápido. Agregue el código siguiente a 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();
});
Adición del código del servidor local de webpack
Cree un archivo en el directorio raíz del proyecto denominado webpack.config.js que contendrá la lógica de la aplicación para esta guía de inicio rápido. Agregue el siguiente código a 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'
]
}),
]
};
Ejecución del código
Utilice webpack-dev-server
para compilar y ejecutar la aplicación. Ejecute el siguiente comando para agrupar el host de aplicación en un servidor web local:
`npx webpack serve --config webpack.config.js`
Abra el explorador y, en dos pestañas, vaya a http://localhost:8080/.. Las pestañas deben mostrar un resultado similar al de la imagen siguiente:
En la primera pestaña, escriba un token de acceso de usuario válido. En la segunda pestaña, escriba otro token de acceso de usuario válido. Consulte la documentación relativa al token de acceso de usuario si aún no tiene ningún token de acceso disponible para utilizarlo. En ambas pestañas, haga clic en los botones "Inicializar agente de llamada". Las pestañas deberían mostrar un resultado similar al de la siguiente imagen:
En la primera pestaña, escriba la identidad de usuario de Azure Communication Services de la segunda pestaña y seleccione el botón "Iniciar llamada". La primera pestaña iniciará la llamada saliente a la segunda pestaña, y se habilitará el botón "Aceptar llamada" de la segunda pestaña:
En la segunda pestaña, seleccione el botón "Aceptar llamada". Se responderá a la llamada y esta se conectará. Las pestañas deben mostrar un resultado similar al de la imagen siguiente:
Ambas pestañas se encuentran ahora correctamente en una videollamada de 1:1. Ambos usuarios pueden escuchar el audio del otro y ver la secuencia de vídeo del otro.
Comience a usar Azure Communication Services con el SDK de llamadas de Azure Communication Services para agregar llamadas de voz y vídeo 1:1 a la aplicación. Aprenderá a iniciar llamadas y a responder a ellas con Calling SDK de Azure Communication Services para Windows.
Código de ejemplo
Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.
Requisitos previos
Para completar este tutorial, debe cumplir los siguientes requisitos previos:
- Una cuenta de Azure con una suscripción activa. Cree una cuenta gratuita.
- Instale Visual Studio 2022 con la carga de trabajo de desarrollo de la Plataforma universal de Windows.
- Un recurso de Communication Services implementado. Cree un recurso de Communication Services. Debe registrar la cadena de conexión para esta guía de inicio rápido.
- Un token de acceso de usuario para su instancia de Azure Communication Services.
- Obtenga el identificador de subproceso de Teams para las operaciones de llamadas mediante el Probador de Graph. Más información sobre cómo crear el identificador de subproceso de chat.
Instalación
Creación del proyecto
En Visual Studio, cree un proyecto con la plantilla Aplicación vacía (Windows universal) para configurar una aplicación para la Plataforma universal de Windows (UWP) de una sola página.
Instalar el paquete
Haga clic con el botón derecho en su proyecto y vaya a Manage Nuget Packages
para instalar la versión Azure.Communication.Calling.WindowsClient
1.2.0-beta.1 o superior. Asegúrese de que la casilla Incluir versión preliminar esté activada.
Solicitar acceso
Vaya a Package.appxmanifest
y seleccione Capabilities
.
Compruebe Internet (Client)
y Internet (Client & Server)
para obtener acceso entrante y saliente a Internet. Compruebe Microphone
para acceder a la fuente de audio del micrófono y Webcam
para acceder a la fuente de vídeo de la cámara.
Instalación del marco de la aplicación
Es necesario configurar un diseño básico para adjuntar la lógica. Para hacer una llamada saliente, se necesita un elemento TextBox
para proporcionar el id. de usuario del destinatario. También se necesita un botón Start/Join call
y un botón Hang up
. También se incluyen en este ejemplo las casillas Mute
y BackgroundBlur
para mostrar las características de alternancia de estados de audio y efectos de vídeo.
Abra MainPage.xaml
del proyecto y agregue el nodo Grid
a 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 MainPage.xaml.cs
y reemplace el contenido por la siguiente implementación:
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 objetos
La siguiente tabla enumera las clases y las interfaces controlan algunas de las características principales del SDK de llamadas de Azure Communication Services:
Nombre | Descripción |
---|---|
CallClient |
CallClient es el punto de entrada principal al SDK de llamada. |
TeamsCallAgent |
TeamsCallAgent se utiliza para iniciar y administrar las llamadas. |
TeamsCommunicationCall |
TeamsCommunicationCall se usa para administrar una llamada en curso. |
CallTokenCredential |
CallTokenCredential se usa como la credencial del token para crear una instancia de TeamsCallAgent . |
CallIdentifier |
CallIdentifier se usa para representar la identidad del usuario, que puede ser una de las opciones siguientes: MicrosoftTeamsUserCallIdentifier , UserCallIdentifier , PhoneNumberCallIdentifier etc. |
Autenticar el cliente
Inicialice una instancia TeamsCallAgent
con un token de acceso de usuario que nos permitirá realizar y recibir llamadas y, opcionalmente, obtener una instancia de DeviceManager para consultar las configuraciones de dispositivo cliente.
En el código siguiente, reemplace <AUTHENTICATION_TOKEN>
por un token de acceso de usuario. Consulte la documentación relativa al token de acceso de usuario si aún no tiene ningún token disponible.
Agregue la función InitCallAgentAndDeviceManagerAsync
, que arranca el SDK. Este asistente se puede personalizar para cumplir los requisitos de la aplicación.
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 una llamada
Agregue la implementación a CallButton_Click
para iniciar varios tipos de llamadas con el objeto teamsCallAgent
que creamos y enlazar controladores de eventos RemoteParticipantsUpdated
y StateChanged
en el 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;
}
}
Finalizar una llamada
Finalice la llamada actual cuando se haga clic en el botón Hang up
. Agregue la implementación al HangupButton_Click para finalizar una llamada y detenga la vista previa y los flujos 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 });
}
}
Alternar desactivar o activar audio
Desactive el audio saliente al hacer clic en el botón Mute
. Agregue la implementación al MuteLocal_Click para desactivar el audio de la llamada.
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
}
}
Iniciar la llamada
Una vez obtenido un objeto StartTeamsCallOptions
, TeamsCallAgent
se puede usar para iniciar la llamada de Teams:
private async Task<TeamsCommunicationCall> StartCteCallAsync(string cteCallee)
{
var options = new StartTeamsCallOptions();
var teamsCall = await this.teamsCallAgent.StartCallAsync( new MicrosoftTeamsUserCallIdentifier(cteCallee), options);
return call;
}
Aceptar una llamada entrante
El receptor de eventos TeamsIncomingCallReceived
se configura en el asistente de arranque del SDK InitCallAgentAndDeviceManagerAsync
.
this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;
La aplicación tiene la oportunidad de configurar cómo se debe aceptar la llamada entrante, como los tipos de flujo de vídeo y audio.
private async void OnIncomingCallAsync(object sender, TeamsIncomingCallReceivedEventArgs args)
{
var teamsIncomingCall = args.IncomingCall;
var acceptteamsCallOptions = new AcceptTeamsCallOptions() { };
teamsCall = await teamsIncomingCall.AcceptAsync(acceptteamsCallOptions);
teamsCall.StateChanged += OnStateChangedAsync;
}
Unirse a una llamada de Teams
El usuario también puede unirse a una llamada existente pasando un vínculo
TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
JoinTeamsCallOptions options = new JoinTeamsCallOptions();
TeamsCall call = await teamsCallAgent.JoinAsync(link, options);
Supervisión del evento de cambio de estado de la llamada y respuesta a este
El evento StateChanged
en el objeto TeamsCommunicationCall
se desencadena cuando una llamada en curso realiza transacciones de un estado a otro. La aplicación ofrece las oportunidades para reflejar los cambios de estado en la interfaz de usuario o insertar lógicas de negocios.
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;
}
}
}
Ejecución del código
Puede compilar y ejecutar el código en Visual Studio. Para las plataformas de la solución, se admiten ARM64
, x64
y x86
.
Para hacer una llamada saliente, proporcione un identificador de usuario en el campo de texto y haga clic en el botón Start Call/Join
. La llamada a 8:echo123
lo conecta a un bot de eco, lo que resulta ideal como introducción y para verificar que los dispositivos de audio funcionen.
Comience a usar Azure Communication Services con el SDK de llamadas de Azure Communication Services para agregar llamadas de voz y vídeo 1:1 a la aplicación. Aprenderá a iniciar llamadas y a responder a ellas con Calling SDK de Azure Communication Services para Java.
Código de ejemplo
Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.
Requisitos previos
- Una cuenta de Azure con una suscripción activa. Cree una cuenta gratuita.
- Android Studio, para crear la aplicación de Android.
- Un recurso de Communication Services implementado. Cree un recurso de Communication Services. Debe registrar la cadena de conexión para esta guía de inicio rápido.
- Un token de acceso de usuario para su instancia de Azure Communication Services.
- Obtenga el identificador de subproceso de Teams para las operaciones de llamadas mediante el Probador de Graph. Más información sobre cómo crear el identificador de subproceso de chat.
Instalación
Creación de una aplicación de Android con una actividad vacía
En Android Studio, seleccione Start a new Android Studio project (Iniciar un nuevo proyecto de Android Studio).
Seleccione la plantilla de proyecto "Actividad vacía" en "Teléfono y tableta".
Seleccione el SDK mínimo de la "API 26: Android 8.0 (Oreo)" o una versión posterior.
Instalar el paquete
Busque el nivel de proyecto build.gradle y asegúrese de agregar mavenCentral()
a la lista de repositorios en buildscript
y allprojects
.
buildscript {
repositories {
...
mavenCentral()
...
}
}
allprojects {
repositories {
...
mavenCentral()
...
}
}
A continuación, en el nivel de módulo build.gradle, agregue las siguientes líneas a las secciones de dependencias y Android.
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'
...
}
Adición de permisos al manifiesto de aplicación
Para solicitar los permisos necesarios para realizar una llamada, primero se deben declarar en el manifiesto de aplicación (app/src/main/AndroidManifest.xml
). Reemplace el contenido del archivo por el código siguiente:
<?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>
Configuración del diseño de la aplicación
Se necesitan dos entradas: una entrada de texto para el identificador del destinatario y un botón para realizar la llamada. Estas entradas se pueden agregar a través del diseñador o editando el XML de diseño. Cree un botón con un identificador de call_button
y una entrada de texto de callee_id
. Vaya hasta (app/src/main/res/layout/activity_main.xml
) y reemplace el contenido del archivo con el código siguiente:
<?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>
Creación de los enlaces y scaffolding de actividades principales
Con el diseño creado, se pueden agregar los enlaces, así como el scaffolding básico de la actividad. La actividad administra la solicitud de los permisos en tiempo de ejecución, la creación del agente de llamadas de Teams y la realización de llamadas cuando se presiona el botón. Cada proceso se tratará en su propia sección. El método onCreate
se reemplazará para invocar getAllPermissions
y createTeamsAgent
, así como para agregar los enlaces para el botón de llamada. Esto solo ocurre una vez cuando se crea la actividad. Para obtener más información sobre onCreate
, consulte la guía Descripción del ciclo de vida de la actividad.
Vaya a MainActivity.java y reemplace el contenido por el siguiente 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
}
}
Solicitud de permisos en tiempo de ejecución
Para Android 6.0 y las versiones posteriores (nivel de API 23) y targetSdkVersion
23 o las versiones posteriores, los permisos se conceden en tiempo de ejecución en lugar de cuando se instala la aplicación. A fin de admitirlo, getAllPermissions
se puede implementar para llamar a ActivityCompat.checkSelfPermission
y ActivityCompat.requestPermissions
para cada permiso necesario.
/**
* 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);
}
}
Nota
Al diseñar la aplicación, tenga en cuenta cuándo deben solicitarse estos permisos. Se deben solicitar a medida que sean necesarios, pero no con anterioridad. Para más información, consulte la Guía de permisos de Android.
Modelo de objetos
Las siguientes clases e interfaces controlan algunas de las características principales del SDK de llamadas de Azure Communication Services:
Nombre | Descripción |
---|---|
CallClient |
CallClient es el punto de entrada principal al SDK de llamada. |
TeamsCallAgent |
TeamsCallAgent se utiliza para iniciar y administrar las llamadas. |
TeamsCall |
TeamsCall se usa para representar una llamada de Teams. |
CommunicationTokenCredential |
CommunicationTokenCredential se usa como la credencial del token para crear una instancia de TeamsCallAgent . |
CommunicationIdentifier |
CommunicationIdentifier se usa como otro tipo de participante que podría formar parte de una llamada. |
Creación de un agente a partir del token de acceso de usuario
Con un token de usuario, se puede crear una instancia del agente de llamadas autenticado. Por lo general, este token se genera desde un servicio con autenticación específica para la aplicación. Para obtener más información sobre los tokens de acceso de usuario, consulte la guía Tokens de acceso de usuario.
En el inicio rápido, reemplace <User_Access_Token>
por un token de acceso de usuario generado para el recurso de Azure Communication Service.
/**
* 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();
}
}
Inicio de una llamada mediante el agente de llamadas
La llamada se puede realizar a través del agente de llamadas de Teams, y solo es necesario proporcionar una lista de identificadores de destinatarios y las opciones de llamada. Para el inicio rápido, se usarán las opciones de llamada predeterminadas sin vídeo y un id. de destinatario único de la entrada de texto.
/**
* 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);
}
Responder a una llamada
La aceptación de una llamada se puede realizar mediante el agente de llamada de Teams mediante solo una referencia al contexto actual.
public void acceptACall(TeamsIncomingCall teamsIncomingCall){
teamsIncomingCall.accept(this);
}
Unirse a una llamada de Teams
Un usuario puede unirse a una llamada existente pasando un vínculo.
/**
* Join a call using a teams meeting link.
*/
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
TeamsCall call = teamsCallAgent.join(this, link);
}
Unirse a una llamada de Teams con opciones
También podemos unirnos a una llamada existente con opciones preestablecidas, como la desactivación del audio.
/**
* 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 el agente de escucha de llamadas entrantes
Para poder detectar llamadas entrantes y otras acciones no realizadas por este usuario, los agentes de escucha deben estar configurados.
private TeamsIncomingCall teamsincomingCall;
teamsCallAgent.addOnIncomingCallListener(this::handleIncomingCall);
private void handleIncomingCall(TeamsIncomingCall incomingCall) {
this.teamsincomingCall = incomingCall;
}
Inicio de la aplicación y llamada al bot de eco
Ahora se puede iniciar la aplicación con el botón "Ejecutar aplicación" de la barra de herramientas (Mayús + F10). Para verificar que puede realizar llamadas, llame a 8:echo123
. Se reproducirá un mensaje grabado previamente y, luego, se repetirá su mensaje.
Puede empezar a usar Azure Communication Services con Calling SDK de Azure Communication Services para agregar llamadas de voz y vídeo una a una a la aplicación. Aprenderá a iniciar videollamadas y a responder a ellas con Calling SDK de Azure Communication Services para iOS mediante la identidad de Teams.
Código de ejemplo
Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.
Requisitos previos
- Obtenga una cuenta de Azure con una suscripción activa. Cree una cuenta gratuita.
- Mac con Xcode, junto con un certificado de desarrollador válido instalado en el Llavero.
- Cree un recurso activo de Communication Services. Cree un recurso de Communication Services. Debe registrar la cadena de conexión para esta guía de inicio rápido.
- Un token de acceso de usuario para su instancia de Azure Communication Services.
- Obtenga el identificador de subproceso de Teams para las operaciones de llamadas mediante el Probador de Graph. Más información sobre cómo crear el identificador de hilo de chat.
Instalación
Creación del proyecto de Xcode
En Xcode, cree un nuevo proyecto de iOS y seleccione la plantilla Aplicación de una vista. En este tutorial se usa el marco SwiftUI, por lo que debe establecer el lenguaje en Swift y la interfaz de usuario en SwiftUI. Durante este inicio rápido, no va a crear pruebas. No dude en desactivar Incluir pruebas.
Instalación de CocoaPods
Utilice esta guía para instalar CocoaPods en su Mac.
Instalación del paquete y las dependencias con CocoaPods
Para crear un
Podfile
para la aplicación, abra el terminal, vaya a la carpeta del proyecto y ejecute el archivo init del pod.Agregue el siguiente código al
Podfile
y guarde los cambios. Consulte las versiones de soporte técnico del SDK.
platform :ios, '13.0'
use_frameworks!
target 'VideoCallingQuickstart' do
pod 'AzureCommunicationCalling', '~> 2.10.0'
end
Ejecute la instalación del pod.
Abra
.xcworkspace
con Xcode.
Solicite acceso al micrófono y a la cámara.
Para acceder al micrófono y a la cámara del dispositivo, debe actualizar la lista de propiedades de información de la aplicación con NSMicrophoneUsageDescription
y NSCameraUsageDescription
. Usted establece el valor asociado a una cadena que incluye el cuadro de diálogo empleado por el sistema para solicitar acceso al usuario.
Haga clic con el botón derecho en la entrada Info.plist
del árbol del proyecto y seleccione Open As > Source Code (Abrir como > Código fuente). Agregue las líneas siguientes a la sección <dict>
de nivel superior y guarde el archivo.
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>
<key>NSCameraUsageDescription</key>
<string>Need camera access for video calling</string>
Instalación del marco de la aplicación
Abra el archivo ContentView.swift
del proyecto y agregue una declaración de importación en la parte superior del archivo para importar la biblioteca AzureCommunicationCalling
y AVFoundation
. AVFoundation se usa para capturar el permiso de audio del código.
import AzureCommunicationCalling
import AVFoundation
Modelo de objetos
Las siguientes clases e interfaces controlan algunas de las características principales del SDK de llamadas de Azure Communication Services para iOS.
Nombre | Descripción |
---|---|
CallClient |
CallClient es el punto de entrada principal al SDK de llamada. |
TeamsCallAgent |
TeamsCallAgent se utiliza para iniciar y administrar las llamadas. |
TeamsIncomingCall |
TeamsIncomingCall se usa para aceptar o rechazar la llamadas entrantes de Teams. |
CommunicationTokenCredential |
CommunicationTokenCredential se usa como la credencial del token para crear una instancia de TeamsCallAgent . |
CommunicationIdentifier |
CommunicationIdentifier se usa para representar la identidad del usuario, que puede ser una de las opciones siguientes: CommunicationUserIdentifier , PhoneNumberIdentifier o CallingApplication . |
Creación del agente de llamadas de Teams
Reemplace la implementación del struct
de ContentView por algunos controles simples de la interfaz de usuario que permitan a los usuarios iniciar y finalizar una llamada. En este inicio rápido, agregaremos la lógica de negocios a estos controles.
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 el cliente
Para poder inicializar una instancia de TeamsCallAgent
, necesita un token de acceso de usuario que permita realizar y recibir llamadas. Consulte la documentación sobre el token de acceso de usuario si no tiene ningún token disponible.
Una vez que tenga un token, agregue el código siguiente a la devolución de llamada onAppear
en el archivo ContentView.swift
. Debe reemplazar <USER ACCESS TOKEN>
por un token de acceso de usuario válido para el recurso:
var userCredential: CommunicationTokenCredential?
do {
userCredential = try CommunicationTokenCredential(token: "<USER ACCESS TOKEN>")
} catch {
print("ERROR: It was not possible to create user credential.")
return
}
Inicialización de CallAgent de Teams y acceso al Administrador de dispositivos
Para crear una instancia de TeamsCallAgent
a partir de CallClient
, use el método callClient.createTeamsCallAgent
que devuelve de manera asincrónica un objeto TeamsCallAgent
una vez que se inicializa. DeviceManager
permite enumerar los dispositivos locales que se pueden usar en una llamada para transmitir secuencias de audio o vídeo. También permite solicitar permiso a un usuario para acceder al micrófono o a la cámara.
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")
}
}
}
}
Solicitud de permisos
Para solicitar permisos de audio y vídeo, es necesario agregar el código siguiente a la devolución de llamada onAppear
.
AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
if granted {
AVCaptureDevice.requestAccess(for: .video) { (videoGranted) in
/* NO OPERATION */
}
}
}
Realización de una llamada saliente
El método startCall
se establece como la acción que se lleva a cabo cuando se toca el botón de Iniciar llamada. En este inicio rápido, de manera predeterminada, las llamadas salientes son solo de audio. Para iniciar una llamada con vídeo, debemos establecer VideoOptions
con LocalVideoStream
y pasarlo con startCallOptions
para definir las opciones iniciales de la llamada.
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.
}
Unirse a reunión de Teams
El método join
permite al usuario unirse a una reunión de 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
y RemotePariticipantObserver
se usan para administrar participantes remotos y eventos que tienen lugar en mitad de la llamada. Establecemos los observadores en la función 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")
}
}
Respuesta a una llamada entrante
Para responder a una llamada entrante, implemente un objeto TeamsIncomingCallHandler
para que aparezca el banner de llamada entrante y poder responder o rechazar la llamada. Inserte la siguiente implementación en 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
}
}
}
Es necesario crear una instancia de TeamsIncomingCallHandler
. Para ello, agregue el código siguiente a la devolución de llamada onAppear
en ContentView.swift
:
Establezca un delegado para TeamsCallAgent
una vez que TeamsCallAgent
se haya creado correctamente:
self.teamsCallAgent!.delegate = incomingCallHandler
Cuando hay una llamada entrante, TeamsIncomingCallHandler
llama a la función showIncomingCallBanner
para mostrar el botón answer
y decline
.
func showIncomingCallBanner(_ incomingCall: TeamsIncomingCall) {
self.teamsIncomingCall = incomingCall
}
Las acciones asociadas a answer
y decline
se implementan como el código siguiente. Para responder a la llamada con vídeo, es necesario activar el vídeo local y establecer las opciones de AcceptCallOptions
con 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.
}
}
Suscripción a los eventos
Podemos implementar una clase TeamsCallObserver
para suscribirnos a una colección de eventos a los que se enviarán notificaciones si los valores cambian durante la llamada.
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
}
}
}
Ejecución del código
Para compilar y ejecutar la aplicación en el simulador de iOS, seleccione Product > Run (Producto > Ejecutar) o use el método abreviado de teclado (⌘-R).
Limpieza de recursos
Si quiere limpiar y quitar una suscripción a Communication Services, puede eliminar el recurso o grupo de recursos. Al eliminar el grupo de recursos, también se elimina cualquier otro recurso que esté asociado a él. Obtenga más información sobre la limpieza de recursos.
Pasos siguientes
Para más información, consulte los siguientes artículos.
- Consulte Introducción al ejemplo de llamada web.
- Más información sobre las Funcionalidades del SDK de llamadas
- Más información sobre cómo funciona la llamada