Guia de início rápido: associe seu aplicativo de chamadas a uma fila de chamadas do Teams

Neste guia de início rápido, você aprenderá como iniciar uma chamada do usuário dos Serviços de Comunicação do Azure para a Fila de Chamadas do Teams. Você vai conseguir isso com as seguintes etapas:

  1. Habilite a federação do recurso dos Serviços de Comunicação do Azure com o Locatário do Teams.
  2. Selecione ou crie a fila de chamadas do Teams por meio do Centro de administração do Teams.
  3. Obtenha o endereço de e-mail da Fila de Chamadas através do Centro de Administração do Teams.
  4. Obtenha o ID do objeto da fila de chamadas por meio da API do Graph.
  5. Inicie uma chamada com o SDK de Chamada dos Serviços de Comunicação do Azure.

Se você quiser pular para o final, você pode baixar este início rápido como um exemplo no GitHub.

Habilite a interoperabilidade em seu locatário do Teams

O usuário do Microsoft Entra com a função de administrador do Teams pode executar o cmdlet do PowerShell com o módulo MicrosoftTeams para habilitar o recurso Serviços de Comunicação no locatário.

1. Preparar o módulo Microsoft Teams

Primeiro, abra o PowerShell e valide a existência do módulo Teams com o seguinte comando:

Get-module *teams* 

Se não vir o módulo, instale-o MicrosoftTeams primeiro. Para instalar o módulo, você precisa executar o PowerShell como administrador. Em seguida, execute o seguinte comando:

	Install-Module -Name MicrosoftTeams

Você será informado sobre os módulos que serão instalados, o que você pode confirmar com uma Y ou A resposta. Se o módulo estiver instalado, mas estiver desatualizado, você poderá executar o seguinte comando para atualizá-lo:

	Update-Module MicrosoftTeams

2. Conecte-se ao módulo Microsoft Teams

Quando o módulo estiver instalado e pronto, você poderá se conectar ao módulo MicrosoftTeams com o seguinte comando. Ser-lhe-á solicitada uma janela interativa para iniciar sessão. A conta de usuário que você vai usar precisa ter permissões de administrador do Teams. Caso contrário, você poderá obter uma access denied resposta nas próximas etapas.

Connect-MicrosoftTeams

3. Habilitar a configuração do locatário

A interoperabilidade com os recursos dos Serviços de Comunicação é controlada por meio da configuração do locatário e da política atribuída. O locatário do Teams tem uma configuração de locatário único e os usuários do Teams atribuíram política global ou política personalizada. Para obter mais informações, consulte Atribuir políticas no Teams.

Após o logon bem-sucedido, você pode executar o cmdlet Set-CsTeamsAcsFederationConfiguration para habilitar o recurso Serviços de Comunicação em seu locatário. Substitua o texto IMMUTABLE_RESOURCE_ID por um ID de recurso imutável no recurso de comunicação. Você pode encontrar mais detalhes sobre como obter essas informações aqui.

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

4. Ativar a política de inquilino

Cada usuário do Teams atribuiu um External Access Policy que determina se os usuários dos Serviços de Comunicação podem chamar esse usuário do Teams. Use o cmdlet Set-CsExternalAccessPolicy para garantir que a política atribuída ao usuário do Teams tenha sido definida EnableAcsFederationAccess como $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Criar ou selecionar Fila de chamadas do Teams

A Fila de Chamadas do Teams é um recurso do Microsoft Teams que distribui chamadas de entrada de forma eficiente entre um grupo de usuários ou agentes designados. É útil para cenários de suporte ao cliente ou call center. As chamadas são colocadas em uma fila e atribuídas ao próximo agente disponível com base em um método de roteamento predeterminado. Os agentes recebem notificações e podem lidar com chamadas usando os controles de chamada do Teams. O recurso oferece relatórios e análises para acompanhamento de desempenho. Ele simplifica o tratamento de chamadas, garante uma experiência consistente ao cliente e otimiza a produtividade do agente. Você pode selecionar a Fila de Chamadas existente ou criar uma nova por meio do Centro de Administração do Teams.

Saiba mais sobre como criar fila de chamadas usando o Centro de Administração do Teams aqui.

Localizar ID de objeto para fila de chamadas

Depois que a fila de chamadas é criada, precisamos encontrar o ID do objeto correlacionado para usá-lo mais tarde para chamadas. A ID do objeto está conectada à Conta de Recursos que foi anexada à fila de chamadas - abra a guia Contas de Recursos no Administrador do Teams e localize o email. Captura de ecrã de Contas de Recursos no Portal de Administração do Teams. Todas as informações necessárias para a Conta de Recursos podem ser encontradas no Microsoft Graph Explorer usando este e-mail na pesquisa.

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

Nos resultados poderemos encontrar o campo "ID"

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

Pré-requisitos

  • Obtenha uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente.
  • Node.js versões Ative LTS e Maintenance LTS (8.11.1 e 10.14.1)
  • Crie um recurso ativo dos Serviços de Comunicação. Crie um recurso de Serviços de Comunicação.

Configuração

Criar uma nova aplicação Node.js

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

mkdir calling-quickstart && cd calling-quickstart

Instalar o pacote

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

Importante

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

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

Configurar a estrutura do aplicativo

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

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

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

Aqui está o 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 callee's Teams user 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 objeto do SDK da Web dos Serviços de Comunicação do Azure

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

Nome Descrição
CallClient O ponto de entrada principal para o SDK de chamada.
CallAgent Usado para iniciar e gerenciar chamadas.
DeviceManager Usado para gerenciar dispositivos de mídia.
Call Usado para representar uma chamada.
LocalVideoStream Usado para criar um fluxo de vídeo local para um dispositivo de câmera no sistema local.
RemoteParticipant Usado para representar um participante remoto na Chamada.
RemoteVideoStream Usado para representar um fluxo de vídeo remoto de um participante remoto.

Crie um arquivo no diretório raiz do seu projeto chamado client.js para conter a lógica do aplicativo para este início rápido. Adicione o seguinte código 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 callQueueId = 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 a Teams Call Queue
 * 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: callQueueId.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.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();
            });
        });
        
        call.on('isLocalVideoStartedChanged', () => {
            console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
        });
        console.log(`isLocalVideoStarted: ${call.isLocalVideoStarted}`);
        // Inspect the call's current remote participants and subscribe to them.
        call.remoteParticipants.forEach(remoteParticipant => {
            subscribeToRemoteParticipant(remoteParticipant);
        });
        // Subscribe to the call's 'remoteParticipantsUpdated' event to be
        // notified when new participants are added to the call or removed from the call.
        call.on('remoteParticipantsUpdated', e => {
            // Subscribe to new remote participants that are added to the call.
            e.added.forEach(remoteParticipant => {
                subscribeToRemoteParticipant(remoteParticipant)
            });
            // Unsubscribe from participants that are removed from the call
            e.removed.forEach(remoteParticipant => {
                console.log('Remote participant removed from the call.');
            });
        });
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a remote participant obj.
// Listen for property changes and collection updates.
subscribeToRemoteParticipant = (remoteParticipant) => {
    try {
        // Inspect the initial remoteParticipant.state value.
        console.log(`Remote participant state: ${remoteParticipant.state}`);
        // Subscribe to remoteParticipant's 'stateChanged' event for value changes.
        remoteParticipant.on('stateChanged', () => {
            console.log(`Remote participant state changed: ${remoteParticipant.state}`);
        });
        // Inspect the remoteParticipants's current videoStreams and subscribe to them.
        remoteParticipant.videoStreams.forEach(remoteVideoStream => {
            subscribeToRemoteVideoStream(remoteVideoStream)
        });
        // Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
        // notified when the remoteParticipant adds new videoStreams and removes video streams.
        remoteParticipant.on('videoStreamsUpdated', e => {
            // Subscribe to newly added remote participant's video streams.
            e.added.forEach(remoteVideoStream => {
                subscribeToRemoteVideoStream(remoteVideoStream)
            });
            // Unsubscribe from newly removed remote participants' video streams.
            e.removed.forEach(remoteVideoStream => {
                console.log('Remote participant video stream was removed.');
            })
        });
    } catch (error) {
        console.error(error);
    }
}
/**
 * Subscribe to a remote participant's remote video stream obj.
 * You have to subscribe to the 'isAvailableChanged' event to render the remoteVideoStream. If the 'isAvailable' property
 * changes to 'true' a remote participant is sending a stream. Whenever the availability of a remote stream changes
 * you can choose to destroy the whole 'Renderer' a specific 'RendererView' or keep them. Displaying RendererView without a video stream will result in a blank video frame. 
 */
subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    // Create a video stream renderer for the remote video stream.
    let videoStreamRenderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    const renderVideo = async () => {
        try {
            // Create a renderer view for the remote video stream.
            view = await videoStreamRenderer.createView();
            // Attach the renderer view to the UI.
            remoteVideoContainer.hidden = false;
            remoteVideoContainer.appendChild(view.target);
        } catch (e) {
            console.warn(`Failed to createView, reason=${e.message}, code=${e.code}`);
        }	
    }
    
    remoteVideoStream.on('isAvailableChanged', async () => {
        // Participant has switched video on.
        if (remoteVideoStream.isAvailable) {
            await renderVideo();
        // Participant has switched video off.
        } else {
            if (view) {
                view.dispose();
                view = undefined;
            }
        }
    });
    // Participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        await renderVideo();
    }
}
// Start your local video stream.
// This will send your local video stream to remote participants so they can view it.
startVideoButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        await call.startVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
// Stop your local video stream.
// This will stop your local video stream from being sent to remote participants.
stopVideoButton.onclick = async () => {
    try {
        await call.stopVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
/**
 * To render a LocalVideoStream, you need to create a new instance of VideoStreamRenderer, and then
 * create a new VideoStreamRendererView instance using the asynchronous createView() method.
 * You may then attach view.target to any UI element. 
 */
// Create a local video stream for your camera device
createLocalVideoStream = async () => {
    const camera = (await deviceManager.getCameras())[0];
    if (camera) {
        return new LocalVideoStream(camera);
    } else {
        console.error(`No camera device found on the system`);
    }
}
// Display your local video stream preview in your UI
displayLocalVideoStream = async () => {
    try {
        localVideoStreamRenderer = new VideoStreamRenderer(localVideoStream);
        const view = await localVideoStreamRenderer.createView();
        localVideoContainer.hidden = false;
        localVideoContainer.appendChild(view.target);
    } catch (error) {
        console.error(error);
    } 
}
// Remove your local video stream preview from your UI
removeLocalVideoStream = async() => {
    try {
        localVideoStreamRenderer.dispose();
        localVideoContainer.hidden = true;
    } catch (error) {
        console.error(error);
    } 
}
// End the current call
hangUpCallButton.addEventListener("click", async () => {
    // end the current call
    await call.hangUp();
});

Adicionar o código do servidor local do webpack

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

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

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

Executar o código

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

npx webpack serve --config webpack.config.js

Passos manuais para configurar a chamada:

  1. Abra o navegador e navegue até http://localhost:8080/.
  2. Insira um token de acesso de usuário válido. Consulte a documentação do token de acesso do usuário se ainda não tiver tokens de acesso disponíveis para uso.
  3. Clique nos botões "Inicializar Call Agent".
  4. Digite o ID do objeto da fila de chamadas e selecione o botão "Iniciar chamada". O aplicativo iniciará a chamada de saída para a fila de chamadas com determinado ID de objeto.
  5. A chamada está conectada à fila de chamadas.
  6. O usuário dos Serviços de Comunicação é roteado através da Fila de Chamadas com base em sua configuração.

Neste início rápido, você aprenderá como iniciar uma chamada do usuário dos Serviços de Comunicação do Azure para a Fila de Chamadas do Teams. Você vai alcançá-lo com os seguintes passos:

  1. Habilite a federação do recurso dos Serviços de Comunicação do Azure com o Locatário do Teams.
  2. Selecione ou crie a fila de chamadas do Teams por meio do Centro de administração do Teams.
  3. Obtenha o endereço de e-mail da Fila de Chamadas através do Centro de Administração do Teams.
  4. Obtenha o ID do objeto da fila de chamadas por meio da API do Graph.
  5. Inicie uma chamada com o SDK de Chamada dos Serviços de Comunicação do Azure.

Se você quiser pular para o final, você pode baixar este início rápido como um exemplo no GitHub.

Habilite a interoperabilidade em seu locatário do Teams

O usuário do Microsoft Entra com a função de administrador do Teams pode executar o cmdlet do PowerShell com o módulo MicrosoftTeams para habilitar o recurso Serviços de Comunicação no locatário.

1. Preparar o módulo Microsoft Teams

Primeiro, abra o PowerShell e valide a existência do módulo Teams com o seguinte comando:

Get-module *teams* 

Se não vir o módulo, instale-o MicrosoftTeams primeiro. Para instalar o módulo, você precisa executar o PowerShell como administrador. Em seguida, execute o seguinte comando:

	Install-Module -Name MicrosoftTeams

Você será informado sobre os módulos que serão instalados, o que você pode confirmar com uma Y ou A resposta. Se o módulo estiver instalado, mas estiver desatualizado, você poderá executar o seguinte comando para atualizá-lo:

	Update-Module MicrosoftTeams

2. Conecte-se ao módulo Microsoft Teams

Quando o módulo estiver instalado e pronto, você poderá se conectar ao módulo MicrosoftTeams com o seguinte comando. Ser-lhe-á solicitada uma janela interativa para iniciar sessão. A conta de usuário que você vai usar precisa ter permissões de administrador do Teams. Caso contrário, você poderá obter uma access denied resposta nas próximas etapas.

Connect-MicrosoftTeams

3. Habilitar a configuração do locatário

A interoperabilidade com os recursos dos Serviços de Comunicação é controlada por meio da configuração do locatário e da política atribuída. O locatário do Teams tem uma configuração de locatário único e os usuários do Teams atribuíram política global ou política personalizada. Para obter mais informações, consulte Atribuir políticas no Teams.

Após o logon bem-sucedido, você pode executar o cmdlet Set-CsTeamsAcsFederationConfiguration para habilitar o recurso Serviços de Comunicação em seu locatário. Substitua o texto IMMUTABLE_RESOURCE_ID por um ID de recurso imutável no recurso de comunicação. Você pode encontrar mais detalhes sobre como obter essas informações aqui.

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

4. Ativar a política de inquilino

Cada usuário do Teams atribuiu um External Access Policy que determina se os usuários dos Serviços de Comunicação podem chamar esse usuário do Teams. Use o cmdlet Set-CsExternalAccessPolicy para garantir que a política atribuída ao usuário do Teams tenha sido definida EnableAcsFederationAccess como $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Criar ou selecionar Fila de chamadas do Teams

A Fila de Chamadas do Teams é um recurso do Microsoft Teams que distribui chamadas de entrada de forma eficiente entre um grupo de usuários ou agentes designados. É útil para cenários de suporte ao cliente ou call center. As chamadas são colocadas em uma fila e atribuídas ao próximo agente disponível com base em um método de roteamento predeterminado. Os agentes recebem notificações e podem lidar com chamadas usando os controles de chamada do Teams. O recurso oferece relatórios e análises para acompanhamento de desempenho. Ele simplifica o tratamento de chamadas, garante uma experiência consistente ao cliente e otimiza a produtividade do agente. Você pode selecionar a Fila de Chamadas existente ou criar uma nova por meio do Centro de Administração do Teams.

Saiba mais sobre como criar fila de chamadas usando o Centro de Administração do Teams aqui.

Localizar ID de objeto para fila de chamadas

Depois que a fila de chamadas é criada, precisamos encontrar o ID do objeto correlacionado para usá-lo mais tarde para chamadas. A ID do objeto está conectada à Conta de Recursos que foi anexada à Fila de Chamadas - abra a guia Contas de Recursos no Administrador do Teams e localize o email. Captura de ecrã de Contas de Recursos no Portal de Administração do Teams. Todas as informações necessárias para a Conta de Recursos podem ser encontradas no Microsoft Graph Explorer usando este e-mail na pesquisa.

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

Nos resultados poderemos encontrar o campo "ID"

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

Para usar no aplicativo de chamada, precisamos adicionar um prefixo a esse ID. Atualmente, os seguintes são suportados:

  • Fila de chamadas na nuvem pública: 28:orgid:<id>
  • Fila de chamadas na nuvem do governo: 28:gcch:<id>

Pré-requisitos

  • Uma conta do Azure com uma subscrição ativa. Crie uma conta gratuitamente.

  • Android Studio, para criar a sua aplicação Android.

  • Um recurso de Serviços de Comunicação implantado. Crie um recurso de Serviços de Comunicação.

  • Um Token de Acesso de Usuário para seu Serviço de Comunicação do Azure. Você também pode usar a CLI do Azure e executar o comando com sua cadeia de conexão para criar um usuário e um token de acesso.

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

    Para obter detalhes, consulte Usar a CLI do Azure para criar e gerenciar tokens de acesso.

  • Suporte mínimo para aplicativos de chamada do Teams: 2.12.0-beta.1

Configuração

Criar um aplicativo Android com uma atividade vazia

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

Captura de ecrã a mostrar o botão 'Iniciar um novo projeto Android Studio' selecionado no Android Studio.

Selecione o modelo de projeto "Atividade de visualizações vazias" em "Telefone e tablet".

Captura de tela mostrando a opção 'Atividade vazia' selecionada na tela Modelo de projeto.

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

Captura de tela mostrando a opção 'Atividade vazia' selecionada na tela Modelo de projeto 2.

Instalar o pacote

Localize o seu projeto settings.gradle.kts e certifique-se de ver mavenCentral() na lista de repositórios em pluginManagement e dependencyResolutionManagement

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

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
    ...
        mavenCentral()
    }
}

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

android {
    ...
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

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

Adicionar permissões ao manifesto do aplicativo

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

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

Configurar o layout do aplicativo

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

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

Criar os andaimes e encadernações da atividade principal

Com o layout criado as encadernações podem ser adicionadas, bem como o andaime básico da atividade. A atividade lida com a solicitação de permissões de tempo de execução, a criação do agente de chamada e a realização da chamada quando o botão é pressionado. O onCreate método é substituído para invocar getAllPermissions e createAgent adicionar as ligações para o botão de chamada. Esse evento ocorre apenas uma vez quando a atividade é criada. Para obter mais informações, consulte onCreateo guia Compreender o ciclo de vida da atividade.

Navegue até MainActivity.java e substitua o conteúdo pelo seguinte 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;
        }
        List<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));
    }
}

Solicitar permissões em tempo de execução

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

/**
 * 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

Ao projetar seu aplicativo, considere quando essas permissões devem ser solicitadas. As permissões devem ser solicitadas conforme necessário, não com antecedência. Para obter mais informações, consulte o Guia de permissões do Android.

Modelo de objeto

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

Nome Descrição
CallClient O CallClient é o principal ponto de entrada para o SDK de chamada.
CallAgent O CallAgent é usado para iniciar e gerenciar chamadas.
CommunicationTokenCredential O CommunicationTokenCredential é usado como a credencial de token para instanciar o CallAgent.
CommunicationIdentifier O CommunicationIdentifier é usado como diferente tipo de participante que pode fazer parte de uma chamada.

Criar um agente a partir do token de acesso do usuário

Com um token de usuário, um agente de chamada autenticado pode ser instanciado. Geralmente esse token é gerado a partir de um serviço com autenticação específica para o aplicativo. Para obter mais informações sobre tokens de acesso de usuário, consulte o guia Tokens de acesso de usuário.

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


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

Executar o código

O aplicativo agora pode ser iniciado usando o botão "Executar aplicativo" na barra de ferramentas.

Passos manuais para configurar a chamada:

  1. Inicie o aplicativo usando o Android Studio.
  2. Digite o ID do objeto da fila de chamadas (com prefixo) e selecione o botão "Iniciar chamada". O aplicativo iniciará a chamada de saída para a fila de chamadas com determinado ID de objeto.
  3. A chamada está conectada à fila de chamadas.
  4. O usuário dos Serviços de Comunicação é roteado através da Fila de Chamadas com base em sua configuração.

Neste início rápido, você aprenderá como iniciar uma chamada do usuário dos Serviços de Comunicação do Azure para a Fila de Chamadas do Teams. Você vai alcançá-lo com os seguintes passos:

  1. Habilite a federação do recurso dos Serviços de Comunicação do Azure com o Locatário do Teams.
  2. Selecione ou crie a fila de chamadas do Teams por meio do Centro de administração do Teams.
  3. Obtenha o endereço de e-mail da Fila de Chamadas através do Centro de Administração do Teams.
  4. Obtenha o ID do objeto da fila de chamadas por meio da API do Graph.
  5. Inicie uma chamada com o SDK de Chamada dos Serviços de Comunicação do Azure.

Se você quiser pular para o final, você pode baixar este início rápido como um exemplo no GitHub.

Habilite a interoperabilidade em seu locatário do Teams

O usuário do Microsoft Entra com a função de administrador do Teams pode executar o cmdlet do PowerShell com o módulo MicrosoftTeams para habilitar o recurso Serviços de Comunicação no locatário.

1. Preparar o módulo Microsoft Teams

Primeiro, abra o PowerShell e valide a existência do módulo Teams com o seguinte comando:

Get-module *teams* 

Se não vir o módulo, instale-o MicrosoftTeams primeiro. Para instalar o módulo, você precisa executar o PowerShell como administrador. Em seguida, execute o seguinte comando:

	Install-Module -Name MicrosoftTeams

Você será informado sobre os módulos que serão instalados, o que você pode confirmar com uma Y ou A resposta. Se o módulo estiver instalado, mas estiver desatualizado, você poderá executar o seguinte comando para atualizá-lo:

	Update-Module MicrosoftTeams

2. Conecte-se ao módulo Microsoft Teams

Quando o módulo estiver instalado e pronto, você poderá se conectar ao módulo MicrosoftTeams com o seguinte comando. Ser-lhe-á solicitada uma janela interativa para iniciar sessão. A conta de usuário que você vai usar precisa ter permissões de administrador do Teams. Caso contrário, você poderá obter uma access denied resposta nas próximas etapas.

Connect-MicrosoftTeams

3. Habilitar a configuração do locatário

A interoperabilidade com os recursos dos Serviços de Comunicação é controlada por meio da configuração do locatário e da política atribuída. O locatário do Teams tem uma configuração de locatário único e os usuários do Teams atribuíram política global ou política personalizada. Para obter mais informações, consulte Atribuir políticas no Teams.

Após o logon bem-sucedido, você pode executar o cmdlet Set-CsTeamsAcsFederationConfiguration para habilitar o recurso Serviços de Comunicação em seu locatário. Substitua o texto IMMUTABLE_RESOURCE_ID por um ID de recurso imutável no recurso de comunicação. Você pode encontrar mais detalhes sobre como obter essas informações aqui.

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

4. Ativar a política de inquilino

Cada usuário do Teams atribuiu um External Access Policy que determina se os usuários dos Serviços de Comunicação podem chamar esse usuário do Teams. Use o cmdlet Set-CsExternalAccessPolicy para garantir que a política atribuída ao usuário do Teams tenha sido definida EnableAcsFederationAccess como $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Criar ou selecionar Fila de chamadas do Teams

A Fila de Chamadas do Teams é um recurso do Microsoft Teams que distribui chamadas de entrada de forma eficiente entre um grupo de usuários ou agentes designados. É útil para cenários de suporte ao cliente ou call center. As chamadas são colocadas em uma fila e atribuídas ao próximo agente disponível com base em um método de roteamento predeterminado. Os agentes recebem notificações e podem lidar com chamadas usando os controles de chamada do Teams. O recurso oferece relatórios e análises para acompanhamento de desempenho. Ele simplifica o tratamento de chamadas, garante uma experiência consistente ao cliente e otimiza a produtividade do agente. Você pode selecionar a Fila de Chamadas existente ou criar uma nova por meio do Centro de Administração do Teams.

Saiba mais sobre como criar fila de chamadas usando o Centro de Administração do Teams aqui.

Localizar ID de objeto para fila de chamadas

Depois que a fila de chamadas é criada, precisamos encontrar o ID do objeto correlacionado para usá-lo mais tarde para chamadas. A ID do objeto está conectada à Conta de Recursos que foi anexada à Fila de Chamadas - abra a guia Contas de Recursos no Administrador do Teams e localize o email. Captura de ecrã de Contas de Recursos no Portal de Administração do Teams. Todas as informações necessárias para a Conta de Recursos podem ser encontradas no Microsoft Graph Explorer usando este e-mail na pesquisa.

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

Nos resultados poderemos encontrar o campo "ID"

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

Para usar no aplicativo de chamada, precisamos adicionar um prefixo a esse ID. Atualmente, os seguintes são suportados:

  • Fila de chamadas na nuvem pública: 28:orgid:<id>
  • Fila de chamadas na nuvem do governo: 28:gcch:<id>

Pré-requisitos

  • Obtenha uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente.

  • Um Mac com Xcode, juntamente com um certificado de programador válido instalado no seu Porta-chaves.

  • Um recurso de Serviços de Comunicação implantado. Crie um recurso de Serviços de Comunicação. Você precisa gravar sua cadeia de conexão para este início rápido.

  • Um Token de Acesso de Usuário para seu Serviço de Comunicação do Azure. Você também pode usar a CLI do Azure e executar o comando com sua cadeia de conexão para criar um usuário e um token de acesso.

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

    Para obter detalhes, consulte Usar a CLI do Azure para criar e gerenciar tokens de acesso.

  • Suporte mínimo para aplicativos de chamada do Teams: 2.15.0

Configuração

Criando o projeto Xcode

No Xcode, crie um novo projeto iOS e selecione o modelo App . Este tutorial usa a estrutura SwiftUI, portanto, você deve definir o idioma como Swift e a interface do usuário como SwiftUI. Você não vai criar testes durante esse início rápido. Sinta-se à vontade para desmarcar Incluir testes.

Captura de tela mostrando a janela Novo projeto no Xcode.

Instale o pacote e as dependências com o CocoaPods

  1. Para criar um Podfile para seu aplicativo, abra o terminal e navegue até a pasta do projeto e execute:

    pod init

  2. Adicione o seguinte código ao Podfile e salve (certifique-se de que "target" corresponde ao nome do seu projeto):

    platform :ios, '13.0'
    use_frameworks!
    
    target 'AzureCommunicationCallingSample' do
      pod 'AzureCommunicationCalling', '~> 2.15.0'
    end
    
  3. Execute o pod install.

  4. Abra o com Xcode .xcworkspace .

Solicitar acesso ao microfone

Para acessar o microfone do dispositivo, você precisa atualizar a Lista de Propriedades de Informações do seu aplicativo com um NSMicrophoneUsageDescriptionarquivo . Você define o valor associado como um string que foi incluído na caixa de diálogo que o sistema usa para solicitar acesso do usuário.

Clique com o botão direito do mouse na Info.plist entrada da árvore do projeto e selecione Abrir como>código-fonte. Adicione as seguintes linhas à secção de nível <dict> superior e, em seguida, guarde o ficheiro.

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

Configurar a estrutura do aplicativo

Abra o arquivo ContentView.swift do seu projeto e adicione uma import declaração à parte superior do arquivo para importar o AzureCommunicationCalling libraryarquivo . Além disso, importe AVFoundation, precisamos deste código para solicitação de permissão de áudio no código.

import AzureCommunicationCalling
import AVFoundation

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

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 objeto

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

Nome Descrição
CallClient O CallClient é o principal ponto de entrada para o SDK de chamada.
CallAgent O CallAgent é usado para iniciar e gerenciar chamadas.
CommunicationTokenCredential O CommunicationTokenCredential é usado como a credencial de token para instanciar o CallAgent.
CommunicationUserIdentifier O CommunicationUserIdentifier é usado para representar a identidade do usuário, que pode ser uma das seguintes opções: CommunicationUserIdentifier,PhoneNumberIdentifier ou CallingApplication.

Autenticar o cliente

Inicialize uma CallAgent instância com um Token de Acesso de Usuário, que nos permite fazer e receber chamadas.

No código a seguir, você precisa substituir <USER ACCESS TOKEN> por um token de acesso de usuário válido para seu recurso. Consulte a documentação do token de acesso do usuário se ainda não tiver um token disponível.

Adicione o seguinte código ao retorno de onAppear chamada em 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.")
    }
}

Iniciar uma chamada

O startCall método é definido como a ação que é executada quando o botão Iniciar chamada é tocado. Atualize a implementação para iniciar uma chamada com o 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")
                }
            }
        }
    }
}

Você também pode usar as propriedades para StartCallOptions definir as opções iniciais para a chamada (ou seja, permite iniciar a chamada com o microfone silenciado).

Terminar uma chamada

Implemente o endCall método para encerrar a chamada atual quando o botão Encerrar chamada for tocado.

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

Executar o código

Você pode criar e executar seu aplicativo no simulador do iOS selecionando Execução de produto>ou usando o atalho de teclado (⌘-R).

Nota

Na primeira vez que fizer uma chamada, o sistema solicitar-lhe-á acesso ao microfone. Em um aplicativo de produção, você deve usar a AVAudioSession API para verificar o status da permissão e atualizar graciosamente o comportamento do aplicativo quando a permissão não for concedida.

Passos manuais para configurar a chamada:

  1. Inicie o aplicativo usando o Xcode
  2. Digite o ID do objeto da fila de chamadas (com prefixo) e selecione o botão "Iniciar chamada". O aplicativo iniciará a chamada de saída para a fila de chamadas com determinado ID de objeto.
  3. A chamada está conectada à fila de chamadas.
  4. O usuário dos Serviços de Comunicação é roteado através da Fila de Chamadas com base em sua configuração.

Neste início rápido, você aprenderá como iniciar uma chamada do usuário dos Serviços de Comunicação do Azure para a Fila de Chamadas do Teams. Você vai alcançá-lo com os seguintes passos:

  1. Habilite a federação do recurso dos Serviços de Comunicação do Azure com o Locatário do Teams.
  2. Selecione ou crie a fila de chamadas do Teams por meio do Centro de administração do Teams.
  3. Obtenha o endereço de e-mail da Fila de Chamadas através do Centro de Administração do Teams.
  4. Obtenha o ID do objeto da fila de chamadas por meio da API do Graph.
  5. Inicie uma chamada com o SDK de Chamada dos Serviços de Comunicação do Azure.

Se você quiser pular para o final, você pode baixar este início rápido como um exemplo no GitHub.

Habilite a interoperabilidade em seu locatário do Teams

O usuário do Microsoft Entra com a função de administrador do Teams pode executar o cmdlet do PowerShell com o módulo MicrosoftTeams para habilitar o recurso Serviços de Comunicação no locatário.

1. Preparar o módulo Microsoft Teams

Primeiro, abra o PowerShell e valide a existência do módulo Teams com o seguinte comando:

Get-module *teams* 

Se não vir o módulo, instale-o MicrosoftTeams primeiro. Para instalar o módulo, você precisa executar o PowerShell como administrador. Em seguida, execute o seguinte comando:

	Install-Module -Name MicrosoftTeams

Você será informado sobre os módulos que serão instalados, o que você pode confirmar com uma Y ou A resposta. Se o módulo estiver instalado, mas estiver desatualizado, você poderá executar o seguinte comando para atualizá-lo:

	Update-Module MicrosoftTeams

2. Conecte-se ao módulo Microsoft Teams

Quando o módulo estiver instalado e pronto, você poderá se conectar ao módulo MicrosoftTeams com o seguinte comando. Ser-lhe-á solicitada uma janela interativa para iniciar sessão. A conta de usuário que você vai usar precisa ter permissões de administrador do Teams. Caso contrário, você poderá obter uma access denied resposta nas próximas etapas.

Connect-MicrosoftTeams

3. Habilitar a configuração do locatário

A interoperabilidade com os recursos dos Serviços de Comunicação é controlada por meio da configuração do locatário e da política atribuída. O locatário do Teams tem uma configuração de locatário único e os usuários do Teams atribuíram política global ou política personalizada. Para obter mais informações, consulte Atribuir políticas no Teams.

Após o logon bem-sucedido, você pode executar o cmdlet Set-CsTeamsAcsFederationConfiguration para habilitar o recurso Serviços de Comunicação em seu locatário. Substitua o texto IMMUTABLE_RESOURCE_ID por um ID de recurso imutável no recurso de comunicação. Você pode encontrar mais detalhes sobre como obter essas informações aqui.

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

4. Ativar a política de inquilino

Cada usuário do Teams atribuiu um External Access Policy que determina se os usuários dos Serviços de Comunicação podem chamar esse usuário do Teams. Use o cmdlet Set-CsExternalAccessPolicy para garantir que a política atribuída ao usuário do Teams tenha sido definida EnableAcsFederationAccess como $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Criar ou selecionar Fila de chamadas do Teams

A Fila de Chamadas do Teams é um recurso do Microsoft Teams que distribui chamadas de entrada de forma eficiente entre um grupo de usuários ou agentes designados. É útil para cenários de suporte ao cliente ou call center. As chamadas são colocadas em uma fila e atribuídas ao próximo agente disponível com base em um método de roteamento predeterminado. Os agentes recebem notificações e podem lidar com chamadas usando os controles de chamada do Teams. O recurso oferece relatórios e análises para acompanhamento de desempenho. Ele simplifica o tratamento de chamadas, garante uma experiência consistente ao cliente e otimiza a produtividade do agente. Você pode selecionar a Fila de Chamadas existente ou criar uma nova por meio do Centro de Administração do Teams.

Saiba mais sobre como criar fila de chamadas usando o Centro de Administração do Teams aqui.

Localizar ID de objeto para fila de chamadas

Depois que a fila de chamadas é criada, precisamos encontrar o ID do objeto correlacionado para usá-lo mais tarde para chamadas. A ID do objeto está conectada à Conta de Recursos que foi anexada à Fila de Chamadas - abra a guia Contas de Recursos no Administrador do Teams e localize o email. Captura de ecrã de Contas de Recursos no Portal de Administração do Teams. Todas as informações necessárias para a Conta de Recursos podem ser encontradas no Microsoft Graph Explorer usando este e-mail na pesquisa.

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

Nos resultados poderemos encontrar o campo "ID"

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

Para usar no aplicativo de chamada, precisamos adicionar um prefixo a esse ID. Atualmente, os seguintes são suportados:

  • Fila de chamadas na nuvem pública: 28:orgid:<id>
  • Fila de chamadas na nuvem do governo: 28:gcch:<id>

Pré-requisitos

Para concluir este tutorial, precisa dos seguintes pré-requisitos:

  • Uma conta do Azure com uma subscrição ativa. Crie uma conta gratuitamente.

  • Instale o Visual Studio 2022 com a carga de trabalho de desenvolvimento da Plataforma Universal do Windows.

  • Um recurso de Serviços de Comunicação implantado. Crie um recurso de Serviços de Comunicação. Você precisa gravar sua cadeia de conexão para este início rápido.

  • Um Token de Acesso de Usuário para seu Serviço de Comunicação do Azure. Você também pode usar a CLI do Azure e executar o comando com sua cadeia de conexão para criar um usuário e um token de acesso.

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

    Para obter detalhes, consulte Usar a CLI do Azure para criar e gerenciar tokens de acesso.

  • Suporte mínimo para aplicativos de chamada do Teams: 1.11.0

Configuração

Criação do projeto

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

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

Instalar o pacote

Selecione com o botão direito do mouse seu projeto e vá para Manage Nuget Packages instalar Azure.Communication.Calling.WindowsClient1.4.0 ou superior. Certifique-se de que Include Prerelease está marcada se quiser ver as versões para pré-visualização pública.

Pedir acesso

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

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

Configurar a estrutura do aplicativo

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

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

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

        <!-- 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 o e substitua MainPage.xaml.cs o conteúdo pela seguinte implementação:

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

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

        private CallClient callClient;
        private CallTokenRefreshOptions callTokenRefreshOptions = new CallTokenRefreshOptions(false);
        private 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 objeto

A tabela a seguir listou as classes e interfaces que manipulam alguns dos principais recursos do SDK de Chamada dos Serviços de Comunicação do Azure:

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

Autenticar o cliente

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

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

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

        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 a chamada

Depois que um StartCallOptions objeto é obtido, CallAgent pode ser usado para iniciar a chamada dos Serviços de Comunicação do Azure:

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

Terminar uma chamada

Termine a chamada atual quando o Hang up botão for clicado. Adicione a implementação ao HangupButton_Click para encerrar uma chamada e interrompa a visualização e os fluxos 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 mudo/desativar mudo no áudio

Silencie o áudio de saída quando o Mute botão for clicado. Adicione a implementação ao MuteLocal_Click para silenciar a chamada.

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

Aceitar uma chamada recebida

IncomingCallReceived o coletor de eventos é configurado no auxiliar InitCallAgentAndDeviceManagerAsyncde bootstrap do SDK.

    this.callAgent.IncomingCallReceived += OnIncomingCallAsync;

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

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

            var acceptCallOptions = new AcceptCallOptions() { };

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

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

StateChanged evento no CommunicationCall objeto é acionado quando um em andamento chama transações de um estado para outro. O aplicativo oferece a oportunidade de refletir as alterações de estado na interface do usuário ou inserir lógicas de negócios.

        private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            var 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;
                }
            }
        }

Faça o botão de chamada funcionar

Quando o Callee ID não estiver nulo ou vazio, você poderá iniciar uma chamada.

O estado da chamada deve ser alterado usando a OnStateChangedAsync ação.


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

Executar o código

Você pode criar e executar o código no Visual Studio. Para plataformas de solução, suportamos ARM64, x64e x86.

Passos manuais para configurar a chamada:

  1. Inicie o aplicativo usando o Visual Studio.
  2. Digite o ID do objeto da fila de chamadas (com prefixo) e selecione o botão "Iniciar chamada". O aplicativo iniciará a chamada de saída para a fila de chamadas com determinado ID de objeto.
  3. A chamada está conectada à fila de chamadas.
  4. O usuário dos Serviços de Comunicação é roteado através da Fila de Chamadas com base em sua configuração.

Clean up resources (Limpar recursos)

Se quiser limpar e remover uma assinatura dos Serviços de Comunicação, você pode excluir o recurso ou grupo de recursos. A exclusão do grupo de recursos também exclui quaisquer outros recursos associados a ele. Saiba mais sobre a limpeza de recursos.

Próximos passos

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