Compartir a través de


Inicio rápido: Incorporación de una aplicación de llamadas a un operador automático de Teams

En este inicio rápido, aprenderá a iniciar una llamada de un usuario de Azure Communication Services a un operador automático de Teams. Lo logrará con los pasos siguientes:

  1. Habilitar la federación de recurso de Azure Communication Services con el inquilino de Teams.
  2. Seleccione o cree el operador automático de Teams a través del Centro de Administración de Teams.
  3. Obtenga la dirección de correo electrónico del operador automático a través del Centro de Administración de Teams.
  4. Obtenga el id. de objeto del operador automático a través de Graph API.
  5. Iniciar una llamada con el SDK de llamadas de Azure Communication Services.

Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.

Habilitar interoperabilidad en el inquilino de Teams

El usuario de Microsoft Entra con el rol de administrador de Teams puede ejecutar el cmdlet de PowerShell con el módulo de MicrosoftTeams para habilitar el recurso de Communication Services en el inquilino.

1. Preparar el módulo de Microsoft Teams

En primer lugar, abra PowerShell y valide la existencia del módulo de Teams con el siguiente comando:

Get-module *teams* 

Si no ve el módulo MicrosoftTeams, instálelo primero. Para instalar el módulo debe ejecutar PowerShell como administrador. Luego, ejecute el siguiente comando:

	Install-Module -Name MicrosoftTeams

Se le informará sobre los módulos que se instalarán, que puede confirmar con una respuesta Y o A. Si el módulo está instalado pero no está actualizado, puede ejecutar el siguiente comando para actualizar el módulo:

	Update-Module MicrosoftTeams

2. Conexión al módulo de Microsoft Teams

Cuando el módulo esté instalado y listo, puede conectarse al módulo MicrosoftTeams con el siguiente comando. Se le pedirá que inicie sesión con una ventana interactiva. La cuenta de usuario que va a usar debe tener permisos de administrador de Teams. De lo contrario, puede obtener una respuesta access denied en los pasos siguientes.

Connect-MicrosoftTeams

3. Habilitar la configuración del inquilino

La interoperabilidad con los recursos de Communication Services se controla mediante la configuración de inquilinos y la directiva asignada. El inquilino de Teams tiene una configuración de inquilino único y los usuarios de Teams tienen asignada directiva global o directiva personalizada. Para más información, consulte Asignación de directivas en Teams.

Después de iniciar sesión correctamente, puede ejecutar el cmdlet Set-CsTeamsAcsFederationConfiguration para habilitar el recurso de Communication Services en el inquilino. Reemplace el texto IMMUTABLE_RESOURCE_ID por un identificador de recurso inmutable en el recurso de comunicación. Puede encontrar más detalles sobre cómo obtener esta información aquí.

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4. Habilitar una directiva de inquilino

A cada usuario de Teams se le ha asignado un External Access Policy que determina si los usuarios de Communication Services pueden llamar a este usuario de Teams. Use el cmdlet Set-CsExternalAccessPolicy para asegurarse de que la directiva asignada al usuario de Teams ha establecido EnableAcsFederationAccess en $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Crear o seleccionar Operador automático de Teams

El operador automático de Teams es un sistema que proporciona un sistema de control de llamadas automatizado para las llamadas entrantes. Sirve de recepcionista virtual, permitiendo que los autores de llamadas se enruten automáticamente a la persona o departamento adecuados sin necesidad de un operador humano. Puede seleccionar un operador automático existente o crearlo a través del Centro de administración de Teams.

Obtenga más información sobre cómo crear un operador automático mediante el Centro de administración de Teams aquí.

Buscar el id. de objeto para el operador automático

Después de crear el operador automático, es necesario encontrar el id. de objeto correlacionado para usarlo más adelante para las llamadas. El id. de objeto está conectado a la cuenta de recursos que se adjuntó al operador automático: abra la pestaña Cuentas de recursos en Administración de Teams y busque el correo electrónico de la cuenta. Captura de pantalla de las cuentas de recursos en el portal de administración de Teams. Toda la información necesaria para la cuenta de recursos se puede encontrar en Explorador de Microsoft Graph utilizando este correo electrónico en la búsqueda.

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

En los resultados, encontraremos el campo "Id."

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

Prerrequisitos

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

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 next del SDK de llamadas de Azure Communication Services.

npm install @azure/communication-common@next --save
npm install @azure/communication-calling@next --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 - Calling Web SDK</title>
    </head>
    <body>
        <h4>Azure Communication Services - Calling Web SDK</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">Initialize Call Agent</button>
        <br>
        <br>
        <input id="application-object-id"
            type="text"
            placeholder="Enter application objectId identity in format: 'APP_GUID'"
            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.
CallAgent Se utiliza para iniciar y administrar las llamadas.
DeviceManager Se usa para administrar dispositivos multimedia.
Call Se usa para representar una llamada.
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 client.js que contendrá la lógica de la aplicación para esta guía de inicio rápido. Agregue el código siguiente a client.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 callAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let applicationObjectId = document.getElementById('application-object-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 CallAgent instance with a AzureCommunicationTokenCredential via created CallClient. CallAgent 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());
        callAgent = await callClient.createCallAgent(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.
        callAgent.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 an Teams Auto attendant
 * 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 = callAgent.startCall([{ teamsAppId: applicationObjectId.value.trim(), cloud:"public" }], { 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 `CallAgent.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();
});

Agregar el código del servidor local del paquete web

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: './client.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

Pasos manuales para configurar la llamada:

  1. Abra el explorador web y vaya a http://localhost:8080/.
  2. Escribir un 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.
  3. Haga clic en los botones "Inicializar agente de llamada".
  4. Escriba el id. de objeto del operador automático y seleccione el botón "Iniciar llamada". La aplicación iniciará la llamada saliente al operador automático con el id. de objeto especificado.
  5. La llamada está conectada al operador automático.
  6. El usuario de Communication Services se enruta a través del operador automático en función de su configuración.

Importante

Esta característica de Azure Communication Services se encuentra actualmente en versión preliminar.

Las API y los SDK en versión preliminar se proporcionan sin contrato de nivel de servicio. Se recomienda no usarlos para las cargas de trabajo de producción. Es posible que algunas características no sean compatibles o que sus funcionalidades estén limitadas.

Para obtener más información, consulte Términos de uso complementarios para las Versiones preliminares de Microsoft Azure.

En este inicio rápido, aprenderá a iniciar una llamada de un usuario de Azure Communication Services a un operador automático de Teams. Lo logrará con los pasos siguientes:

  1. Habilitar la federación de recurso de Azure Communication Services con el inquilino de Teams.
  2. Seleccione o cree el operador automático de Teams a través del Centro de Administración de Teams.
  3. Obtenga la dirección de correo electrónico del operador automático a través del Centro de Administración de Teams.
  4. Obtenga el id. de objeto del operador automático a través de Graph API.
  5. Iniciar una llamada con el SDK de llamadas de Azure Communication Services.

Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.

Habilitar interoperabilidad en el inquilino de Teams

El usuario de Microsoft Entra con el rol de administrador de Teams puede ejecutar el cmdlet de PowerShell con el módulo de MicrosoftTeams para habilitar el recurso de Communication Services en el inquilino.

1. Preparar el módulo de Microsoft Teams

En primer lugar, abra PowerShell y valide la existencia del módulo de Teams con el siguiente comando:

Get-module *teams* 

Si no ve el módulo MicrosoftTeams, instálelo primero. Para instalar el módulo debe ejecutar PowerShell como administrador. Luego, ejecute el siguiente comando:

	Install-Module -Name MicrosoftTeams

Se le informará sobre los módulos que se instalarán, que puede confirmar con una respuesta Y o A. Si el módulo está instalado pero no está actualizado, puede ejecutar el siguiente comando para actualizar el módulo:

	Update-Module MicrosoftTeams

2. Conexión al módulo de Microsoft Teams

Cuando el módulo esté instalado y listo, puede conectarse al módulo MicrosoftTeams con el siguiente comando. Se le pedirá que inicie sesión con una ventana interactiva. La cuenta de usuario que va a usar debe tener permisos de administrador de Teams. De lo contrario, puede obtener una respuesta access denied en los pasos siguientes.

Connect-MicrosoftTeams

3. Habilitar la configuración del inquilino

La interoperabilidad con los recursos de Communication Services se controla mediante la configuración de inquilinos y la directiva asignada. El inquilino de Teams tiene una configuración de inquilino único y los usuarios de Teams tienen asignada directiva global o directiva personalizada. Para más información, consulte Asignación de directivas en Teams.

Después de iniciar sesión correctamente, puede ejecutar el cmdlet Set-CsTeamsAcsFederationConfiguration para habilitar el recurso de Communication Services en el inquilino. Reemplace el texto IMMUTABLE_RESOURCE_ID por un identificador de recurso inmutable en el recurso de comunicación. Puede encontrar más detalles sobre cómo obtener esta información aquí.

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4. Habilitar una directiva de inquilino

A cada usuario de Teams se le ha asignado un External Access Policy que determina si los usuarios de Communication Services pueden llamar a este usuario de Teams. Use el cmdlet Set-CsExternalAccessPolicy para asegurarse de que la directiva asignada al usuario de Teams ha establecido EnableAcsFederationAccess en $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Crear o seleccionar Operador automático de Teams

El operador automático de Teams es un sistema que proporciona un sistema de control de llamadas automatizado para las llamadas entrantes. Sirve de recepcionista virtual, permitiendo que los autores de llamadas se enruten automáticamente a la persona o departamento adecuados sin necesidad de un operador humano. Puede seleccionar un operador automático existente o crearlo a través del Centro de administración de Teams.

Obtenga más información sobre cómo crear un operador automático mediante el Centro de administración de Teams aquí.

Buscar el id. de objeto para el operador automático

Después de crear el operador automático, es necesario encontrar el id. de objeto correlacionado para usarlo más adelante para las llamadas. El id. de objeto está conectado a la cuenta de recursos que se adjuntó al operador automático: abra la pestaña Cuentas de recursos en Administración de Teams y busque el correo electrónico de la cuenta. Captura de pantalla de las cuentas de recursos en el portal de administración de Teams. Toda la información necesaria para la cuenta de recursos se puede encontrar en Explorador de Microsoft Graph utilizando este correo electrónico en la búsqueda.

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

En los resultados, encontraremos el campo "Id."

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

Para usarlo en la aplicación que realiza la llamada, es necesario agregar un prefijo a este identificador. Actualmente se admite lo siguiente:

  • Operador automático de nube pública: 28:orgid:<id>
  • Operador automático de nube gubernamental: 28:gcch:<id>

Requisitos previos

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

Captura de pantalla que muestra el botón

Seleccione la plantilla de proyecto "Actividad de vistas vacía" en "Teléfono y tableta".

Captura de pantalla que muestra la opción

Seleccione el SDK mínimo de la "API 26: Android 8.0 (Oreo)" o una versión posterior.

Captura de pantalla que muestra la opción

Instalar el paquete

Busque el nivel de proyecto settings.gradle.kts y asegúrese de comprobar mavenCentral() en la lista de repositorios en pluginManagement y dependencyResolutionManagement.

pluginManagement {
    repositories {
    ...
        mavenCentral()
    ...
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    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 {
    ...
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    ...
    implementation ("com.azure.android:azure-communication-calling:2.6.0")
    ...
}

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.acsquickstart">

    <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="${launchApp}">

    <EditText
        android:id="@+id/callee_id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Callee Id"
        android:inputType="textPersonName"
        android:layout_marginTop="100dp"
        android:layout_marginHorizontal="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="46dp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <Button
            android:id="@+id/call_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Call" />

        <Button
            android:id="@+id/hangup_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hangup" />

    </LinearLayout>

    <TextView
        android:id="@+id/status_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="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 y la realización de llamadas cuando se presione el botón. El método onCreate se reemplazará para invocar getAllPermissions y createAgent, 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.acsquickstart;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.concurrent.ExecutionException;

import com.azure.android.communication.common.CommunicationIdentifier;
import com.azure.android.communication.common.CommunicationUserIdentifier;
import com.azure.android.communication.calling.Call;
import com.azure.android.communication.calling.CallAgent;
import com.azure.android.communication.calling.CallClient;
import com.azure.android.communication.calling.HangUpOptions;
import com.azure.android.communication.common.CommunicationTokenCredential;
import com.azure.android.communication.calling.StartCallOptions;

public class MainActivity extends AppCompatActivity {
    private static final String[] allPermissions = new String[] { Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE };
    private static final String UserToken = "<User_Access_Token>";

    TextView statusBar;

    private CallAgent agent;
    private Call call;
    private Button callButton;

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

        callButton = findViewById(R.id.call_button);

        getAllPermissions();
        createAgent();
        callButton.setOnClickListener(l -> startCall());

        Button hangupButton = findViewById(R.id.hangup_button);
        hangupButton.setOnClickListener(l -> endCall());

        statusBar = findViewById(R.id.status_bar);
        
        setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
    }

    /**
     * Start a call
     */
    private void startCall() {
        if (UserToken.startsWith("<")) {
            Toast.makeText(this, "Please enter token in source code", Toast.LENGTH_SHORT).show();
            return;
        }

        EditText calleeIdView = findViewById(R.id.callee_id);
        String calleeId = calleeIdView.getText().toString();
        if (calleeId.isEmpty()) {
            Toast.makeText(this, "Please enter callee", Toast.LENGTH_SHORT).show();
            return;
        }
        ArrayList<CommunicationIdentifier> participants = new ArrayList<>();
        participants.add(new MicrosoftTeamsAppIdentifier(calleeId));
        StartCallOptions options = new StartCallOptions();
        call = agent.startCall(
                getApplicationContext(),
                participants,
                options);
        call.addOnStateChangedListener(p -> setStatus(call.getState().toString()));
    }

    /**
     * Ends the call previously started
     */
    private void endCall() {
        try {
            call.hangUp(new HangUpOptions()).get();
        } catch (ExecutionException | InterruptedException e) {
            Toast.makeText(this, "Unable to hang up call", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Create the call agent
     */
    private void createAgent() {
        try {
            CommunicationTokenCredential credential = new CommunicationTokenCredential(UserToken);
            agent = new CallClient().createCallAgent(getApplicationContext(), credential).get();
        } catch (Exception ex) {
            Toast.makeText(getApplicationContext(), "Failed to create call agent.", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Ensure all permissions were granted, otherwise inform the user permissions are missing.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) {
        boolean allPermissionsGranted = true;
        for (int result : grantResults) {
            allPermissionsGranted &= (result == PackageManager.PERMISSION_GRANTED);
        }
        if (!allPermissionsGranted) {
            Toast.makeText(this, "All permissions are needed to make the call.", Toast.LENGTH_LONG).show();
            finish();
        }
    }

    /**
     * Shows message in the status bar
     */
    private void setStatus(String status) {
        runOnUiThread(() -> statusBar.setText(status));
    }
}

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() {
    ArrayList<String> permissionsToAskFor = new ArrayList<>();
    for (String permission : allPermissions) {
        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.
CallAgent CallAgent se utiliza para iniciar y administrar las llamadas.
CommunicationTokenCredential CommunicationTokenCredential se usa como la credencial del token para crear una instancia de CallAgent.
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 call agent for placing calls
 */
private void createAgent() {
    String userToken = "<User_Access_Token>";

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

Ejecución del código

Ahora se puede iniciar la aplicación con el botón "Ejecutar aplicación" de la barra de herramientas.

Pasos manuales para configurar la llamada:

  1. Inicie la aplicación mediante Android Studio.
  2. Escriba el id. de objeto del operador automático (con prefijo) y seleccione el botón "Iniciar llamada". La aplicación iniciará la llamada saliente al operador automático con el id. de objeto especificado.
  3. La llamada está conectada al operador automático.
  4. El usuario de Communication Services se enruta a través del operador automático en función de su configuración.

Importante

Esta característica de Azure Communication Services se encuentra actualmente en versión preliminar.

Las API y los SDK en versión preliminar se proporcionan sin contrato de nivel de servicio. Se recomienda no usarlos para las cargas de trabajo de producción. Es posible que algunas características no sean compatibles o que sus funcionalidades estén limitadas.

Para obtener más información, consulte Términos de uso complementarios para las Versiones preliminares de Microsoft Azure.

En este inicio rápido, aprenderá a iniciar una llamada de un usuario de Azure Communication Services a un operador automático de Teams. Lo logrará con los pasos siguientes:

  1. Habilitar la federación de recurso de Azure Communication Services con el inquilino de Teams.
  2. Seleccione o cree el operador automático de Teams a través del Centro de Administración de Teams.
  3. Obtenga la dirección de correo electrónico del operador automático a través del Centro de Administración de Teams.
  4. Obtenga el id. de objeto del operador automático a través de Graph API.
  5. Iniciar una llamada con el SDK de llamadas de Azure Communication Services.

Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.

Habilitar interoperabilidad en el inquilino de Teams

El usuario de Microsoft Entra con el rol de administrador de Teams puede ejecutar el cmdlet de PowerShell con el módulo de MicrosoftTeams para habilitar el recurso de Communication Services en el inquilino.

1. Preparar el módulo de Microsoft Teams

En primer lugar, abra PowerShell y valide la existencia del módulo de Teams con el siguiente comando:

Get-module *teams* 

Si no ve el módulo MicrosoftTeams, instálelo primero. Para instalar el módulo debe ejecutar PowerShell como administrador. Luego, ejecute el siguiente comando:

	Install-Module -Name MicrosoftTeams

Se le informará sobre los módulos que se instalarán, que puede confirmar con una respuesta Y o A. Si el módulo está instalado pero no está actualizado, puede ejecutar el siguiente comando para actualizar el módulo:

	Update-Module MicrosoftTeams

2. Conexión al módulo de Microsoft Teams

Cuando el módulo esté instalado y listo, puede conectarse al módulo MicrosoftTeams con el siguiente comando. Se le pedirá que inicie sesión con una ventana interactiva. La cuenta de usuario que va a usar debe tener permisos de administrador de Teams. De lo contrario, puede obtener una respuesta access denied en los pasos siguientes.

Connect-MicrosoftTeams

3. Habilitar la configuración del inquilino

La interoperabilidad con los recursos de Communication Services se controla mediante la configuración de inquilinos y la directiva asignada. El inquilino de Teams tiene una configuración de inquilino único y los usuarios de Teams tienen asignada directiva global o directiva personalizada. Para más información, consulte Asignación de directivas en Teams.

Después de iniciar sesión correctamente, puede ejecutar el cmdlet Set-CsTeamsAcsFederationConfiguration para habilitar el recurso de Communication Services en el inquilino. Reemplace el texto IMMUTABLE_RESOURCE_ID por un identificador de recurso inmutable en el recurso de comunicación. Puede encontrar más detalles sobre cómo obtener esta información aquí.

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4. Habilitar una directiva de inquilino

A cada usuario de Teams se le ha asignado un External Access Policy que determina si los usuarios de Communication Services pueden llamar a este usuario de Teams. Use el cmdlet Set-CsExternalAccessPolicy para asegurarse de que la directiva asignada al usuario de Teams ha establecido EnableAcsFederationAccess en $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Crear o seleccionar Operador automático de Teams

El operador automático de Teams es un sistema que proporciona un sistema de control de llamadas automatizado para las llamadas entrantes. Sirve de recepcionista virtual, permitiendo que los autores de llamadas se enruten automáticamente a la persona o departamento adecuados sin necesidad de un operador humano. Puede seleccionar un operador automático existente o crearlo a través del Centro de administración de Teams.

Obtenga más información sobre cómo crear un operador automático mediante el Centro de administración de Teams aquí.

Buscar el id. de objeto para el operador automático

Después de crear el operador automático, es necesario encontrar el id. de objeto correlacionado para usarlo más adelante para las llamadas. El id. de objeto está conectado a la cuenta de recursos que se adjuntó al operador automático: abra la pestaña Cuentas de recursos en Administración de Teams y busque el correo electrónico de la cuenta. Captura de pantalla de las cuentas de recursos en el portal de administración de Teams. Toda la información necesaria para la cuenta de recursos se puede encontrar en Explorador de Microsoft Graph utilizando este correo electrónico en la búsqueda.

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

En los resultados, encontraremos el campo "Id."

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

Para usarlo en la aplicación que realiza la llamada, es necesario agregar un prefijo a este identificador. Actualmente se admite lo siguiente:

  • Operador automático de nube pública: 28:orgid:<id>
  • Operador automático de nube gubernamental: 28:gcch:<id>

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.

  • 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. También puede usar la CLI de Azure y ejecutar el comando siguiente con la cadena de conexión para crear un usuario y un token de acceso.

    az communication identity token issue --scope voip --connection-string "yourConnectionString"
    

    Para más información, consulte Uso de la CLI de Azure para crear y administrar tokens de acceso.

  • Compatibilidad mínima con las aplicaciones de llamada de Teams: 2.14.0-beta.1

Instalación

Creación del proyecto de Xcode

En Xcode, cree un proyecto de iOS y seleccione la plantilla App (Aplicación). En este tutorial se usa el marco SwiftUI, por lo que debe establecer Lenguaje en Swift e Interfaz de usuario en SwiftUI. Durante este inicio rápido, no va a crear pruebas. No dude en desactivar Incluir pruebas.

Captura de pantalla que muestra la ventana Nuevo proyecto en Xcode.

Instalación del paquete y las dependencias con CocoaPods

  1. Para crear un archivo Podfile para la aplicación, abra el terminal, vaya a la carpeta del proyecto y ejecute:

    pod init

  2. Agregue el código siguiente al podfile y guárdelo (Asegúrese de que "target" coincide con el nombre del proyecto):

    platform :ios, '13.0'
    use_frameworks!
    
    target 'AzureCommunicationCallingSample' do
      pod 'AzureCommunicationCalling', '~> 2.14.0-beta.1'
    end
    
  3. Ejecute pod install.

  4. Abra .xcworkspace con Xcode.

Solicitud de acceso al micrófono

Para acceder al micrófono del dispositivo, debe actualizar la lista de propiedades de información de la aplicación con el elemento NSMicrophoneUsageDescription. Puede establecer el valor asociado al elemento string que se incluirá en el cuadro de diálogo que el sistema usa para solicitar acceso al usuario.

Haga clic con el botón derecho en la entrada Info.plist del árbol del proyecto y seleccione Abrir como>Código fuente. Agregue las líneas siguientes a la sección <dict> de nivel superior y guarde el archivo.

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

Instalación del marco de la aplicación

Abra el archivo ContentView.swift del proyecto y agregue una declaración import en la parte superior del archivo para importar el elemento AzureCommunicationCalling library. Además, importe AVFoundation; lo necesitaremos para la solicitud de permiso de audio en el código.

import AzureCommunicationCalling
import AVFoundation

Reemplace la implementación de la estructura ContentView por algunos controles de interfaz de usuario simples que permitan a los usuarios iniciar y finalizar una llamada. En este inicio rápido, asociaremos la lógica de negocios a estos controles.

struct ContentView: View {
    @State var callee: String = ""
    @State var callClient: CallClient?
    @State var callAgent: CallAgent?
    @State var call: Call?

    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Who would you like to call?", text: $callee)
                    Button(action: startCall) {
                        Text("Start Call")
                    }.disabled(callAgent == nil)
                    Button(action: endCall) {
                        Text("End Call")
                    }.disabled(call == nil)
                }
            }
            .navigationBarTitle("Calling Quickstart")
        }.onAppear {
            // Initialize call agent
        }
    }

    func startCall() {
        // Ask permissions
        AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
            if granted {
                // Add start call logic
            }
        }
    }

    func endCall() {
        // Add end call logic
    }
}

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.
CallAgent CallAgent se utiliza para iniciar y administrar las llamadas.
CommunicationTokenCredential CommunicationTokenCredential se usa como la credencial del token para crear una instancia de CallAgent.
CommunicationUserIdentifier CommunicationUserIdentifier se usa para representar la identidad del usuario, que puede ser una de las opciones siguientes: CommunicationUserIdentifier, PhoneNumberIdentifier o CallingApplication.

Autenticar el cliente

Inicialice una instancia de CallAgent con un token de acceso de usuario que nos permita realizar y recibir llamadas.

En el código siguiente, tendrá que reemplazar <USER ACCESS TOKEN> por un token de acceso de usuario válido para el recurso. Consulte la documentación relativa al token de acceso de usuario si aún no tiene ningún token disponible.

Agregue el código siguiente a la devolución de llamada onAppear en ContentView.swift:

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

self.callClient = CallClient()

// Creates the call agent
self.callClient?.createCallAgent(userCredential: userCredential!) { (agent, error) in
    if error != nil {
        print("ERROR: It was not possible to create a call agent.")
        return
    }
    else {
        self.callAgent = agent
        print("Call agent successfully created.")
    }
}

Inicio de una llamada

El método startCall se establece como la acción que se llevará a cabo cuando se pulse el botón Iniciar llamada. Actualice la implementación para iniciar una llamada con ASACallAgent:

func startCall()
{
    // Ask permissions
    AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
        if granted {
            // start call logic
            let callees:[CommunicationIdentifier] = [MicrosoftTeamsAppIdentifier(self.callee)]
            self.callAgent?.startCall(participants: callees, options: StartCallOptions()) { (call, error) in
                if (error == nil) {
                    self.call = call
                } else {
                    print("Failed to get call object")
                }
            }
        }
    }
}

También puede usar las propiedades de StartCallOptions para establecer las opciones iniciales de la llamada (es decir, permite iniciar la llamada con el micrófono silenciado).

Finalización de una llamada

Implemente el método endCall para finalizar la llamada actual cuando se toque el botón Finalizar llamada.

func endCall()
{    
    self.call!.hangUp(options: HangUpOptions()) { (error) in
        if (error != nil) {
            print("ERROR: It was not possible to hangup the call.")
        }
    }
}

Ejecución del código

Para compilar y ejecutar la aplicación en el simulador de iOS, seleccione Producto>Ejecutar o use el método abreviado de teclado (⌘-R).

Nota:

La primera vez que realice una llamada, el sistema le solicitará acceso al micrófono. En una aplicación de producción, debe usar la API AVAudioSession para comprobar el estado del permiso y actualizar correctamente el comportamiento de la aplicación cuando no se conceda el permiso.

Pasos manuales para configurar la llamada:

  1. Inicio de la aplicación mediante Xcode
  2. Escriba el id. de objeto del operador automático (con prefijo) y seleccione el botón "Iniciar llamada". La aplicación iniciará la llamada saliente al operador automático con el id. de objeto especificado.
  3. La llamada está conectada al operador automático.
  4. El usuario de Communication Services se enruta a través del operador automático en función de su configuración.

Importante

Esta característica de Azure Communication Services se encuentra actualmente en versión preliminar.

Las API y los SDK en versión preliminar se proporcionan sin contrato de nivel de servicio. Se recomienda no usarlos para las cargas de trabajo de producción. Es posible que algunas características no sean compatibles o que sus funcionalidades estén limitadas.

Para obtener más información, consulte Términos de uso complementarios para las Versiones preliminares de Microsoft Azure.

En este inicio rápido, aprenderá a iniciar una llamada de un usuario de Azure Communication Services a un operador automático de Teams. Lo logrará con los pasos siguientes:

  1. Habilitar la federación de recurso de Azure Communication Services con el inquilino de Teams.
  2. Seleccione o cree el operador automático de Teams a través del Centro de Administración de Teams.
  3. Obtenga la dirección de correo electrónico del operador automático a través del Centro de Administración de Teams.
  4. Obtenga el id. de objeto del operador automático a través de Graph API.
  5. Iniciar una llamada con el SDK de llamadas de Azure Communication Services.

Si quiere ir directamente al final, puede descargar esta guía de inicio rápido como ejemplo desde GitHub.

Habilitar interoperabilidad en el inquilino de Teams

El usuario de Microsoft Entra con el rol de administrador de Teams puede ejecutar el cmdlet de PowerShell con el módulo de MicrosoftTeams para habilitar el recurso de Communication Services en el inquilino.

1. Preparar el módulo de Microsoft Teams

En primer lugar, abra PowerShell y valide la existencia del módulo de Teams con el siguiente comando:

Get-module *teams* 

Si no ve el módulo MicrosoftTeams, instálelo primero. Para instalar el módulo debe ejecutar PowerShell como administrador. Luego, ejecute el siguiente comando:

	Install-Module -Name MicrosoftTeams

Se le informará sobre los módulos que se instalarán, que puede confirmar con una respuesta Y o A. Si el módulo está instalado pero no está actualizado, puede ejecutar el siguiente comando para actualizar el módulo:

	Update-Module MicrosoftTeams

2. Conexión al módulo de Microsoft Teams

Cuando el módulo esté instalado y listo, puede conectarse al módulo MicrosoftTeams con el siguiente comando. Se le pedirá que inicie sesión con una ventana interactiva. La cuenta de usuario que va a usar debe tener permisos de administrador de Teams. De lo contrario, puede obtener una respuesta access denied en los pasos siguientes.

Connect-MicrosoftTeams

3. Habilitar la configuración del inquilino

La interoperabilidad con los recursos de Communication Services se controla mediante la configuración de inquilinos y la directiva asignada. El inquilino de Teams tiene una configuración de inquilino único y los usuarios de Teams tienen asignada directiva global o directiva personalizada. Para más información, consulte Asignación de directivas en Teams.

Después de iniciar sesión correctamente, puede ejecutar el cmdlet Set-CsTeamsAcsFederationConfiguration para habilitar el recurso de Communication Services en el inquilino. Reemplace el texto IMMUTABLE_RESOURCE_ID por un identificador de recurso inmutable en el recurso de comunicación. Puede encontrar más detalles sobre cómo obtener esta información aquí.

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4. Habilitar una directiva de inquilino

A cada usuario de Teams se le ha asignado un External Access Policy que determina si los usuarios de Communication Services pueden llamar a este usuario de Teams. Use el cmdlet Set-CsExternalAccessPolicy para asegurarse de que la directiva asignada al usuario de Teams ha establecido EnableAcsFederationAccess en $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Crear o seleccionar Operador automático de Teams

El operador automático de Teams es un sistema que proporciona un sistema de control de llamadas automatizado para las llamadas entrantes. Sirve de recepcionista virtual, permitiendo que los autores de llamadas se enruten automáticamente a la persona o departamento adecuados sin necesidad de un operador humano. Puede seleccionar un operador automático existente o crearlo a través del Centro de administración de Teams.

Obtenga más información sobre cómo crear un operador automático mediante el Centro de administración de Teams aquí.

Buscar el id. de objeto para el operador automático

Después de crear el operador automático, es necesario encontrar el id. de objeto correlacionado para usarlo más adelante para las llamadas. El id. de objeto está conectado a la cuenta de recursos que se adjuntó al operador automático: abra la pestaña Cuentas de recursos en Administración de Teams y busque el correo electrónico de la cuenta. Captura de pantalla de las cuentas de recursos en el portal de administración de Teams. Toda la información necesaria para la cuenta de recursos se puede encontrar en Explorador de Microsoft Graph utilizando este correo electrónico en la búsqueda.

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

En los resultados, encontraremos el campo "Id."

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

Para usarlo en la aplicación que realiza la llamada, es necesario agregar un prefijo a este identificador. Actualmente se admite lo siguiente:

  • Operador automático de nube pública: 28:orgid:<id>
  • Operador automático de nube gubernamental: 28:gcch:<id>

Requisitos previos

Para completar este tutorial, debe cumplir los siguientes requisitos previos:

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.

Captura de pantalla que muestra la ventana Nuevo proyecto de UWP en Visual Studio.

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.4.0 o superior. Asegúrese de comprobar Include Prerelease si desea ver las versiones de la versión preliminar pública.

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.

Captura de pantalla que muestra la solicitud de acceso a Internet y al micrófono en Visual Studio.

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

        <!-- Don't forget to replace ‘CallingQuickstart’ with your project’s name -->


    <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 CallAgent callAgent;
        private CommunicationCall call;

        private LocalOutgoingAudioStream micStream;

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

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            await InitCallAgentAndDeviceManagerAsync();

            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, IncomingCallReceivedEventArgs args)
        {
            // Handle incoming call event
        }

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

        #region Helper methods

        private async Task InitCallAgentAndDeviceManagerAsync()
        {
            //Initialize the call agent and search for devices
        }


        private async Task<CommunicationCall> StartCallAsync(string acsCallee)
        {
            // Start a call to an Azure Communication Services user using the CallAgent and the callee id
        }

        #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.
CallAgent CallAgent se utiliza para iniciar y administrar las llamadas.
CommunicationCall CommunicationCall se usa para administrar una llamada en curso.
CallTokenCredential CallTokenCredential se usa como la credencial del token para crear una instancia de CallAgent.
CallIdentifier CallIdentifier se usa para representar la identidad del usuario, que puede ser una de las opciones siguientes: UserCallIdentifier, PhoneNumberCallIdentifier, etc.

Autenticar el cliente

Inicialice una instancia CallAgent 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() { 
                    
                    // make sure to put your project AppName
                    AppName = "CallingQuickstart",

                    AppVersion="1.0",

                    Tags = new[] { "Calling", "ACS", "Windows" }
                    }

                });

            // Set up local audio stream using the first mic enumerated
            var deviceManager = await this.callClient.GetDeviceManagerAsync();
            var mic = deviceManager?.Microphones?.FirstOrDefault();

            micStream = new LocalOutgoingAudioStream();

            var tokenCredential = new CallTokenCredential(authToken, callTokenRefreshOptions);

            var callAgentOptions = new CallAgentOptions()
            {
                DisplayName = $"{Environment.MachineName}/{Environment.UserName}",
            };

            this.callAgent = await this.callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);

            this.callAgent.IncomingCallReceived += OnIncomingCallAsync;
        }

Iniciar la llamada

Una vez obtenido un objeto StartCallOptions, CallAgent se puede usar para iniciar la llamada de Azure Communication Services:

        private async Task<CommunicationCall> StartCallAsync(string acsCallee)
        {
            var options = new StartCallOptions();
            var call = await this.callAgent.StartCallAsync( new [] { new MicrosoftTeamsAppCallIdentifier(acsCallee) }, options);
            return call;
        }

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 call = this.callAgent?.Calls?.FirstOrDefault();
            if (call != null)
            {
                await call.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 call = this.callAgent?.Calls?.FirstOrDefault();

                if (call != null)
                {
                    if ((bool)muteCheckbox.IsChecked)
                    {
                        await call.MuteOutgoingAudioAsync();
                    }
                    else
                    {
                        await call.UnmuteOutgoingAudioAsync();
                    }
                }

                // Update the UI to reflect the state
            }
        }

Aceptar una llamada entrante

El receptor de eventos IncomingCallReceived se configura en el asistente de arranque del SDK InitCallAgentAndDeviceManagerAsync.

    this.callAgent.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, IncomingCallReceivedEventArgs args)
        {
            var incomingCall = args.IncomingCall;

            var acceptCallOptions = new AcceptCallOptions() { };

            call = await incomingCall.AcceptAsync(acceptCallOptions);
            call.StateChanged += OnStateChangedAsync;
        }

Supervisar y responder al evento de cambio de estado de llamada

El evento StateChanged en el objeto CommunicationCall 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 call = sender as CommunicationCall;

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

                // Update the UI

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

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

                            call.Dispose();

                            break;
                        }
                    default: break;
                }
            }
        }

Haga que el botón de llamada funcione

Una vez que Callee ID no es null o está vacío, puede iniciar una llamada.

El estado de la llamada debe cambiarse mediante la acción OnStateChangedAsync.


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

        if (!string.IsNullOrEmpty(callString))
        {
            call = await StartCallAsync(callString);

            call.StateChanged += OnStateChangedAsync;
        }
    
        
    }

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.

Pasos manuales para configurar la llamada:

  1. Inicie la aplicación mediante Visual Studio.
  2. Escriba el id. de objeto del operador automático (con prefijo) y seleccione el botón "Iniciar llamada". La aplicación iniciará la llamada saliente al operador automático con el id. de objeto especificado.
  3. La llamada está conectada al operador automático.
  4. El usuario de Communication Services se enruta a través del operador automático en función de su configuración.

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.