Краткое руководство. Присоединение вызывающего приложения к автосекретарю 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

После установки и готовности модуля можно подключиться к модулю MicrosftTeams с помощью следующей команды. Вам будет предложено выполнить вход в интерактивное окно. Учетная запись пользователя, которую вы собираетесь использовать, должны иметь разрешения администратора 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 — это система, которая предоставляет автоматическую систему обработки вызовов для входящих вызовов. Он служит виртуальным приемом, что позволяет абонентам автоматически направляться на соответствующего человека или отдела без необходимости оператора человека. Вы можете выбрать существующий или создать автосекретарь с помощью Центра администрирования 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 application objectId identity in format: 'APP_GUID'"
            style="margin-bottom:1em; width: 500px; display: block;"/>
        <button id="start-call-button" type="button" disabled="true">Start Call</button>
        <button id="hangup-call-button" type="button" disabled="true">Hang up Call</button>
        <button id="accept-call-button" type="button" disabled="true">Accept Call</button>
        <button id="start-video-button" type="button" disabled="true">Start Video</button>
        <button id="stop-video-button" type="button" disabled="true">Stop Video</button>
        <br>
        <br>
        <div id="connectedLabel" style="color: #13bb13;" hidden>Call is connected!</div>
        <br>
        <div id="remoteVideoContainer" style="width: 40%;" hidden>Remote participants' video streams:</div>
        <br>
        <div id="localVideoContainer" style="width: 30%;" hidden>Local video stream:</div>
        <!-- points to the bundle generated from client.js -->
        <script src="./main.js"></script>
    </body>
</html>

объектная модель веб-пакета 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 applicationObjectId = document.getElementById('application-object-id');
let initializeCallAgentButton = document.getElementById('initialize-teams-call-agent');
let startCallButton = document.getElementById('start-call-button');
let hangUpCallButton = document.getElementById('hangup-call-button');
let acceptCallButton = document.getElementById('accept-call-button');
let startVideoButton = document.getElementById('start-video-button');
let stopVideoButton = document.getElementById('stop-video-button');
let connectedLabel = document.getElementById('connectedLabel');
let remoteVideoContainer = document.getElementById('remoteVideoContainer');
let localVideoContainer = document.getElementById('localVideoContainer');
/**
 * Create an instance of CallClient. Initialize a CallAgent instance with a AzureCommunicationTokenCredential via created CallClient. CallAgent enables us to make outgoing calls and receive incoming calls. 
 * You can then use the CallClient.getDeviceManager() API instance to get the DeviceManager.
 */
initializeCallAgentButton.onclick = async () => {
    try {
        const callClient = new CallClient(); 
        tokenCredential = new AzureCommunicationTokenCredential(userAccessToken.value.trim());
        callAgent = await callClient.createCallAgent(tokenCredential)
        // Set up a camera device to use.
        deviceManager = await callClient.getDeviceManager();
        await deviceManager.askDevicePermission({ video: true });
        await deviceManager.askDevicePermission({ audio: true });
        // Listen for an incoming call to accept.
        callAgent.on('incomingCall', async (args) => {
            try {
                incomingCall = args.incomingCall;
                acceptCallButton.disabled = false;
                startCallButton.disabled = true;
            } catch (error) {
                console.error(error);
            }
        });
        startCallButton.disabled = false;
        initializeCallAgentButton.disabled = true;
    } catch(error) {
        console.error(error);
    }
}
/**
 * Place a 1:1 outgoing video call to an Teams Auto attendant
 * Add an event listener to initiate a call when the `startCallButton` is selected.
 * Enumerate local cameras using the deviceManager `getCameraList` API.
 * In this quickstart, we're using the first camera in the collection. Once the desired camera is selected, a
 * LocalVideoStream instance will be constructed and passed within `videoOptions` as an item within the
 * localVideoStream array to the call method. When the call connects, your application will be sending a video stream to the other participant. 
 */
startCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = callAgent.startCall([{ teamsAppId: applicationObjectId.value.trim(), cloud:"public" }], { videoOptions: videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
/**
 * Accepting an incoming call with a video
 * Add an event listener to accept a call when the `acceptCallButton` is selected.
 * You can accept incoming calls after subscribing to the `CallAgent.on('incomingCall')` event.
 * You can pass the local video stream to accept the call with the following code.
 */
acceptCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = await incomingCall.accept({ videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a call obj.
// Listen for property changes and collection udpates.
subscribeToCall = (call) => {
    try {
        // Inspect the initial call.id value.
        console.log(`Call Id: ${call.id}`);
        //Subsribe to call's 'idChanged' event for value changes.
        call.on('idChanged', () => {
            console.log(`Call ID changed: ${call.id}`); 
        });
        // Inspect the initial call.state value.
        console.log(`Call state: ${call.state}`);
        // Subscribe to call's 'stateChanged' event for value changes.
        call.on('stateChanged', async () => {
            console.log(`Call state changed: ${call.state}`);
            if(call.state === 'Connected') {
                connectedLabel.hidden = false;
                acceptCallButton.disabled = true;
                startCallButton.disabled = true;
                hangUpCallButton.disabled = false;
                startVideoButton.disabled = false;
                stopVideoButton.disabled = false;
            } else if (call.state === 'Disconnected') {
                connectedLabel.hidden = true;
                startCallButton.disabled = false;
                hangUpCallButton.disabled = true;
                startVideoButton.disabled = true;
                stopVideoButton.disabled = true;
                console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
            }   
        });

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

Добавление кода локального сервера 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 сейчас доступна в предварительной версии.

Предварительные версии API и пакеты SDK предоставляются без соглашения об уровне обслуживания. Рекомендуется не использовать их для рабочих нагрузок. Некоторые функции могут не поддерживаться или могут иметь ограниченные возможности.

Дополнительные сведения см . в дополнительных условиях использования для предварительных версий Microsoft Azure.

В этом кратком руководстве вы узнаете, как начать звонок от Службы коммуникации 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

После установки и готовности модуля можно подключиться к модулю MicrosftTeams с помощью следующей команды. Вам будет предложено выполнить вход в интерактивное окно. Учетная запись пользователя, которую вы собираетесь использовать, должны иметь разрешения администратора 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 — это система, которая предоставляет автоматическую систему обработки вызовов для входящих вызовов. Он служит виртуальным приемом, что позволяет абонентам автоматически направляться на соответствующего человека или отдела без необходимости оператора человека. Вы можете выбрать существующий или создать автосекретарь с помощью Центра администрирования 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.6.0")
    ...
}

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

Чтобы запрашивать разрешения, необходимые для вызова, они должны быть объявлены в манифесте приложения (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;
        }
        ArrayList<CommunicationIdentifier> participants = new ArrayList<>();
        participants.add(new MicrosoftTeamsAppIdentifier(calleeId));
        StartCallOptions options = new StartCallOptions();
        call = agent.startCall(
                getApplicationContext(),
                participants,
                options);
        call.addOnStateChangedListener(p -> setStatus(call.getState().toString()));
    }

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

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

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

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

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

В 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 сейчас доступна в предварительной версии.

Предварительные версии API и пакеты SDK предоставляются без соглашения об уровне обслуживания. Рекомендуется не использовать их для рабочих нагрузок. Некоторые функции могут не поддерживаться или могут иметь ограниченные возможности.

Дополнительные сведения см . в дополнительных условиях использования для предварительных версий Microsoft Azure.

В этом кратком руководстве вы узнаете, как начать звонок от Службы коммуникации 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

После установки и готовности модуля можно подключиться к модулю MicrosftTeams с помощью следующей команды. Вам будет предложено выполнить вход в интерактивное окно. Учетная запись пользователя, которую вы собираетесь использовать, должны иметь разрешения администратора 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 — это система, которая предоставляет автоматическую систему обработки вызовов для входящих вызовов. Он служит виртуальным приемом, что позволяет абонентам автоматически направляться на соответствующего человека или отдела без необходимости оператора человека. Вы можете выбрать существующий или создать автосекретарь с помощью Центра администрирования 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.14.0-beta.1

Установка

Создание проекта 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.14.0-beta.1'
    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 сейчас доступна в предварительной версии.

Предварительные версии API и пакеты SDK предоставляются без соглашения об уровне обслуживания. Рекомендуется не использовать их для рабочих нагрузок. Некоторые функции могут не поддерживаться или могут иметь ограниченные возможности.

Дополнительные сведения см . в дополнительных условиях использования для предварительных версий Microsoft Azure.

В этом кратком руководстве вы узнаете, как начать звонок от Службы коммуникации 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

После установки и готовности модуля можно подключиться к модулю MicrosftTeams с помощью следующей команды. Вам будет предложено выполнить вход в интерактивное окно. Учетная запись пользователя, которую вы собираетесь использовать, должны иметь разрешения администратора 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 — это система, которая предоставляет автоматическую систему обработки вызовов для входящих вызовов. Он служит виртуальным приемом, что позволяет абонентам автоматически направляться на соответствующего человека или отдела без необходимости оператора человека. Вы можете выбрать существующий или создать автосекретарь с помощью Центра администрирования 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.10.0-beta.1

Установка

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

Создайте в 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. Пользователь служб коммуникации направляется через автосекретарь на основе его конфигурации.

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

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

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

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