Поделиться через


Краткое руководство. Присоединение вызывающего приложения к очереди вызовов Teams

В этом кратком руководстве вы узнаете, как начать звонок из Службы коммуникации Azure пользователя в очередь вызовов Teams. Вы собираетесь достичь этого с помощью следующих действий:

  1. Включите федерацию ресурсов Службы коммуникации Azure с клиентом Teams.
  2. Выберите или создайте очередь звонков Teams с помощью Центра администрирования Teams.
  3. Получение адреса электронной почты очереди звонков с помощью Центра администрирования Teams.
  4. Получение идентификатора объекта очереди вызовов с помощью API Graph.
  5. Запустите вызов с помощью пакета SDK для вызовов Службы коммуникации Azure.

Если вы хотите сразу перейти к завершающему этапу, можно скачать это краткое руководство в качестве примера с портала GitHub.

Включение взаимодействия в клиенте Teams

Пользователь Microsoft Entra с ролью администратора Teams может запустить командлет PowerShell с модулем MicrosoftTeams, чтобы включить ресурс Служб коммуникации в клиенте.

1. Подготовка модуля Microsoft Teams

Сначала откройте PowerShell и проверьте существование модуля Teams с помощью следующей команды:

Get-module *teams* 

Если модуль не отображается MicrosoftTeams , сначала установите его. Чтобы установить модуль, необходимо запустить PowerShell от имени администратора. Затем выполните следующую команду.

	Install-Module -Name MicrosoftTeams

Вы будете проинформированы о модулях, которые будут установлены, которые можно подтвердить с помощью Y или A ответа. Если модуль установлен, но устарел, можно выполнить следующую команду, чтобы обновить модуль:

	Update-Module MicrosoftTeams

2. Подключение к модулю Microsoft Teams

После установки и готовности модуля можно подключиться к модулю MicrosoftTeams с помощью следующей команды. Вам будет предложено выполнить вход в интерактивное окно. Учетная запись пользователя, которую вы собираетесь использовать, должны иметь разрешения администратора Teams. В противном случае вы можете получить access denied ответ в следующих шагах.

Connect-MicrosoftTeams

3. Включение конфигурации клиента

Взаимодействие с ресурсами Служб коммуникации управляется с помощью конфигурации клиента и назначенной политики. Клиент Teams имеет одну конфигурацию клиента, а пользователи Teams назначили глобальную политику или пользовательскую политику. Дополнительные сведения см. в разделе "Назначение политик" в Teams.

После успешного входа можно запустить командлет Set-CsTeamsAcsFederationConfiguration , чтобы включить ресурс Служб коммуникации в клиенте. Замените текст IMMUTABLE_RESOURCE_ID неизменяемым идентификатором ресурса в ресурсе связи. Дополнительные сведения см . здесь.

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

4. Включение политики клиента

Каждому пользователю Teams назначено значение, определяющее External Access Policy , могут ли пользователи Служб коммуникации вызывать этого пользователя Teams. Используйте командлет Set-CsExternalAccessPolicy, чтобы убедиться, что политика, назначенная пользователю Teams, имеет значение EnableAcsFederationAccess$true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Создание или выбор очереди вызовов Teams

Очередь звонков Teams — это функция в Microsoft Teams, которая эффективно распределяет входящие вызовы среди группы назначенных пользователей или агентов. Это полезно для сценариев поддержки клиентов или центра обработки вызовов. Вызовы помещаются в очередь и назначаются следующему доступному агенту на основе предопределенного метода маршрутизации. Агенты получают уведомления и могут обрабатывать вызовы с помощью элементов управления вызовами Teams. Эта функция предоставляет отчеты и аналитику для отслеживания производительности. Это упрощает обработку вызовов, обеспечивает согласованный интерфейс клиента и оптимизирует производительность агента. Вы можете выбрать существующую или создать новую очередь звонков с помощью Центра администрирования Teams.

Дополнительные сведения о создании очереди вызовов с помощью Центра администрирования Teams см. здесь.

Поиск идентификатора объекта для очереди вызовов

После создания очереди вызовов необходимо найти сопоставленный идентификатор объекта, чтобы использовать его позже для вызовов. Идентификатор объекта подключен к учетной записи ресурсов, подключенной к очереди вызовов, откройте вкладку "Учетные записи ресурсов" в администраторе Teams и найдите электронную почту. Снимок экрана: учетные записи ресурсов на портале администрирования Teams. Все необходимые сведения для учетной записи ресурсов можно найти в обозревателе Microsoft Graph с помощью этого сообщения электронной почты в поиске.

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

В результатах мы сможем найти поле "ID"

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

Необходимые компоненты

Установка

Создание нового приложения Node.js

Откройте терминал или командное окно, создайте новый каталог для приложения и перейдите к каталогу.

mkdir calling-quickstart && cd calling-quickstart

Установка пакета

Используйте команду npm install, чтобы установить пакет SDK Служб коммуникации Azure для реализации вызовов на JavaScript.

Внимание

В этом кратком руководстве используется версия пакета SDK Служб коммуникации Azure для вызовов next.

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

Настройка платформы приложения

В этом кратком руководстве для объединения ресурсов приложения используется webpack. Выполните следующую команду, чтобы установить пакеты npm webpack, webpack-cli и webpack-dev-server, а также указать их в качестве зависимостей разработки в 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

index.html Создайте файл в корневом каталоге проекта. Мы будем использовать этот файл для настройки базового макета, с помощью которого пользователь сможет осуществить персональный видеовызов.

Вот этот код:

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

объектная модель веб-пакета SDK Службы коммуникации Azure

Следующие классы и интерфейсы обрабатывают некоторые основные функции пакета SDK для вызовов Службы коммуникации Azure:

Имя Описание
CallClient Основная точка входа в пакет SDK для вызовов.
CallAgent Используется для инициирования вызовов и управления ими.
DeviceManager Используется для управления устройствами мультимедиа.
Call Используется для представления вызова.
LocalVideoStream Используется для создания локального видеопотока для устройства камеры в локальной системе.
RemoteParticipant Используется для представления удаленного участника в вызове.
RemoteVideoStream Используется для представления удаленного видеопотока от удаленного участника.

Создайте файл в корневом каталоге проекта, который будет client.js содержать логику приложения для этого краткого руководства. Добавьте следующий код в файл 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();
});

Добавление кода локального сервера webpack

Создайте файл в корневом каталоге проекта с именем webpack.config.js , чтобы содержать логику локального сервера для этого краткого руководства. Добавьте следующий код в 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'
            ]
        }),
    ]
};

Выполнение кода

Чтобы создать и запустить приложение, используйте webpack-dev-server. Выполните следующую команду, чтобы создать пакет узла приложения на локальном веб-сервере.

npx webpack serve --config webpack.config.js

Инструкции по настройке вызова вручную:

  1. Откройте браузер и перейдите к http://localhost:8080/.
  2. Введите допустимый маркер доступа пользователя. Обратитесь к документации по маркерам доступа пользователя, если у вас еще нет маркеров доступа, доступных для использования.
  3. Нажмите кнопки "Инициализировать агент вызова".
  4. Введите идентификатор объекта очереди вызовов и нажмите кнопку "Пуск вызова". Приложение запустит исходящий вызов в очередь вызовов с заданным идентификатором объекта.
  5. Вызов подключен к очереди вызовов.
  6. Пользователь служб коммуникации направляется через очередь вызовов на основе его конфигурации.

В этом кратком руководстве вы узнаете, как начать звонок из Службы коммуникации Azure пользователя в очередь вызовов Teams. Вы собираетесь достичь этого с помощью следующих действий:

  1. Включите федерацию ресурсов Службы коммуникации Azure с клиентом Teams.
  2. Выберите или создайте очередь звонков Teams с помощью Центра администрирования Teams.
  3. Получение адреса электронной почты очереди звонков с помощью Центра администрирования Teams.
  4. Получение идентификатора объекта очереди вызовов с помощью API Graph.
  5. Запустите вызов с помощью пакета SDK для вызовов Службы коммуникации Azure.

Если вы хотите сразу перейти к завершающему этапу, можно скачать это краткое руководство в качестве примера с портала GitHub.

Включение взаимодействия в клиенте Teams

Пользователь Microsoft Entra с ролью администратора Teams может запустить командлет PowerShell с модулем MicrosoftTeams, чтобы включить ресурс Служб коммуникации в клиенте.

1. Подготовка модуля Microsoft Teams

Сначала откройте PowerShell и проверьте существование модуля Teams с помощью следующей команды:

Get-module *teams* 

Если модуль не отображается MicrosoftTeams , сначала установите его. Чтобы установить модуль, необходимо запустить PowerShell от имени администратора. Затем выполните следующую команду.

	Install-Module -Name MicrosoftTeams

Вы будете проинформированы о модулях, которые будут установлены, которые можно подтвердить с помощью Y или A ответа. Если модуль установлен, но устарел, можно выполнить следующую команду, чтобы обновить модуль:

	Update-Module MicrosoftTeams

2. Подключение к модулю Microsoft Teams

После установки и готовности модуля можно подключиться к модулю MicrosoftTeams с помощью следующей команды. Вам будет предложено выполнить вход в интерактивное окно. Учетная запись пользователя, которую вы собираетесь использовать, должны иметь разрешения администратора Teams. В противном случае вы можете получить access denied ответ в следующих шагах.

Connect-MicrosoftTeams

3. Включение конфигурации клиента

Взаимодействие с ресурсами Служб коммуникации управляется с помощью конфигурации клиента и назначенной политики. Клиент Teams имеет одну конфигурацию клиента, а пользователи Teams назначили глобальную политику или пользовательскую политику. Дополнительные сведения см. в разделе "Назначение политик" в Teams.

После успешного входа можно запустить командлет Set-CsTeamsAcsFederationConfiguration , чтобы включить ресурс Служб коммуникации в клиенте. Замените текст IMMUTABLE_RESOURCE_ID неизменяемым идентификатором ресурса в ресурсе связи. Дополнительные сведения см . здесь.

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

4. Включение политики клиента

Каждому пользователю Teams назначено значение, определяющее External Access Policy , могут ли пользователи Служб коммуникации вызывать этого пользователя Teams. Используйте командлет Set-CsExternalAccessPolicy, чтобы убедиться, что политика, назначенная пользователю Teams, имеет значение EnableAcsFederationAccess$true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Создание или выбор очереди вызовов Teams

Очередь звонков Teams — это функция в Microsoft Teams, которая эффективно распределяет входящие вызовы среди группы назначенных пользователей или агентов. Это полезно для сценариев поддержки клиентов или центра обработки вызовов. Вызовы помещаются в очередь и назначаются следующему доступному агенту на основе предопределенного метода маршрутизации. Агенты получают уведомления и могут обрабатывать вызовы с помощью элементов управления вызовами Teams. Эта функция предоставляет отчеты и аналитику для отслеживания производительности. Это упрощает обработку вызовов, обеспечивает согласованный интерфейс клиента и оптимизирует производительность агента. Вы можете выбрать существующую или создать новую очередь звонков с помощью Центра администрирования Teams.

Дополнительные сведения о создании очереди вызовов с помощью Центра администрирования Teams см. здесь.

Поиск идентификатора объекта для очереди вызовов

После создания очереди вызовов необходимо найти сопоставленный идентификатор объекта, чтобы использовать его позже для вызовов. Идентификатор объекта подключен к учетной записи ресурсов, подключенной к очереди вызовов, откройте вкладку "Учетные записи ресурсов" в администраторе Teams и найдите электронную почту. Снимок экрана: учетные записи ресурсов на портале администрирования Teams. Все необходимые сведения для учетной записи ресурсов можно найти в обозревателе Microsoft Graph с помощью этого сообщения электронной почты в поиске.

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

В результатах мы сможем найти поле "ID"

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

Чтобы использовать в вызывающем приложении, необходимо добавить префикс в этот идентификатор. В настоящее время поддерживаются следующие компоненты:

  • Очередь вызовов общедоступного облака: 28:orgid:<id>
  • Очередь облачных вызовов для государственных организаций: 28:gcch:<id>

Необходимые компоненты

Установка

Создание приложения Android с пустым действием

В Android Studio щелкните Start a new Android Studio project (Создать проект Android Studio).

Снимок экрана с выбранной кнопкой создания нового проекта в Android Studio.

Выберите шаблон проекта "Пустые представления" в разделе "Телефон и планшет".

Снимок экрана с экраном шаблона проекта, где выбран вариант Empty Activity (Пустое действие).

Выберите минимальную версию пакета SDK: "API 26: Android 8.0 (Oreo)" или более позднюю.

Снимок экрана с экраном шаблона проекта, где выбран вариант Empty Activity (Пустое действие) (2).

Установка пакета

Найдите проект settings.gradle.kts и убедитесь, что вы увидите mavenCentral() список репозиториев в pluginManagement разделе и dependencyResolutionManagement

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

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

Затем добавьте в build.gradle на уровне модуля следующие строки в разделах dependencies и android.

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

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

Добавление разрешений в манифест приложения

Чтобы запрашивать разрешения, необходимые для вызова, они должны быть объявлены в манифесте приложения (app/src/main/AndroidManifest.xml). Замените содержимое файла следующим кодом:

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

Настройка макета для приложения

Нам нужны два элемента: текстовое поле для идентификатора вызываемого участника и кнопка для начала вызова. Эти входные данные можно добавить через конструктор или изменить xml макета. Создайте кнопку с идентификатором call_button и текстовое поле callee_id. Перейдите к (app/src/main/res/layout/activity_main.xml) и замените содержимое файла следующим кодом:

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

Создание шаблонов и привязок для основных событий

После создания макета вы можете добавить в действие привязки и основные шаблоны. Действие обрабатывает запрос разрешений среды выполнения, создание агента вызова и размещение вызова при нажатии кнопки. Метод onCreate переопределяется для вызова getAllPermissions и createAgent добавления привязок для кнопки вызова. Это событие происходит только один раз при создании действия. Дополнительные сведения onCreateсм. в руководстве по жизненному циклу действий.

Перейдите к файлу MainActivity.java и замените его содержимое следующим кодом:

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

Запрос разрешений во время выполнения

В Android 6.0 и более поздних версий (API уровня 23) или targetSdkVersion версии 23 или более поздней разрешения предоставляются не при установке приложения, а во время выполнения. Для поддержки его getAllPermissions можно реализовать для вызова ActivityCompat.checkSelfPermission и ActivityCompat.requestPermissions для каждого требуемого разрешения.

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

Примечание.

При проектировании приложения учитывайте, когда будут запрошены такие разрешения. Разрешения следует запрашивать по мере необходимости, а не заранее. Дополнительные сведения см. в руководстве по разрешениям Android.

Объектная модель

Следующие классы и интерфейсы реализуют некоторые основные функции пакета SDK Служб коммуникации Azure для вызовов.

Имя Описание
CallClient Это CallClient основная точка входа в пакет SDK для вызова.
CallAgent Используется CallAgent для запуска вызовов и управления ими.
CommunicationTokenCredential Используется CommunicationTokenCredential в качестве учетных данных маркера для создания экземпляра CallAgent.
CommunicationIdentifier Используется CommunicationIdentifier в качестве другого типа участника, который может быть частью вызова.

Создание агента на основе маркера доступа пользователя

С помощью маркера пользователя можно создать экземпляр агента вызова с проверкой подлинности. Как правило, этот маркер создается из службы с проверкой подлинности, конкретной для приложения. Дополнительные сведения о маркерах доступа пользователей см. в руководстве по маркерам доступа пользователей.

Для целей этого краткого руководства замените <User_Access_Token> маркером доступа пользователя, который был создан для вашего ресурса Службы коммуникации 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();
    }
}

Выполнение кода

Теперь приложение можно запустить с помощью кнопки "Запустить приложение" на панели инструментов.

Инструкции по настройке вызова вручную:

  1. Запустите приложение с помощью Android Studio.
  2. Введите идентификатор объекта очереди вызовов (с префиксом) и нажмите кнопку "Пуск вызова". Приложение запустит исходящий вызов в очередь вызовов с заданным идентификатором объекта.
  3. Вызов подключен к очереди вызовов.
  4. Пользователь служб коммуникации направляется через очередь вызовов на основе его конфигурации.

В этом кратком руководстве вы узнаете, как начать звонок из Службы коммуникации Azure пользователя в очередь вызовов Teams. Вы собираетесь достичь этого с помощью следующих действий:

  1. Включите федерацию ресурсов Службы коммуникации Azure с клиентом Teams.
  2. Выберите или создайте очередь звонков Teams с помощью Центра администрирования Teams.
  3. Получение адреса электронной почты очереди звонков с помощью Центра администрирования Teams.
  4. Получение идентификатора объекта очереди вызовов с помощью API Graph.
  5. Запустите вызов с помощью пакета SDK для вызовов Службы коммуникации Azure.

Если вы хотите сразу перейти к завершающему этапу, можно скачать это краткое руководство в качестве примера с портала GitHub.

Включение взаимодействия в клиенте Teams

Пользователь Microsoft Entra с ролью администратора Teams может запустить командлет PowerShell с модулем MicrosoftTeams, чтобы включить ресурс Служб коммуникации в клиенте.

1. Подготовка модуля Microsoft Teams

Сначала откройте PowerShell и проверьте существование модуля Teams с помощью следующей команды:

Get-module *teams* 

Если модуль не отображается MicrosoftTeams , сначала установите его. Чтобы установить модуль, необходимо запустить PowerShell от имени администратора. Затем выполните следующую команду.

	Install-Module -Name MicrosoftTeams

Вы будете проинформированы о модулях, которые будут установлены, которые можно подтвердить с помощью Y или A ответа. Если модуль установлен, но устарел, можно выполнить следующую команду, чтобы обновить модуль:

	Update-Module MicrosoftTeams

2. Подключение к модулю Microsoft Teams

После установки и готовности модуля можно подключиться к модулю MicrosoftTeams с помощью следующей команды. Вам будет предложено выполнить вход в интерактивное окно. Учетная запись пользователя, которую вы собираетесь использовать, должны иметь разрешения администратора Teams. В противном случае вы можете получить access denied ответ в следующих шагах.

Connect-MicrosoftTeams

3. Включение конфигурации клиента

Взаимодействие с ресурсами Служб коммуникации управляется с помощью конфигурации клиента и назначенной политики. Клиент Teams имеет одну конфигурацию клиента, а пользователи Teams назначили глобальную политику или пользовательскую политику. Дополнительные сведения см. в разделе "Назначение политик" в Teams.

После успешного входа можно запустить командлет Set-CsTeamsAcsFederationConfiguration , чтобы включить ресурс Служб коммуникации в клиенте. Замените текст IMMUTABLE_RESOURCE_ID неизменяемым идентификатором ресурса в ресурсе связи. Дополнительные сведения см . здесь.

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

4. Включение политики клиента

Каждому пользователю Teams назначено значение, определяющее External Access Policy , могут ли пользователи Служб коммуникации вызывать этого пользователя Teams. Используйте командлет Set-CsExternalAccessPolicy, чтобы убедиться, что политика, назначенная пользователю Teams, имеет значение EnableAcsFederationAccess$true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Создание или выбор очереди вызовов Teams

Очередь звонков Teams — это функция в Microsoft Teams, которая эффективно распределяет входящие вызовы среди группы назначенных пользователей или агентов. Это полезно для сценариев поддержки клиентов или центра обработки вызовов. Вызовы помещаются в очередь и назначаются следующему доступному агенту на основе предопределенного метода маршрутизации. Агенты получают уведомления и могут обрабатывать вызовы с помощью элементов управления вызовами Teams. Эта функция предоставляет отчеты и аналитику для отслеживания производительности. Это упрощает обработку вызовов, обеспечивает согласованный интерфейс клиента и оптимизирует производительность агента. Вы можете выбрать существующую или создать новую очередь звонков с помощью Центра администрирования Teams.

Дополнительные сведения о создании очереди вызовов с помощью Центра администрирования Teams см. здесь.

Поиск идентификатора объекта для очереди вызовов

После создания очереди вызовов необходимо найти сопоставленный идентификатор объекта, чтобы использовать его позже для вызовов. Идентификатор объекта подключен к учетной записи ресурсов, подключенной к очереди вызовов, откройте вкладку "Учетные записи ресурсов" в администраторе Teams и найдите электронную почту. Снимок экрана: учетные записи ресурсов на портале администрирования Teams. Все необходимые сведения для учетной записи ресурсов можно найти в обозревателе Microsoft Graph с помощью этого сообщения электронной почты в поиске.

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

В результатах мы сможем найти поле "ID"

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

Чтобы использовать в вызывающем приложении, необходимо добавить префикс в этот идентификатор. В настоящее время поддерживаются следующие компоненты:

  • Очередь вызовов общедоступного облака: 28:orgid:<id>
  • Очередь облачных вызовов для государственных организаций: 28:gcch:<id>

Необходимые компоненты

  • Получите учетную запись Azure с активной подпиской. Создайте учетную запись бесплатно .

  • компьютер Mac с Xcode, а также действительный сертификат разработчика, установленный в цепочку ключей;

  • Развернутый ресурс Служб коммуникации. Создайте ресурс Служб коммуникации. Для этого краткого руководства необходимо записать строка подключения.

  • Маркер доступа пользователя для Службы коммуникации Azure. Вы также можете использовать Azure CLI и выполнить команду с строка подключения для создания пользователя и маркера доступа.

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

    Дополнительные сведения см. в статье "Создание маркеров доступа и управление ими" с помощью Azure CLI.

  • Минимальная поддержка вызывающих приложений Teams: 2.15.0

Установка

Создание проекта Xcode

В Xcode создайте новый проект iOS и выберите шаблон App (Приложение). В этом руководстве используется платформа SwiftUI, поэтому для параметра Language (Язык) нужно задать значение Swift, а для параметра User Interface (Пользовательский интерфейс) — значение SwiftUI. В рамках этого краткого руководства вы не будете создавать тесты. Вы можете снять флажок Include Tests (Включить тесты).

Снимок экрана с окном New Project (Новый проект) в Xcode.

Установка пакета и его зависимостей с помощью CocoaPods

  1. Чтобы создать Podfile для приложения, откройте терминал и перейдите в папку проекта и выполните следующую команду:

    pod init

  2. Добавьте следующий код в Podfile и сохраните (убедитесь, что "target" соответствует имени проекта):

    platform :ios, '13.0'
    use_frameworks!
    
    target 'AzureCommunicationCallingSample' do
      pod 'AzureCommunicationCalling', '~> 2.15.0'
    end
    
  3. Запустите pod install.

  4. Откройте файл .xcworkspace с помощью Xcode.

Запрос доступа к микрофону

Чтобы получить доступ к микрофону устройства, вам необходимо указать ключ NSMicrophoneUsageDescription в списке свойств сведений приложения. Для параметра string , связанного с ним, включенного в диалоговое окно, система использует для запроса доступа от пользователя.

В дереве проекта щелкните правой кнопкой мыши запись Info.plist и выберите Open As>Source Code (Открыть как > Исходный код). Добавьте в раздел верхнего уровня <dict> следующие строки, а затем сохраните файл.

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

Настройка платформы приложения

Откройте файл ContentView.swift проекта и добавьте объявление import в начало файла, чтобы импортировать AzureCommunicationCalling library. Кроме того, импортируйте AVFoundationэтот код для запроса на разрешение звука в коде.

import AzureCommunicationCalling
import AVFoundation

Замените реализацию структуры ContentView простыми элементами управления пользовательского интерфейса, которые позволяют начать и завершить вызов. Мы присоединяем бизнес-логику к этим элементам управления в этом кратком руководстве.

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

Объектная модель

Следующие классы и интерфейсы реализуют некоторые основные функции пакета SDK Служб коммуникации Azure для вызовов.

Имя Описание
CallClient Это CallClient основная точка входа в пакет SDK для вызова.
CallAgent Используется CallAgent для запуска вызовов и управления ими.
CommunicationTokenCredential Используется CommunicationTokenCredential в качестве учетных данных маркера для создания экземпляра CallAgent.
CommunicationUserIdentifier Используется CommunicationUserIdentifier для представления удостоверения пользователя, который может быть одним из следующих параметров: CommunicationUserIdentifierPhoneNumberIdentifierCallingApplication.

аутентификация клиента;

Инициализировать CallAgent экземпляр с помощью маркера доступа пользователей, который позволяет нам совершать и принимать звонки.

В следующем коде необходимо заменить <USER ACCESS TOKEN> допустимым маркером доступа пользователя для ресурса. Если у вас еще нет доступного маркера, см. документацию по маркеру доступа пользователя.

Добавьте следующий код в обратный вызов onAppear в 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.")
    }
}

Инициирование вызова

Метод startCall задается в качестве действия, выполняемого при нажатии кнопки "Пуск вызова ". Измените реализацию, чтобы вызов начинался с помощью 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")
                }
            }
        }
    }
}

Вы также можете использовать свойства для StartCallOptions задания начальных параметров вызова (т. е. позволяет запускать звонок с микрофоном, отключенным).

Завершение вызова

Реализуйте метод endCall, чтобы завершать текущий вызов при нажатии кнопки Завершить вызов.

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

Выполнение кода

Вы можете создать и запустить приложение в симуляторе iOS, выбрав "Запуск продукта>" или с помощью сочетания клавиш ('-R).

Примечание.

При первом вызове в системе отобразится запрос на получение доступа к микрофону. В приложении в рабочей среде используйте API AVAudioSession для проверки состояния разрешения и корректно обновите поведение приложения, если разрешение не предоставлено.

Инструкции по настройке вызова вручную:

  1. Запуск приложения с помощью Xcode
  2. Введите идентификатор объекта очереди вызовов (с префиксом) и нажмите кнопку "Пуск вызова". Приложение запустит исходящий вызов в очередь вызовов с заданным идентификатором объекта.
  3. Вызов подключен к очереди вызовов.
  4. Пользователь служб коммуникации направляется через очередь вызовов на основе его конфигурации.

В этом кратком руководстве вы узнаете, как начать звонок из Службы коммуникации Azure пользователя в очередь вызовов Teams. Вы собираетесь достичь этого с помощью следующих действий:

  1. Включите федерацию ресурсов Службы коммуникации Azure с клиентом Teams.
  2. Выберите или создайте очередь звонков Teams с помощью Центра администрирования Teams.
  3. Получение адреса электронной почты очереди звонков с помощью Центра администрирования Teams.
  4. Получение идентификатора объекта очереди вызовов с помощью API Graph.
  5. Запустите вызов с помощью пакета SDK для вызовов Службы коммуникации Azure.

Если вы хотите сразу перейти к завершающему этапу, можно скачать это краткое руководство в качестве примера с портала GitHub.

Включение взаимодействия в клиенте Teams

Пользователь Microsoft Entra с ролью администратора Teams может запустить командлет PowerShell с модулем MicrosoftTeams, чтобы включить ресурс Служб коммуникации в клиенте.

1. Подготовка модуля Microsoft Teams

Сначала откройте PowerShell и проверьте существование модуля Teams с помощью следующей команды:

Get-module *teams* 

Если модуль не отображается MicrosoftTeams , сначала установите его. Чтобы установить модуль, необходимо запустить PowerShell от имени администратора. Затем выполните следующую команду.

	Install-Module -Name MicrosoftTeams

Вы будете проинформированы о модулях, которые будут установлены, которые можно подтвердить с помощью Y или A ответа. Если модуль установлен, но устарел, можно выполнить следующую команду, чтобы обновить модуль:

	Update-Module MicrosoftTeams

2. Подключение к модулю Microsoft Teams

После установки и готовности модуля можно подключиться к модулю MicrosoftTeams с помощью следующей команды. Вам будет предложено выполнить вход в интерактивное окно. Учетная запись пользователя, которую вы собираетесь использовать, должны иметь разрешения администратора Teams. В противном случае вы можете получить access denied ответ в следующих шагах.

Connect-MicrosoftTeams

3. Включение конфигурации клиента

Взаимодействие с ресурсами Служб коммуникации управляется с помощью конфигурации клиента и назначенной политики. Клиент Teams имеет одну конфигурацию клиента, а пользователи Teams назначили глобальную политику или пользовательскую политику. Дополнительные сведения см. в разделе "Назначение политик" в Teams.

После успешного входа можно запустить командлет Set-CsTeamsAcsFederationConfiguration , чтобы включить ресурс Служб коммуникации в клиенте. Замените текст IMMUTABLE_RESOURCE_ID неизменяемым идентификатором ресурса в ресурсе связи. Дополнительные сведения см . здесь.

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

4. Включение политики клиента

Каждому пользователю Teams назначено значение, определяющее External Access Policy , могут ли пользователи Служб коммуникации вызывать этого пользователя Teams. Используйте командлет Set-CsExternalAccessPolicy, чтобы убедиться, что политика, назначенная пользователю Teams, имеет значение EnableAcsFederationAccess$true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Создание или выбор очереди вызовов Teams

Очередь звонков Teams — это функция в Microsoft Teams, которая эффективно распределяет входящие вызовы среди группы назначенных пользователей или агентов. Это полезно для сценариев поддержки клиентов или центра обработки вызовов. Вызовы помещаются в очередь и назначаются следующему доступному агенту на основе предопределенного метода маршрутизации. Агенты получают уведомления и могут обрабатывать вызовы с помощью элементов управления вызовами Teams. Эта функция предоставляет отчеты и аналитику для отслеживания производительности. Это упрощает обработку вызовов, обеспечивает согласованный интерфейс клиента и оптимизирует производительность агента. Вы можете выбрать существующую или создать новую очередь звонков с помощью Центра администрирования Teams.

Дополнительные сведения о создании очереди вызовов с помощью Центра администрирования Teams см. здесь.

Поиск идентификатора объекта для очереди вызовов

После создания очереди вызовов необходимо найти сопоставленный идентификатор объекта, чтобы использовать его позже для вызовов. Идентификатор объекта подключен к учетной записи ресурсов, подключенной к очереди вызовов, откройте вкладку "Учетные записи ресурсов" в администраторе Teams и найдите электронную почту. Снимок экрана: учетные записи ресурсов на портале администрирования Teams. Все необходимые сведения для учетной записи ресурсов можно найти в обозревателе Microsoft Graph с помощью этого сообщения электронной почты в поиске.

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

В результатах мы сможем найти поле "ID"

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

Чтобы использовать в вызывающем приложении, необходимо добавить префикс в этот идентификатор. В настоящее время поддерживаются следующие компоненты:

  • Очередь вызовов общедоступного облака: 28:orgid:<id>
  • Очередь облачных вызовов для государственных организаций: 28:gcch:<id>

Необходимые компоненты

Для работы с данным руководством вам потребуется:

  • Учетная запись Azure с активной подпиской. Создайте учетную запись бесплатно .

  • Установите Visual Studio 2022 с рабочей нагрузкой разработки универсальная платформа Windows.

  • Развернутый ресурс Служб коммуникации. Создайте ресурс Служб коммуникации. Для этого краткого руководства необходимо записать строка подключения.

  • Маркер доступа пользователя для Службы коммуникации Azure. Вы также можете использовать Azure CLI и выполнить команду с строка подключения для создания пользователя и маркера доступа.

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

    Дополнительные сведения см. в статье "Создание маркеров доступа и управление ими" с помощью Azure CLI.

  • Минимальная поддержка вызывающих приложений Teams: 1.11.0

Установка

Создание проекта

Создайте в Visual Studio новый проект с помощью шаблона Пустое приложение (универсальное приложение Windows), чтобы настроить одностраничное приложение универсальной платформы Windows (UWP).

Снимок экрана: окно нового проекта UWP в Visual Studio.

Установка пакета

Выберите проект правой кнопкой мыши и перейдите к Manage Nuget Packages установке Azure.Communication.Calling.WindowsClient версии 1.4.0 или более поздней версии. Убедитесь, что Include Prerelease вы хотите просмотреть версии общедоступной предварительной версии.

Запрос на доступ

Перейдите Package.appxmanifest и выберите Capabilities. Проверьте Internet (Client) и Internet (Client & Server) получите входящий и исходящий доступ к Интернету. Проверьте Microphone доступ к звуковому каналу микрофона и Webcam чтобы получить доступ к видео-каналу камеры.

Снимок экрана, показывающий запрос доступа к Интернету и микрофону в Visual Studio

Настройка платформы приложения

Необходимо настроить базовую структуру для подключения нашей логики. Чтобы разместить исходящий вызов, необходимо TextBox указать идентификатор пользователя вызываемого абонента. Будут также необходимы кнопки Start/Join call и Hang up. BackgroundBlur Флажки Mute также включены в этот пример, чтобы продемонстрировать функции переключения состояний звука и эффектов видео.

Откройте MainPage.xaml проекта и добавьте узел Grid в 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>

Откройте файл MainPage.xaml.cs и замените его содержимое следующей реализацией.

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

Объектная модель

В следующей таблице перечислены классы и интерфейсы, которые обрабатывают некоторые основные функции пакета SDK для вызовов Службы коммуникации Azure:

Имя Описание
CallClient Это CallClient основная точка входа в пакет SDK для вызова.
CallAgent Используется CallAgent для запуска вызовов и управления ими.
CommunicationCall Используется CommunicationCall для управления текущим вызовом.
CallTokenCredential Используется CallTokenCredential в качестве учетных данных маркера для создания экземпляра CallAgent.
CallIdentifier Используется CallIdentifier для представления удостоверения пользователя, который может быть одним из следующих вариантов: UserCallIdentifierи PhoneNumberCallIdentifier т. д.

аутентификация клиента;

Инициализировать CallAgent экземпляр с помощью маркера доступа пользователя, который позволяет выполнять и принимать вызовы, а также при необходимости получать экземпляр DeviceManager для запроса конфигураций клиентских устройств.

В коде замените <AUTHENTICATION_TOKEN> маркер доступа пользователем. Если у вас еще нет доступного маркера, см. документацию по маркеру доступа пользователя.

Добавьте InitCallAgentAndDeviceManagerAsync функцию, которая загружает пакет SDK. Этот вспомогательный элемент можно настроить в соответствии с требованиями приложения.

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

Запуск вызова

StartCallOptions После получения CallAgent объекта можно использовать для запуска вызова Службы коммуникации 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;
        }

Завершение вызова

Текущий вызов завершается при нажатии кнопки Hang up. Добавьте реализацию в HangupButton_Click для завершения вызова и остановите предварительный просмотр и видеопотоки.

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

Переключатель выключения или отмены звука

Отключение исходящего звука при Mute нажатии кнопки. Добавьте реализацию в MuteLocal_Click для отключения вызова.

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

Прием входящего вызова

IncomingCallReceived Приемник событий настраивается в вспомогательном средстве InitCallAgentAndDeviceManagerAsyncначальной загрузки пакета SDK.

    this.callAgent.IncomingCallReceived += OnIncomingCallAsync;

Приложение имеет возможность настроить прием входящих вызовов, таких как виды видео и аудиопотока.

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

            var acceptCallOptions = new AcceptCallOptions() { };

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

Мониторинг и реагирование на событие изменения состояния вызова

StateChanged событие на CommunicationCall объекте запускается при выполнении транзакций вызова из одного состояния в другое. Приложение предоставляет возможности отражения изменений состояния пользовательского интерфейса или вставки бизнес-логики.

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

Кнопка вызова

Если значение Callee ID не равно null или пусто, можно запустить вызов.

Состояние вызова должно быть изменено с помощью 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;
        }
    
        
    }

Выполнение кода

Вы можете выполнить сборку и запустить код в Visual Studio. Для платформ решений мы поддерживаем ARM64и x64x86.

Инструкции по настройке вызова вручную:

  1. Запустите приложение с помощью Visual Studio.
  2. Введите идентификатор объекта очереди вызовов (с префиксом) и нажмите кнопку "Пуск вызова". Приложение запустит исходящий вызов в очередь вызовов с заданным идентификатором объекта.
  3. Вызов подключен к очереди вызовов.
  4. Пользователь служб коммуникации направляется через очередь вызовов на основе его конфигурации.

Очистка ресурсов

Если вы хотите отменить и удалить подписку на Службы коммуникации, можно удалить ресурс или группу ресурсов. При удалении группы ресурсов также удаляются все связанные с ней ресурсы. См. сведения об очистке ресурсов.

Следующие шаги

Дополнительные сведения см. в следующих статьях: