Condividi tramite


Guida introduttiva: Aggiungere l'app chiamante a una coda di chiamate di Teams

In questa guida introduttiva si apprenderà come avviare una chiamata da Servizi di comunicazione di Azure utente alla coda di chiamate di Teams. A questo scopo, seguire questa procedura:

  1. Abilitare la federazione di Servizi di comunicazione di Azure risorsa con il tenant di Teams.
  2. Selezionare o creare una coda di chiamate di Teams tramite l'interfaccia di amministrazione di Teams.
  3. Ottenere l'indirizzo di posta elettronica della coda di chiamate tramite l'interfaccia di amministrazione di Teams.
  4. Ottenere l'ID oggetto della coda di chiamate tramite l'API Graph.
  5. Avviare una chiamata con Servizi di comunicazione di Azure Calling SDK.

Se si vuole passare direttamente alla fine, è possibile scaricare questa guida introduttiva come esempio in GitHub.

Abilitare l'interoperabilità nel tenant di Teams

L'utente Di Microsoft Entra con il ruolo di amministratore di Teams può eseguire il cmdlet di PowerShell con il modulo MicrosoftTeams per abilitare la risorsa servizi di comunicazione nel tenant.

1. Preparare il modulo di Microsoft Teams

Prima di tutto, aprire PowerShell e convalidare l'esistenza del modulo Teams con il comando seguente:

Get-module *teams* 

Se il modulo non viene visualizzato MicrosoftTeams , installarlo per primo. Per installare il modulo, è necessario eseguire PowerShell come amministratore. Poi eseguire quindi il comando seguente.

	Install-Module -Name MicrosoftTeams

Verranno informati sui moduli che verranno installati, che è possibile confermare con una Y risposta o A . Se il modulo è installato ma non aggiornato, è possibile eseguire il comando seguente per aggiornare il modulo:

	Update-Module MicrosoftTeams

2. Connettersi al modulo di Microsoft Teams

Quando il modulo è installato e pronto, è possibile connettersi al modulo MicrosoftTeams con il comando seguente. Verrà visualizzata una finestra interattiva per l'accesso. L'account utente che si intende usare deve avere le autorizzazioni di amministratore di Teams. In caso contrario, è possibile ottenere una access denied risposta nei passaggi successivi.

Connect-MicrosoftTeams

3. Abilitare la configurazione del tenant

L'interoperabilità con le risorse di Servizi di comunicazione viene controllata tramite la configurazione del tenant e i criteri assegnati. Il tenant di Teams ha una configurazione a tenant singolo e gli utenti di Teams hanno assegnato criteri globali o criteri personalizzati. Per altre informazioni, vedere Assegnare criteri in Teams.

Al termine dell'accesso, è possibile eseguire il cmdlet Set-CsTeamsAcsFederationConfiguration per abilitare la risorsa servizi di comunicazione nel tenant. Sostituire il testo IMMUTABLE_RESOURCE_ID con un ID risorsa non modificabile nella risorsa di comunicazione. Per altri dettagli su come ottenere queste informazioni , vedere qui.

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

4. Abilitare i criteri del tenant

A ogni utente di Teams è stato assegnato un oggetto External Access Policy che determina se gli utenti di Servizi di comunicazione possono chiamare l'utente di Teams. Usare il cmdlet Set-CsExternalAccessPolicy per assicurarsi che i criteri assegnati all'utente di Teams siano impostati su EnableAcsFederationAccess$true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Creare o selezionare Coda chiamate di Teams

La coda di chiamate di Teams è una funzionalità di Microsoft Teams che distribuisce in modo efficiente le chiamate in ingresso tra un gruppo di utenti o agenti designati. È utile per gli scenari di assistenza clienti o call center. Le chiamate vengono inserite in una coda e assegnate all'agente disponibile successivo in base a un metodo di routing predeterminato. Gli agenti ricevono notifiche e possono gestire le chiamate usando i controlli delle chiamate di Teams. La funzionalità offre report e analisi per il rilevamento delle prestazioni. Semplifica la gestione delle chiamate, garantisce un'esperienza cliente coerente e ottimizza la produttività degli agenti. È possibile selezionare una coda di chiamate esistente o crearne una nuova tramite l'interfaccia di amministrazione di Teams.

Altre informazioni su come creare una coda di chiamate usando l'interfaccia di amministrazione di Teams sono disponibili qui.

Trovare l'ID oggetto per la coda di chiamate

Dopo aver creato la coda delle chiamate, è necessario trovare l'ID oggetto correlato per usarlo in un secondo momento per le chiamate. L'ID oggetto è connesso all'account risorsa collegato alla coda delle chiamate. Aprire la scheda Account risorsa nell'amministratore di Teams e trovare il messaggio di posta elettronica. Screenshot degli account delle risorse nel portale di amministrazione di Teams. Tutte le informazioni necessarie per l'account di risorsa sono disponibili in Microsoft Graph Explorer usando questo messaggio di posta elettronica nella ricerca.

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

Nei risultati sarà possibile trovare il campo "ID"

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

Prerequisiti

Configurazione

Creare una nuova applicazione Node.js

Aprire la finestra del terminale o di comando, creare una nuova directory per l'app e passare alla directory.

mkdir calling-quickstart && cd calling-quickstart

Installare il pacchetto

Usare il comando npm install per installare SDK di chiamata di Servizi di comunicazione di Azure per JavaScript.

Importante

Questa guida introduttiva usa la versione nextServizi di comunicazione di Azure Calling SDK .

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

Configurare il framework dell'app

Questa guida di avvio rapido usa webpack per aggregare gli asset dell'applicazione. Eseguire il comando seguente per installare i pacchetti npm webpack, webpack-cli e webpack-dev-server ed elencarli come dipendenze di sviluppo in 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

Creare un file index.html nella directory radice del progetto. Questo file verrà usato per configurare un layout di base che consentirà all'utente di effettuare una videochiamata 1:1.

Ecco il codice:

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

Modello a oggetti di Servizi di comunicazione di Azure che chiama Web SDK

Le classi e le interfacce seguenti gestiscono alcune delle principali funzionalità della libreria client Chiamate di Servizi di comunicazione di Azure:

Nome Descrizione
CallClient Punto di ingresso principale di Calling SDK.
CallAgent Usato per avviare e gestire le chiamate.
DeviceManager Usato per gestire i dispositivi multimediali.
Call Utilizzato per rappresentare una chiamata.
LocalVideoStream Usato per creare un flusso video locale per un dispositivo fotocamera nel sistema locale.
RemoteParticipant Utilizzato per rappresentare un partecipante remoto nella chiamata.
RemoteVideoStream Utilizzato per rappresentare un flusso video remoto da un partecipante remoto.

Nella directory radice del progetto creare un file denominato client.js in cui contenere la logica dell'applicazione per questa guida di avvio rapido. Aggiungere il codice seguente 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();
});

Aggiungere il codice del server locale webpack

Creare un file nella directory radice del progetto denominato webpack.config.js per contenere la logica del server locale per questa guida introduttiva. Aggiungere il codice seguente 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'
            ]
        }),
    ]
};

Eseguire il codice

Usare webpack-dev-server per compilare ed eseguire l'app. Eseguire il comando seguente per aggregare l'host dell'applicazione in un server Web locale:

npx webpack serve --config webpack.config.js

Passaggi manuali per configurare la chiamata:

  1. Aprire il browser e passare a http://localhost:8080/.
  2. Immettere un token di accesso utente valido. Se non è disponibile un token, vedere la documentazione dei token di accesso utente.
  3. Fare clic sui pulsanti "Inizializza agente di chiamata".
  4. Immettere l'ID oggetto coda chiamate e selezionare il pulsante "Avvia chiamata". L'applicazione avvierà la chiamata in uscita alla coda di chiamate con l'ID oggetto specificato.
  5. La chiamata è connessa alla coda delle chiamate.
  6. L'utente di Servizi di comunicazione viene instradato tramite coda di chiamate in base alla configurazione.

In questa guida introduttiva si apprenderà come avviare una chiamata da Servizi di comunicazione di Azure utente alla coda di chiamate di Teams. L'operazione verrà ottenuta seguendo questa procedura:

  1. Abilitare la federazione di Servizi di comunicazione di Azure risorsa con il tenant di Teams.
  2. Selezionare o creare una coda di chiamate di Teams tramite l'interfaccia di amministrazione di Teams.
  3. Ottenere l'indirizzo di posta elettronica della coda di chiamate tramite l'interfaccia di amministrazione di Teams.
  4. Ottenere l'ID oggetto della coda di chiamate tramite l'API Graph.
  5. Avviare una chiamata con Servizi di comunicazione di Azure Calling SDK.

Se si vuole passare direttamente alla fine, è possibile scaricare questa guida introduttiva come esempio in GitHub.

Abilitare l'interoperabilità nel tenant di Teams

L'utente Di Microsoft Entra con il ruolo di amministratore di Teams può eseguire il cmdlet di PowerShell con il modulo MicrosoftTeams per abilitare la risorsa servizi di comunicazione nel tenant.

1. Preparare il modulo di Microsoft Teams

Prima di tutto, aprire PowerShell e convalidare l'esistenza del modulo Teams con il comando seguente:

Get-module *teams* 

Se il modulo non viene visualizzato MicrosoftTeams , installarlo per primo. Per installare il modulo, è necessario eseguire PowerShell come amministratore. Poi eseguire quindi il comando seguente.

	Install-Module -Name MicrosoftTeams

Verranno informati sui moduli che verranno installati, che è possibile confermare con una Y risposta o A . Se il modulo è installato ma non aggiornato, è possibile eseguire il comando seguente per aggiornare il modulo:

	Update-Module MicrosoftTeams

2. Connettersi al modulo di Microsoft Teams

Quando il modulo è installato e pronto, è possibile connettersi al modulo MicrosoftTeams con il comando seguente. Verrà visualizzata una finestra interattiva per l'accesso. L'account utente che si intende usare deve avere le autorizzazioni di amministratore di Teams. In caso contrario, è possibile ottenere una access denied risposta nei passaggi successivi.

Connect-MicrosoftTeams

3. Abilitare la configurazione del tenant

L'interoperabilità con le risorse di Servizi di comunicazione viene controllata tramite la configurazione del tenant e i criteri assegnati. Il tenant di Teams ha una configurazione a tenant singolo e gli utenti di Teams hanno assegnato criteri globali o criteri personalizzati. Per altre informazioni, vedere Assegnare criteri in Teams.

Al termine dell'accesso, è possibile eseguire il cmdlet Set-CsTeamsAcsFederationConfiguration per abilitare la risorsa servizi di comunicazione nel tenant. Sostituire il testo IMMUTABLE_RESOURCE_ID con un ID risorsa non modificabile nella risorsa di comunicazione. Per altri dettagli su come ottenere queste informazioni , vedere qui.

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

4. Abilitare i criteri del tenant

A ogni utente di Teams è stato assegnato un oggetto External Access Policy che determina se gli utenti di Servizi di comunicazione possono chiamare l'utente di Teams. Usare il cmdlet Set-CsExternalAccessPolicy per assicurarsi che i criteri assegnati all'utente di Teams siano impostati su EnableAcsFederationAccess$true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Creare o selezionare Coda chiamate di Teams

La coda di chiamate di Teams è una funzionalità di Microsoft Teams che distribuisce in modo efficiente le chiamate in ingresso tra un gruppo di utenti o agenti designati. È utile per gli scenari di assistenza clienti o call center. Le chiamate vengono inserite in una coda e assegnate all'agente disponibile successivo in base a un metodo di routing predeterminato. Gli agenti ricevono notifiche e possono gestire le chiamate usando i controlli delle chiamate di Teams. La funzionalità offre report e analisi per il rilevamento delle prestazioni. Semplifica la gestione delle chiamate, garantisce un'esperienza cliente coerente e ottimizza la produttività degli agenti. È possibile selezionare una coda di chiamate esistente o crearne una nuova tramite l'interfaccia di amministrazione di Teams.

Altre informazioni su come creare una coda di chiamate usando l'interfaccia di amministrazione di Teams sono disponibili qui.

Trovare l'ID oggetto per la coda di chiamate

Dopo aver creato la coda delle chiamate, è necessario trovare l'ID oggetto correlato per usarlo in un secondo momento per le chiamate. L'ID oggetto è connesso all'account risorsa collegato alla coda delle chiamate: aprire la scheda Account risorsa nell'amministratore di Teams e trovare la posta elettronica. Screenshot degli account delle risorse nel portale di amministrazione di Teams. Tutte le informazioni necessarie per l'account di risorsa sono disponibili in Microsoft Graph Explorer usando questo messaggio di posta elettronica nella ricerca.

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

Nei risultati sarà possibile trovare il campo "ID"

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

Per usare nell'app chiamante, è necessario aggiungere un prefisso a questo ID. Attualmente sono supportati gli elementi seguenti:

  • Coda di chiamate cloud pubblico: 28:orgid:<id>
  • Coda di chiamate cloud per enti pubblici: 28:gcch:<id>

Prerequisiti

Configurazione

Creare un'app Android con un'attività vuota

In Android Studio avviare un nuovo progetto.

Screenshot che mostra il pulsante per l'avvio di un nuovo progetto di Android Studio selezionato in Android Studio.

Selezionare il modello di progetto "Empty Views Activity" (Attività vuota) in "Phone and Tablet" (Telefono e tablet).

Screenshot che mostra l'opzione 'Empty Activity' selezionata nella schermata del modello di progetto.

Selezionare Minimum SDK (SDK minimo) di "API 26: Android 8.0 (Oreo)" o versione successiva.

Screenshot che mostra l'opzione 'Empty Activity' selezionata nella schermata 2 del modello di progetto.

Installare il pacchetto

Individuare il settings.gradle.kts del progetto e assicurarsi di visualizzare mavenCentral() nell'elenco dei repository in pluginManagement e dependencyResolutionManagement

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

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

In build.gradle a livello di modulo aggiungere quindi le righe seguenti alle sezioni dependencies e android

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

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

Aggiungere autorizzazioni al manifesto dell'applicazione

Per richiedere le autorizzazioni necessarie per effettuare una chiamata, bisogna dichiararle nel manifesto dell'applicazione (app/src/main/AndroidManifest.xml). Sostituire il contenuto del file con il codice seguente:

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

Configurare il layout per l'app

Sono necessari due input: un input di testo per l'ID del destinatario e un pulsante per effettuare la chiamata. Questi input possono essere aggiunti tramite la finestra di progettazione o modificando il codice XML del layout. Creare un pulsante con un ID call_button e un input di testo callee_id. Passare a app/src/main/res/layout/activity_main.xml e sostituire il contenuto del file con il codice segue:

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

Creare lo scaffolding e i binding per l'attività principale

Con il layout creato è possibile aggiungere i binding e lo scaffolding di base dell'attività. L'attività gestisce la richiesta delle autorizzazioni in fase di esecuzione, la creazione dell'agente di chiamata e l'effettuazione della chiamata quando viene premuto il pulsante. Il metodo onCreate viene sottoposto a override per richiamare getAllPermissions e createAgent e aggiungere le associazioni per il pulsante di chiamata. Questo evento si verifica una sola volta quando viene creata l'attività. Per altre informazioni su onCreate, vedere la guida sul ciclo di vita dell'attività.

Passare a MainActivity.java e sostituire il contenuto con il codice seguente:

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

Richiedere autorizzazioni in fase di esecuzione

Per Android 6.0 e versioni successive (livello API 23) e targetSdkVersion 23 o versione successiva, le autorizzazioni vengono concesse in fase di esecuzione invece che durante l'installazione dell'app. Per supportare questo requisito, è possibile implementare getAllPermissions per chiamare ActivityCompat.checkSelfPermission e ActivityCompat.requestPermissions per ogni autorizzazione richiesta.

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

Quando si progetta l'app, valutare quando dovranno essere richieste queste autorizzazioni. È consigliabile che vengano richieste quando è necessario, non prima. Per altre informazioni, vedere la guida alle autorizzazioni di Android..

Modello a oggetti

Le classi e le interfacce seguenti gestiscono alcune delle principali funzionalità del SDK Chiamate di Servizi di comunicazione di Azure:

Nome Descrizione
CallClient CallClient è il punto di ingresso principale di Calling SDK.
CallAgent CallAgent viene usato per avviare e gestire le chiamate.
CommunicationTokenCredential CommunicationTokenCredential viene usato come credenziale del token per creare un'istanza di CallAgent.
CommunicationIdentifier CommunicationIdentifier viene usato come tipo diverso di partecipante che potrebbe far parte di una chiamata.

Creare un agente dal token di accesso utente

Con un token utente è possibile creare un'istanza di un agente di chiamata autenticato. In genere, questo token viene generato da un servizio con l'autenticazione specifica per l'applicazione. Per altre informazioni sui token di accesso utente, vedere la guida ai token di accesso utente.

Per la guida di avvio rapido, sostituire <User_Access_Token> con un token di accesso utente generato per la risorsa del servizio di comunicazione di 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();
    }
}

Eseguire il codice

L'app può ora essere avviata usando il pulsante "Esegui app" sulla barra degli strumenti.

Passaggi manuali per configurare la chiamata:

  1. Avviare l'app con Android Studio.
  2. Immettere l'ID oggetto coda di chiamata (con prefisso) e selezionare il pulsante "Avvia chiamata". L'applicazione avvierà la chiamata in uscita alla coda di chiamata con l'ID oggetto specificato.
  3. La chiamata è connessa alla coda delle chiamate.
  4. L'utente di Servizi di comunicazione viene instradato tramite coda di chiamate in base alla configurazione.

In questa guida introduttiva si apprenderà come avviare una chiamata da Servizi di comunicazione di Azure utente alla coda di chiamate di Teams. L'operazione verrà ottenuta seguendo questa procedura:

  1. Abilitare la federazione di Servizi di comunicazione di Azure risorsa con il tenant di Teams.
  2. Selezionare o creare una coda di chiamate di Teams tramite l'interfaccia di amministrazione di Teams.
  3. Ottenere l'indirizzo di posta elettronica della coda di chiamate tramite l'interfaccia di amministrazione di Teams.
  4. Ottenere l'ID oggetto della coda di chiamate tramite l'API Graph.
  5. Avviare una chiamata con Servizi di comunicazione di Azure Calling SDK.

Se si vuole passare direttamente alla fine, è possibile scaricare questa guida introduttiva come esempio in GitHub.

Abilitare l'interoperabilità nel tenant di Teams

L'utente Di Microsoft Entra con il ruolo di amministratore di Teams può eseguire il cmdlet di PowerShell con il modulo MicrosoftTeams per abilitare la risorsa servizi di comunicazione nel tenant.

1. Preparare il modulo di Microsoft Teams

Prima di tutto, aprire PowerShell e convalidare l'esistenza del modulo Teams con il comando seguente:

Get-module *teams* 

Se il modulo non viene visualizzato MicrosoftTeams , installarlo per primo. Per installare il modulo, è necessario eseguire PowerShell come amministratore. Poi eseguire quindi il comando seguente.

	Install-Module -Name MicrosoftTeams

Verranno informati sui moduli che verranno installati, che è possibile confermare con una Y risposta o A . Se il modulo è installato ma non aggiornato, è possibile eseguire il comando seguente per aggiornare il modulo:

	Update-Module MicrosoftTeams

2. Connettersi al modulo di Microsoft Teams

Quando il modulo è installato e pronto, è possibile connettersi al modulo MicrosoftTeams con il comando seguente. Verrà visualizzata una finestra interattiva per l'accesso. L'account utente che si intende usare deve avere le autorizzazioni di amministratore di Teams. In caso contrario, è possibile ottenere una access denied risposta nei passaggi successivi.

Connect-MicrosoftTeams

3. Abilitare la configurazione del tenant

L'interoperabilità con le risorse di Servizi di comunicazione viene controllata tramite la configurazione del tenant e i criteri assegnati. Il tenant di Teams ha una configurazione a tenant singolo e gli utenti di Teams hanno assegnato criteri globali o criteri personalizzati. Per altre informazioni, vedere Assegnare criteri in Teams.

Al termine dell'accesso, è possibile eseguire il cmdlet Set-CsTeamsAcsFederationConfiguration per abilitare la risorsa servizi di comunicazione nel tenant. Sostituire il testo IMMUTABLE_RESOURCE_ID con un ID risorsa non modificabile nella risorsa di comunicazione. Per altri dettagli su come ottenere queste informazioni , vedere qui.

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

4. Abilitare i criteri del tenant

A ogni utente di Teams è stato assegnato un oggetto External Access Policy che determina se gli utenti di Servizi di comunicazione possono chiamare l'utente di Teams. Usare il cmdlet Set-CsExternalAccessPolicy per assicurarsi che i criteri assegnati all'utente di Teams siano impostati su EnableAcsFederationAccess$true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Creare o selezionare Coda chiamate di Teams

La coda di chiamate di Teams è una funzionalità di Microsoft Teams che distribuisce in modo efficiente le chiamate in ingresso tra un gruppo di utenti o agenti designati. È utile per gli scenari di assistenza clienti o call center. Le chiamate vengono inserite in una coda e assegnate all'agente disponibile successivo in base a un metodo di routing predeterminato. Gli agenti ricevono notifiche e possono gestire le chiamate usando i controlli delle chiamate di Teams. La funzionalità offre report e analisi per il rilevamento delle prestazioni. Semplifica la gestione delle chiamate, garantisce un'esperienza cliente coerente e ottimizza la produttività degli agenti. È possibile selezionare una coda di chiamate esistente o crearne una nuova tramite l'interfaccia di amministrazione di Teams.

Altre informazioni su come creare una coda di chiamate usando l'interfaccia di amministrazione di Teams sono disponibili qui.

Trovare l'ID oggetto per la coda di chiamate

Dopo aver creato la coda delle chiamate, è necessario trovare l'ID oggetto correlato per usarlo in un secondo momento per le chiamate. L'ID oggetto è connesso all'account risorsa collegato alla coda delle chiamate: aprire la scheda Account risorsa nell'amministratore di Teams e trovare la posta elettronica. Screenshot degli account delle risorse nel portale di amministrazione di Teams. Tutte le informazioni necessarie per l'account di risorsa sono disponibili in Microsoft Graph Explorer usando questo messaggio di posta elettronica nella ricerca.

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

Nei risultati sarà possibile trovare il campo "ID"

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

Per usare nell'app chiamante, è necessario aggiungere un prefisso a questo ID. Attualmente sono supportati gli elementi seguenti:

  • Coda di chiamate cloud pubblico: 28:orgid:<id>
  • Coda di chiamate cloud per enti pubblici: 28:gcch:<id>

Prerequisiti

Configurazione

Creazione del progetto Xcode

In Xcode creare un nuovo progetto iOS e selezionare il modello App. Questa esercitazione usa il framework SwiftUI, quindi è necessario impostare Linguaggio su Swift e Interfaccia utente su SwiftUI. In questo argomento di avvio rapido non verranno creati test. È possibile deselezionare Includi test.

Screenshot che mostra la finestra relativa al nuovo progetto in Xcode.

Installare il pacchetto e le dipendenze con CocoaPods

  1. Per creare un podfile per l'applicazione, aprire il terminale e passare alla cartella del progetto ed eseguire:

    pod init

  2. Aggiungere il codice seguente al Podfile e salvare (assicurarsi che "target" corrisponda al nome del progetto):

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

  4. Aprire .xcworkspace con Xcode.

Richiedere l'accesso al microfono

Per accedere al microfono del dispositivo, è necessario aggiornare il file Information Property List dell'app con NSMicrophoneUsageDescription. Impostare il valore associato su un oggetto string che viene incluso nella finestra di dialogo usata dal sistema per richiedere l'accesso dell'utente.

Fare clic con il pulsante destro del mouse sulla voce Info.plist dell'albero del progetto e scegliere Open As>Source Code (Apri come > Codice sorgente). Aggiungere le righe seguenti nella sezione <dict> di primo livello e quindi salvare il file.

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

Configurare il framework dell'app

Aprire il file ContentView.swift del progetto e aggiungere una dichiarazione import all'inizio del file per importare AzureCommunicationCalling library. Inoltre, importare AVFoundation, questo codice è necessario per la richiesta di autorizzazione audio nel codice.

import AzureCommunicationCalling
import AVFoundation

Sostituire l'implementazione dello struct ContentView con alcuni semplici controlli dell'interfaccia utente che consentano a un utente di avviare e terminare una chiamata. In questa guida di avvio rapido viene collegata logica di business a questi controlli.

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

Modello a oggetti

Le classi e le interfacce seguenti gestiscono alcune delle principali funzionalità del SDK Chiamate di Servizi di comunicazione di Azure:

Nome Descrizione
CallClient CallClient è il punto di ingresso principale di Calling SDK.
CallAgent CallAgent viene usato per avviare e gestire le chiamate.
CommunicationTokenCredential CommunicationTokenCredential viene usato come credenziale del token per creare un'istanza di CallAgent.
CommunicationUserIdentifier CommunicationUserIdentifier viene usato per rappresentare l'identità dell'utente, che può essere una delle opzioni seguenti: CommunicationUserIdentifier, PhoneNumberIdentifier o CallingApplication.

Autenticare il client

Inizializzare un'istanza di CallAgent con un token di accesso utente, che consente di effettuare e ricevere chiamate.

Nel codice seguente è necessario sostituire <USER ACCESS TOKEN> con un token di accesso utente valido per la risorsa. Se non è disponibile un token, vedere la documentazione dei token di accesso utente.

Aggiungere il codice seguente al callback onAppear in 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.")
    }
}

Avvia una chiamata

Il metodo startCall viene impostato come azione che viene eseguita quando viene toccato il pulsante Start Call (Avvia chiamata). Aggiornare l'implementazione per avviare una chiamata 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")
                }
            }
        }
    }
}

È anche possibile usare le proprietà in StartCallOptions per impostare le opzioni iniziali per la chiamata, ad esempio per consentire l'avvio della chiamata con il microfono disattivato.

Terminare una chiamata

Implementare il metodo endCall per terminare la chiamata corrente quando viene toccato il pulsante End Call (Termina chiamata).

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

Eseguire il codice

È possibile creare ed eseguire un'app nel simulatore iOS selezionando Product>Run (Prodotto > Esegui) o premendo i tasti di scelta rapida (⌘-R).

Nota

La prima volta che si effettua una chiamata, il sistema chiederà l'accesso al microfono. In un'applicazione di produzione è consigliabile usare l'API AVAudioSession per controllare lo stato delle autorizzazioni e aggiornare normalmente il comportamento dell'applicazione quando l'autorizzazione non viene concessa.

Passaggi manuali per configurare la chiamata:

  1. Avviare l'app con Xcode
  2. Immettere l'ID oggetto coda di chiamata (con prefisso) e selezionare il pulsante "Avvia chiamata". L'applicazione avvierà la chiamata in uscita alla coda di chiamata con l'ID oggetto specificato.
  3. La chiamata è connessa alla coda delle chiamate.
  4. L'utente di Servizi di comunicazione viene instradato tramite coda di chiamate in base alla configurazione.

In questa guida introduttiva si apprenderà come avviare una chiamata da Servizi di comunicazione di Azure utente alla coda di chiamate di Teams. L'operazione verrà ottenuta seguendo questa procedura:

  1. Abilitare la federazione di Servizi di comunicazione di Azure risorsa con il tenant di Teams.
  2. Selezionare o creare una coda di chiamate di Teams tramite l'interfaccia di amministrazione di Teams.
  3. Ottenere l'indirizzo di posta elettronica della coda di chiamate tramite l'interfaccia di amministrazione di Teams.
  4. Ottenere l'ID oggetto della coda di chiamate tramite l'API Graph.
  5. Avviare una chiamata con Servizi di comunicazione di Azure Calling SDK.

Se si vuole passare direttamente alla fine, è possibile scaricare questa guida introduttiva come esempio in GitHub.

Abilitare l'interoperabilità nel tenant di Teams

L'utente Di Microsoft Entra con il ruolo di amministratore di Teams può eseguire il cmdlet di PowerShell con il modulo MicrosoftTeams per abilitare la risorsa servizi di comunicazione nel tenant.

1. Preparare il modulo di Microsoft Teams

Prima di tutto, aprire PowerShell e convalidare l'esistenza del modulo Teams con il comando seguente:

Get-module *teams* 

Se il modulo non viene visualizzato MicrosoftTeams , installarlo per primo. Per installare il modulo, è necessario eseguire PowerShell come amministratore. Poi eseguire quindi il comando seguente.

	Install-Module -Name MicrosoftTeams

Verranno informati sui moduli che verranno installati, che è possibile confermare con una Y risposta o A . Se il modulo è installato ma non aggiornato, è possibile eseguire il comando seguente per aggiornare il modulo:

	Update-Module MicrosoftTeams

2. Connettersi al modulo di Microsoft Teams

Quando il modulo è installato e pronto, è possibile connettersi al modulo MicrosoftTeams con il comando seguente. Verrà visualizzata una finestra interattiva per l'accesso. L'account utente che si intende usare deve avere le autorizzazioni di amministratore di Teams. In caso contrario, è possibile ottenere una access denied risposta nei passaggi successivi.

Connect-MicrosoftTeams

3. Abilitare la configurazione del tenant

L'interoperabilità con le risorse di Servizi di comunicazione viene controllata tramite la configurazione del tenant e i criteri assegnati. Il tenant di Teams ha una configurazione a tenant singolo e gli utenti di Teams hanno assegnato criteri globali o criteri personalizzati. Per altre informazioni, vedere Assegnare criteri in Teams.

Al termine dell'accesso, è possibile eseguire il cmdlet Set-CsTeamsAcsFederationConfiguration per abilitare la risorsa servizi di comunicazione nel tenant. Sostituire il testo IMMUTABLE_RESOURCE_ID con un ID risorsa non modificabile nella risorsa di comunicazione. Per altri dettagli su come ottenere queste informazioni , vedere qui.

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

4. Abilitare i criteri del tenant

A ogni utente di Teams è stato assegnato un oggetto External Access Policy che determina se gli utenti di Servizi di comunicazione possono chiamare l'utente di Teams. Usare il cmdlet Set-CsExternalAccessPolicy per assicurarsi che i criteri assegnati all'utente di Teams siano impostati su EnableAcsFederationAccess$true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Creare o selezionare Coda chiamate di Teams

La coda di chiamate di Teams è una funzionalità di Microsoft Teams che distribuisce in modo efficiente le chiamate in ingresso tra un gruppo di utenti o agenti designati. È utile per gli scenari di assistenza clienti o call center. Le chiamate vengono inserite in una coda e assegnate all'agente disponibile successivo in base a un metodo di routing predeterminato. Gli agenti ricevono notifiche e possono gestire le chiamate usando i controlli delle chiamate di Teams. La funzionalità offre report e analisi per il rilevamento delle prestazioni. Semplifica la gestione delle chiamate, garantisce un'esperienza cliente coerente e ottimizza la produttività degli agenti. È possibile selezionare una coda di chiamate esistente o crearne una nuova tramite l'interfaccia di amministrazione di Teams.

Altre informazioni su come creare una coda di chiamate usando l'interfaccia di amministrazione di Teams sono disponibili qui.

Trovare l'ID oggetto per la coda di chiamate

Dopo aver creato la coda delle chiamate, è necessario trovare l'ID oggetto correlato per usarlo in un secondo momento per le chiamate. L'ID oggetto è connesso all'account risorsa collegato alla coda delle chiamate: aprire la scheda Account risorsa nell'amministratore di Teams e trovare la posta elettronica. Screenshot degli account delle risorse nel portale di amministrazione di Teams. Tutte le informazioni necessarie per l'account di risorsa sono disponibili in Microsoft Graph Explorer usando questo messaggio di posta elettronica nella ricerca.

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

Nei risultati sarà possibile trovare il campo "ID"

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

Per usare nell'app chiamante, è necessario aggiungere un prefisso a questo ID. Attualmente sono supportati gli elementi seguenti:

  • Coda di chiamate cloud pubblico: 28:orgid:<id>
  • Coda di chiamate cloud per enti pubblici: 28:gcch:<id>

Prerequisiti

Per completare questa esercitazione è necessario soddisfare i prerequisiti seguenti:

Configurazione

Creazione del progetto

In Visual Studio creare un nuovo progetto con il modello App vuota (Windows universale) per configurare un'app UWP (Universal Windows Platform) a pagina singola.

Screenshot che mostra la finestra Nuovo progetto UWP in Visual Studio.

Installare il pacchetto

Selezionare il progetto e passare a Manage Nuget Packages per installare Azure.Communication.Calling.WindowsClient 1.4.0 o superiore. Assicurarsi che Include Prerelease sia verificato se si desidera visualizzare le versioni per l'anteprima pubblica.

Richiedere l'accesso

Passare a Package.appxmanifest e selezionare Capabilities. Controllare Internet (Client) e Internet (Client & Server) per ottenere l'accesso in ingresso e in uscita a Internet. Controllare Microphone per accedere al feed audio del microfono e Webcam per accedere al feed video della fotocamera.

Screenshot che mostra la richiesta di accesso a Internet e microfono in Visual Studio.

Configurare il framework dell'app

È necessario configurare un layout di base per collegare la logica. Per effettuare una chiamata in uscita, è necessario un TextBox per fornire l'ID utente del chiamato. È anche necessario un pulsante Start/Join call e un pulsante Hang up. In questo esempio sono inclusi anche delle caselle di controllo Mute e BackgroundBlur per illustrare le funzionalità di attivazione/disattivazione degli stati audio e degli effetti video.

Aprire il MainPage.xaml del progetto e aggiungere il nodo Grid al 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>

Aprire MainPage.xaml.cs e sostituire il contenuto con l'implementazione seguente:

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

Modello a oggetti

La tabella successiva elenca le classi e le interfacce che gestiscono alcune delle principali funzionalità di Azure Communication Services Calling SDK:

Nome Descrizione
CallClient CallClient è il punto di ingresso principale di Calling SDK.
CallAgent CallAgent viene usato per avviare e gestire le chiamate.
CommunicationCall CommunicationCall viene utilizzato per gestire una chiamata in corso.
CallTokenCredential CallTokenCredential viene usato come credenziale del token per creare un'istanza di CallAgent.
CallIdentifier CallIdentifier viene usato per rappresentare l'identità dell'utente, che può essere una delle opzioni seguenti: UserCallIdentifier, PhoneNumberCallIdentifier e così via.

Autenticare il client

Inizializzare un'istanza CallAgent con un token di accesso utente che consente di effettuare e ricevere chiamate e, facoltativamente, ottenere un'istanza di DeviceManager per eseguire query per le configurazioni del dispositivo client.

Nel codice, sostituire <AUTHENTICATION_TOKEN> con un token di accesso utente. Se non è disponibile un token, vedere la documentazione dei token di accesso utente.

Aggiungere la funzione InitCallAgentAndDeviceManagerAsync, che esegue il bootstrap dell'SDK. Questo helper può essere personalizzato per soddisfare i requisiti dell'applicazione.

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

Avviare la chiamata

Dopo aver ottenuto un oggetto StartCallOptions, è possibile usare CallAgent per avviare la chiamata di Servizi di comunicazione di 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;
        }

Terminare una chiamata

Terminare la chiamata corrente quando si fa clic sul pulsante Hang up. Aggiungere l'implementazione all’HangupButton_Click per terminare una chiamata e arrestare i flussi di anteprima e video.

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

Alternanza attiva/disattiva audio

Disattivare l'audio in uscita quando si fa clic sul pulsante Mute. Aggiungere l'implementazione al MuteLocal_Click per disattivare la chiamata.

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

Accettare una chiamata in arrivo.

Il sink di eventi IncomingCallReceived viene configurato nell'helper bootstrap dell'SDK InitCallAgentAndDeviceManagerAsync.

    this.callAgent.IncomingCallReceived += OnIncomingCallAsync;

L'applicazione ha la possibilità di configurare la modalità di accettazione della chiamata in ingresso, ad esempio i tipi di flusso video e 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;
        }

Monitorare e rispondere all'evento di modifica dello stato della chiamata

L’evento StateChanged sull'oggetto CommunicationCall viene generato quando un oggetto in corso chiama transazioni da uno stato a un altro. L'applicazione offre le opportunità di riflettere le modifiche dello stato nell'interfaccia utente o inserire le logiche di business.

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

Far funzionare il pulsante di chiamata

Una volta che l'oggetto Callee ID non è null o vuoto, è possibile avviare una chiamata.

Lo stato della chiamata deve essere modificato usando l'azione 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;
        }
    
        
    }

Eseguire il codice

È possibile compilare ed eseguire il codice in Visual Studio. Per le piattaforme di soluzioni, è supportato ARM64, x64 e x86.

Passaggi manuali per configurare la chiamata:

  1. Avviare l'app con Visual Studio.
  2. Immettere l'ID oggetto coda di chiamata (con prefisso) e selezionare il pulsante "Avvia chiamata". L'applicazione avvierà la chiamata in uscita alla coda di chiamata con l'ID oggetto specificato.
  3. La chiamata è connessa alla coda delle chiamate.
  4. L'utente di Servizi di comunicazione viene instradato tramite coda di chiamate in base alla configurazione.

Pulire le risorse

Se si vuole pulire e rimuovere una sottoscrizione a Servizi di comunicazione, è possibile eliminare la risorsa o il gruppo di risorse. L'eliminazione del gruppo di risorse comporta anche l'eliminazione di tutte le altre risorse associate. Altre informazioni sulla pulizia delle risorse.

Passaggi successivi

Per altre informazioni, vedere gli articoli seguenti: