Краткое руководство. Добавление видеозвонка 1:1 в приложение в качестве пользователя Teams
Начните работу с Службы коммуникации Azure с помощью пакета SDK для звонков служб коммуникации, чтобы добавить голосовой и видеозвонок 1:1 в приложение. Вы узнаете, как начать и ответить на вызов с помощью пакета SDK для вызовов Службы коммуникации Azure для JavaScript.
Пример кода
Если вы хотите сразу перейти к завершающему этапу, можно скачать это краткое руководство в качестве примера с портала GitHub.
Необходимые компоненты
- Получите учетную запись Azure с активной подпиской. Создайте учетную запись бесплатно .
- Вам нужно иметь Node.js 18. Установщик msi можно использовать для его установки.
- Создайте активный ресурс Служб коммуникации. Создайте ресурс Служб коммуникации.
- Создайте маркер доступа пользователя для создания экземпляра клиента вызова. Узнайте, как создать маркер доступа пользователя и обеспечить управление им.
- Получите идентификатор потока Teams для операций вызова с помощью обозревателя Graph. Дополнительные сведения о создании идентификатора потока чата.
Установка
Создание нового приложения Node.js
Откройте терминал или командное окно, создайте новый каталог для приложения и перейдите к каталогу.
mkdir calling-quickstart && cd calling-quickstart
Воспользуйтесь командой npm init -y
, чтобы создать файл package.json с параметрами по умолчанию.
npm init -y
Установка пакета
Используйте команду npm install
, чтобы установить пакет SDK Служб коммуникации Azure для реализации вызовов на JavaScript.
Внимание
В этом кратком руководстве используется последняя версия пакета SDK для вызовов Службы коммуникации Azure.
npm install @azure/communication-common --save
npm install @azure/communication-calling --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 - Teams Calling Web Application</title>
</head>
<body>
<h4>Azure Communication Services - Teams Calling Web Application</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">Login</button>
<br>
<br>
<input id="callee-teams-user-id"
type="text"
placeholder="Microsoft Teams callee's id (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)"
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 для вызовов. |
AzureCommunicationTokenCredential |
Реализует интерфейс CommunicationTokenCredential , который используется для создания экземпляра teamsCallAgent . |
TeamsCallAgent |
Используется для запуска вызовов Teams и управления ими. |
DeviceManager |
Используется для управления устройствами мультимедиа. |
TeamsCall |
Используется для представления вызова Teams |
LocalVideoStream |
Используется для создания локального видеопотока для устройства камеры в локальной системе. |
RemoteParticipant |
Используется для представления удаленного участника в вызове |
RemoteVideoStream |
Используется для представления удаленного видеопотока от удаленного участника. |
Создайте файл в корневом каталоге проекта, который будет index.js
содержать логику приложения для этого краткого руководства. Добавьте следующий код в index.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 teamsCallAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let calleeTeamsUserId = document.getElementById('callee-teams-user-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 TeamsCallAgent instance with a CommunicationUserCredential via created CallClient. TeamsCallAgent 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());
teamsCallAgent = await callClient.createTeamsCallAgent(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.
teamsCallAgent.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 user
* 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 = teamsCallAgent.startCall({ microsoftTeamsUserId: calleeTeamsUserId.value.trim() }, { 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 `TeamsCallAgent.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: './index.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`
Откройте браузер и перейдите на две вкладки. http://localhost:8080/. Вкладки должны показать аналогичный результат, как на следующем рисунке:
На первой вкладке введите допустимый маркер доступа пользователя. На второй вкладке введите другой допустимый маркер доступа пользователя. Обратитесь к документации по маркерам доступа пользователя, если у вас еще нет маркеров доступа, доступных для использования. На обеих вкладках нажмите кнопки "Инициализация агента вызова". Вкладки должны показать аналогичный результат, как на следующем рисунке:
На первой вкладке введите удостоверение пользователя Службы коммуникации Azure второй вкладки и нажмите кнопку "Начать звонок". Первая вкладка запустит исходящий вызов на второй вкладке, а кнопка "Принять звонок" второй вкладки будет включена:
На второй вкладке нажмите кнопку "Принять звонок". Вызов будет отвечать и подключаться. Вкладки должны показать аналогичный результат, как на следующем рисунке:
Обе вкладки теперь успешно находятся в видеозвонке 1:1. Оба пользователя могут слышать звук друг друга и видеть друг друга видеопоток.
Начните работу с Службы коммуникации Azure с помощью пакета SDK для звонков служб коммуникации, чтобы добавить голосовой и видеозвонок 1:1 в приложение. Вы узнаете, как начать и ответить на вызов с помощью пакета SDK для вызовов Службы коммуникации Azure для Windows.
Пример кода
Если вы хотите сразу перейти к завершающему этапу, можно скачать это краткое руководство в качестве примера с портала GitHub.
Необходимые компоненты
Для работы с данным руководством вам потребуется:
- Учетная запись Azure с активной подпиской. Создайте учетную запись бесплатно .
- Установите Visual Studio 2022 с рабочей нагрузкой разработки универсальная платформа Windows.
- Развернутый ресурс Служб коммуникации. Создайте ресурс Служб коммуникации. Для этого краткого руководства необходимо записать строка подключения.
- Маркер доступа пользователя для Службы коммуникации Azure.
- Получите идентификатор потока Teams для операций вызова с помощью обозревателя Graph. Дополнительные сведения о создании идентификатора потока чата.
Установка
Создание проекта
Создайте в Visual Studio новый проект с помощью шаблона Пустое приложение (универсальное приложение Windows), чтобы настроить одностраничное приложение универсальной платформы Windows (UWP).
Установка пакета
Выберите проект правой кнопкой мыши и перейдите к Manage Nuget Packages
установке Azure.Communication.Calling.WindowsClient
версии 1.2.0-beta.1 или более поздней версии. Убедитесь, что установлен флажок включить предварительную проверку.
Запрос на доступ
Перейдите Package.appxmanifest
и выберите Capabilities
.
Проверьте Internet (Client)
и Internet (Client & Server)
получите входящий и исходящий доступ к Интернету. Проверьте Microphone
доступ к звуковому каналу микрофона и Webcam
чтобы получить доступ к видео-каналу камеры.
Настройка платформы приложения
Необходимо настроить базовую структуру для подключения нашей логики. Чтобы разместить исходящий вызов, необходимо 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">
<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 TeamsCallAgent teamsCallAgent;
private TeamsCommunicationCall teamsCall;
private LocalOutgoingAudioStream micStream;
private LocalOutgoingVideoStream cameraStream;
#region Page initialization
public MainPage()
{
this.InitializeComponent();
// Additional UI customization code goes here
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
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, TeamsIncomingCallReceivedEventArgs args)
{
// Handle incoming call event
}
private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
{
// Handle connected and disconnected state change of a call
}
#endregion
}
}
Объектная модель
В следующей таблице перечислены классы и интерфейсы, которые обрабатывают некоторые основные функции пакета SDK для вызовов Службы коммуникации Azure:
Имя | Описание |
---|---|
CallClient |
Это CallClient основная точка входа в пакет SDK для вызова. |
TeamsCallAgent |
Используется TeamsCallAgent для запуска вызовов и управления ими. |
TeamsCommunicationCall |
Используется TeamsCommunicationCall для управления текущим вызовом. |
CallTokenCredential |
Используется CallTokenCredential в качестве учетных данных маркера для создания экземпляра TeamsCallAgent . |
CallIdentifier |
Используется CallIdentifier для представления удостоверения пользователя, который может быть одним из следующих вариантов: MicrosoftTeamsUserCallIdentifier , UserCallIdentifier и PhoneNumberCallIdentifier т. д. |
аутентификация клиента;
Инициализировать TeamsCallAgent
экземпляр с помощью маркера доступа пользователя, который позволяет выполнять и принимать вызовы, а также при необходимости получать экземпляр DeviceManager для запроса конфигураций клиентских устройств.
В коде замените <AUTHENTICATION_TOKEN>
маркер доступа пользователем. Если у вас еще нет доступного маркера, см. документацию по маркеру доступа пользователя.
Добавьте InitCallAgentAndDeviceManagerAsync
функцию, которая загружает пакет SDK. Этот вспомогательный элемент можно настроить в соответствии с требованиями приложения.
private async Task InitCallAgentAndDeviceManagerAsync()
{
this.callClient = new CallClient(new CallClientOptions() {
Diagnostics = new CallDiagnosticsOptions() {
AppName = "CallingQuickstart",
AppVersion="1.0",
Tags = new[] { "Calling", "CTE", "Windows" }
}
});
// Set up local video stream using the first camera enumerated
var deviceManager = await this.callClient.GetDeviceManagerAsync();
var camera = deviceManager?.Cameras?.FirstOrDefault();
var mic = deviceManager?.Microphones?.FirstOrDefault();
micStream = new LocalOutgoingAudioStream();
var tokenCredential = new CallTokenCredential(authToken, callTokenRefreshOptions);
this.teamsCallAgent = await this.callClient.CreateTeamsCallAgentAsync(tokenCredential);
this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;
}
Инициирование вызова
Добавьте реализацию для CallButton_Click
запуска различных видов вызовов с teamsCallAgent
созданным объектом и перехватчиками RemoteParticipantsUpdated
StateChanged
событий в TeamsCommunicationCall
объекте.
private async void CallButton_Click(object sender, RoutedEventArgs e)
{
var callString = CalleeTextBox.Text.Trim();
teamsCall = await StartCteCallAsync(callString);
if (teamsCall != null)
{
teamsCall.StateChanged += OnStateChangedAsync;
}
}
Завершение вызова
Текущий вызов завершается при нажатии кнопки Hang up
. Добавьте реализацию в HangupButton_Click для завершения вызова и остановите предварительный просмотр и видеопотоки.
private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
var teamsCall = this.teamsCallAgent?.Calls?.FirstOrDefault();
if (teamsCall != null)
{
await teamsCall.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 teamsCall = this.teamsCallAgent?.Calls?.FirstOrDefault();
if (teamsCall != null)
{
if ((bool)muteCheckbox.IsChecked)
{
await teamsCall.MuteOutgoingAudioAsync();
}
else
{
await teamsCall.UnmuteOutgoingAudioAsync();
}
}
// Update the UI to reflect the state
}
}
Запуск вызова
StartTeamsCallOptions
После получения TeamsCallAgent
объекта можно использовать для запуска вызова Teams:
private async Task<TeamsCommunicationCall> StartCteCallAsync(string cteCallee)
{
var options = new StartTeamsCallOptions();
var teamsCall = await this.teamsCallAgent.StartCallAsync( new MicrosoftTeamsUserCallIdentifier(cteCallee), options);
return call;
}
Прием входящего вызова
TeamsIncomingCallReceived
Приемник событий настраивается в вспомогательном средстве InitCallAgentAndDeviceManagerAsync
начальной загрузки пакета SDK.
this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;
Приложение имеет возможность настроить прием входящих вызовов, таких как виды видео и аудиопотока.
private async void OnIncomingCallAsync(object sender, TeamsIncomingCallReceivedEventArgs args)
{
var teamsIncomingCall = args.IncomingCall;
var acceptteamsCallOptions = new AcceptTeamsCallOptions() { };
teamsCall = await teamsIncomingCall.AcceptAsync(acceptteamsCallOptions);
teamsCall.StateChanged += OnStateChangedAsync;
}
Присоединение к вызову Teams
Пользователь также может присоединиться к существующему вызову, передав ссылку
TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
JoinTeamsCallOptions options = new JoinTeamsCallOptions();
TeamsCall call = await teamsCallAgent.JoinAsync(link, options);
Мониторинг и реагирование на событие изменения состояния вызова
StateChanged
событие на TeamsCommunicationCall
объекте запускается при выполнении транзакций вызова из одного состояния в другое. Приложение предоставляет возможности отражения изменений состояния пользовательского интерфейса или вставки бизнес-логики.
private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
{
var teamsCall = sender as TeamsCommunicationCall;
if (teamsCall != null)
{
var state = teamsCall.State;
// Update the UI
switch (state)
{
case CallState.Connected:
{
await teamsCall.StartAudioAsync(micStream);
break;
}
case CallState.Disconnected:
{
teamsCall.StateChanged -= OnStateChangedAsync;
teamsCall.Dispose();
break;
}
default: break;
}
}
}
Выполнение кода
Вы можете выполнить сборку и запустить код в Visual Studio. Для платформ решений мы поддерживаем ARM64
x64
и x86
.
Вы можете выполнить исходящий вызов, указав идентификатор пользователя в текстовом поле и нажав кнопку Start Call/Join
. Вызов 8:echo123
подключает вас к эхо-боту, эта функция отлично подходит для начала работы и проверки работы звуковых устройств.
Начните работу с Службы коммуникации Azure с помощью пакета SDK для звонков служб коммуникации, чтобы добавить голосовой и видеозвонок 1:1 в приложение. Вы узнаете, как начать и ответить на вызов с помощью пакета SDK для вызовов Службы коммуникации Azure для Java.
Пример кода
Если вы хотите сразу перейти к завершающему этапу, можно скачать это краткое руководство в качестве примера с портала GitHub.
Необходимые компоненты
- Учетная запись Azure с активной подпиской. Создайте учетную запись бесплатно .
- Android Studio для создания приложения Android.
- Развернутый ресурс Служб коммуникации. Создайте ресурс Служб коммуникации. Для этого краткого руководства необходимо записать строка подключения.
- Маркер доступа пользователя для Службы коммуникации Azure.
- Получите идентификатор потока Teams для операций вызова с помощью обозревателя Graph. Дополнительные сведения о создании идентификатора потока чата.
Установка
Создание приложения Android с пустым действием
В Android Studio щелкните Start a new Android Studio project (Создать проект Android Studio).
Выберите шаблон проекта Empty Activity (Пустое действие) из раздела Phone and Tablet (Телефон и планшет).
Выберите минимальную версию пакета SDK: "API 26: Android 8.0 (Oreo)" или более позднюю.
Установка пакета
Выберите build.gradle на уровне проекта и добавьте mavenCentral()
в список репозиториев в разделах buildscript
и allprojects
.
buildscript {
repositories {
...
mavenCentral()
...
}
}
allprojects {
repositories {
...
mavenCentral()
...
}
}
Затем добавьте в build.gradle на уровне модуля следующие строки в разделах dependencies и android.
android {
...
packagingOptions {
pickFirst 'META-INF/*'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
...
implementation 'com.azure.android:azure-communication-calling:1.0.0-beta.8'
...
}
Добавление разрешений в манифест приложения
Чтобы запрашивать разрешения, необходимые для вызова, они должны быть объявлены в манифесте приложения (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.ctequickstart">
<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=".MainActivity">
<Button
android:id="@+id/call_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Call"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/callee_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="Callee Id"
android:inputType="textPersonName"
app:layout_constraintBottom_toTopOf="@+id/call_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Создание шаблонов и привязок для основных событий
После создания макета вы можете добавить в действие привязки и основные шаблоны. Действие обрабатывает запрос разрешений среды выполнения, создание агента вызова teams и размещение вызова при нажатии кнопки. Каждый из них рассматривается в собственном разделе. Метод onCreate
переопределяется для вызова getAllPermissions
и createTeamsAgent
добавления привязок для кнопки вызова. Это событие происходит только один раз при создании действия. Дополнительные сведения onCreate
см. в руководстве по жизненному циклу действий.
Перейдите к файлу MainActivity.java и замените его содержимое следующим кодом:
package com.contoso.ctequickstart;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.media.AudioManager;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.azure.android.communication.common.CommunicationUserIdentifier;
import com.azure.android.communication.common.CommunicationTokenCredential;
import com.azure.android.communication.calling.TeamsCallAgent;
import com.azure.android.communication.calling.CallClient;
import com.azure.android.communication.calling.StartTeamsCallOptions;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private TeamsCallAgent teamsCallAgent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getAllPermissions();
createTeamsAgent();
// Bind call button to call `startCall`
Button callButton = findViewById(R.id.call_button);
callButton.setOnClickListener(l -> startCall());
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
}
/**
* Request each required permission if the app doesn't already have it.
*/
private void getAllPermissions() {
// See section on requesting permissions
}
/**
* Create the call agent for placing calls
*/
private void createTeamsAgent() {
// See section on creating the call agent
}
/**
* Place a call to the callee id provided in `callee_id` text input.
*/
private void startCall() {
// See section on starting the call
}
}
Запрос разрешений во время выполнения
В 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() {
String[] requiredPermissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE};
ArrayList<String> permissionsToAskFor = new ArrayList<>();
for (String permission : requiredPermissions) {
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 для вызова. |
TeamsCallAgent |
Используется TeamsCallAgent для запуска вызовов и управления ими. |
TeamsCall |
Используется TeamsCall для представления вызова Teams. |
CommunicationTokenCredential |
Используется CommunicationTokenCredential в качестве учетных данных маркера для создания экземпляра TeamsCallAgent . |
CommunicationIdentifier |
Используется CommunicationIdentifier в качестве другого типа участника, который может быть частью вызова. |
Создание агента на основе маркера доступа пользователя
С помощью маркера пользователя можно создать экземпляр агента вызова с проверкой подлинности. Как правило, этот маркер создается из службы с проверкой подлинности, конкретной для приложения. Дополнительные сведения о маркерах доступа пользователей см. в руководстве по маркерам доступа пользователей.
Для целей этого краткого руководства замените <User_Access_Token>
маркером доступа пользователя, который был создан для вашего ресурса Службы коммуникации Azure.
/**
* Create the teams call agent for placing calls
*/
private void createAgent() {
String userToken = "<User_Access_Token>";
try {
CommunicationTokenCredential credential = new CommunicationTokenCredential(userToken);
teamsCallAgent = new CallClient().createTeamsCallAgent(getApplicationContext(), credential).get();
} catch (Exception ex) {
Toast.makeText(getApplicationContext(), "Failed to create teams call agent.", Toast.LENGTH_SHORT).show();
}
}
Инициирование вызова с помощью агента вызовов
Размещение звонка можно сделать с помощью агента вызова команд и просто требует предоставления списка идентификаторов вызывающих и параметров вызова. В кратком руководстве используются параметры вызова по умолчанию без видео и одного вызываемого идентификатора из текстового ввода.
/**
* Place a call to the callee id provided in `callee_id` text input.
*/
private void startCall() {
EditText calleeIdView = findViewById(R.id.callee_id);
String calleeId = calleeIdView.getText().toString();
StartTeamsCallOptions options = new StartTeamsCallOptions();
teamsCallAgent.startCall(
getApplicationContext(),
new MicrosoftTeamsUserCallIdentifier(calleeId),
options);
}
Ответ на звонок
Прием вызова можно выполнить с помощью агента вызова teams, используя только ссылку на текущий контекст.
public void acceptACall(TeamsIncomingCall teamsIncomingCall){
teamsIncomingCall.accept(this);
}
Присоединение к вызову Teams
Пользователь может присоединиться к существующему вызову, передав ссылку.
/**
* Join a call using a teams meeting link.
*/
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
TeamsCall call = teamsCallAgent.join(this, link);
}
Присоединение к вызову Teams с параметрами
Мы также можем присоединить существующий вызов с предварительно настроенными параметрами, такими как отключение.
/**
* Join a call using a teams meeting link while muted.
*/
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
OutgoingAudioOptions audioOptions = new OutgoingAudioOptions().setMuted(true);
JoinTeamsCallOptions options = new JoinTeamsCallOptions().setAudioOptions(audioOptions);
TeamsCall call = teamsCallAgent.join(this, link, options);
}
Настройка прослушивателя входящих вызовов
Чтобы обнаруживать входящие вызовы и другие действия, не выполненные этим пользователем, необходимо настроить прослушиватели.
private TeamsIncomingCall teamsincomingCall;
teamsCallAgent.addOnIncomingCallListener(this::handleIncomingCall);
private void handleIncomingCall(TeamsIncomingCall incomingCall) {
this.teamsincomingCall = incomingCall;
}
Запуск приложения и вызов эхо-бота
Теперь вы можете запустить приложение с помощью кнопки Run App (Запустить приложение) на панели инструментов или нажав клавиши SHIFT+F10. Убедитесь, что вы можете размещать звонки путем вызова 8:echo123
. Предварительно записанное сообщение воспроизводится, а затем повторите сообщение.
Приступая к работе с Службы коммуникации Azure с помощью пакета SDK для вызова служб коммуникации, чтобы добавить один на один видеозвонок в приложение. Вы узнаете, как начать и ответить на видеозвонок с помощью пакета SDK для вызовов Службы коммуникации Azure для iOS с помощью удостоверения Teams.
Пример кода
Если вы хотите сразу перейти к завершающему этапу, можно скачать это краткое руководство в качестве примера с портала GitHub.
Необходимые компоненты
- Получите учетную запись Azure с активной подпиской. Создайте учетную запись бесплатно .
- компьютер Mac с Xcode, а также действительный сертификат разработчика, установленный в цепочку ключей;
- Создайте активный ресурс Служб коммуникации. Создайте ресурс Служб коммуникации. Для этого краткого руководства необходимо записать строка подключения.
- Маркер доступа пользователя для Службы коммуникации Azure.
- Получите идентификатор потока Teams для операций вызова с помощью обозревателя Graph. Дополнительные сведения о создании идентификатора потока чата
Установка
Создание проекта Xcode
В Xcode создайте новый проект iOS и выберите шаблон Single View App (Приложение с одним представлением). В этом руководстве используется платформа SwiftUI, поэтому для параметра Language (Язык) нужно задать значение Swift, а для параметра User Interface (Пользовательский интерфейс) — значение SwiftUI. В рамках этого краткого руководства вы не будете создавать тесты. Вы можете снять флажок Include Tests (Включить тесты).
Установка CocoaPods
Используйте это руководство для установки CocoaPods на компьютере Mac.
Установка пакета и его зависимостей с помощью CocoaPods
Чтобы создать
Podfile
приложение, откройте терминал и перейдите в папку проекта и запустите pod init.Добавьте следующий код в и сохраните
Podfile
его. Ознакомьтесь с версиями поддержки пакета SDK.
platform :ios, '13.0'
use_frameworks!
target 'VideoCallingQuickstart' do
pod 'AzureCommunicationCalling', '~> 2.10.0'
end
Выполните команду pod install.
Откройте файл
.xcworkspace
с помощью Xcode.
Запрос доступа к микрофону и камере
Чтобы получить доступ к микрофону и камере устройства, необходимо обновить список информационных свойств приложения с помощью NSMicrophoneUsageDescription
и NSCameraUsageDescription
. Вы задаете связанное значение строке, включающей диалоговое окно, которое используется системой для запроса доступа от пользователя.
Щелкните правой кнопкой мыши Info.plist
запись дерева проекта и выберите "Открыть как > исходный код". Добавьте в раздел верхнего уровня <dict>
следующие строки, а затем сохраните файл.
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>
<key>NSCameraUsageDescription</key>
<string>Need camera access for video calling</string>
Настройка платформы приложения
Откройте файл ContentView.swift
проекта и добавьте объявление импорта в начало файла, чтобы импортировать библиотеку AzureCommunicationCalling
и AVFoundation
. AVFoundation используется для записи разрешения аудио из кода.
import AzureCommunicationCalling
import AVFoundation
Объектная модель
Следующие классы и интерфейсы реализуют некоторые основные функции пакета SDK Служб коммуникации Azure для iOS.
Имя | Описание |
---|---|
CallClient |
Это CallClient основная точка входа в пакет SDK для вызова. |
TeamsCallAgent |
Используется TeamsCallAgent для запуска вызовов и управления ими. |
TeamsIncomingCall |
Используется TeamsIncomingCall для принятия или отклонения входящих вызовов команд. |
CommunicationTokenCredential |
Используется CommunicationTokenCredential в качестве учетных данных маркера для создания экземпляра TeamsCallAgent . |
CommunicationIdentifier |
Используется CommunicationIdentifier для представления удостоверения пользователя, который может быть одним из следующих параметров: CommunicationUserIdentifier PhoneNumberIdentifier или CallingApplication . |
Создание агента вызовов Teams
Замените реализацию ContentView struct
простыми элементами управления пользовательского интерфейса, которые позволяют пользователю инициировать и завершить вызов. Мы добавим бизнес-логику в эти элементы управления в этом кратком руководстве.
struct ContentView: View {
@State var callee: String = ""
@State var callClient: CallClient?
@State var teamsCallAgent: TeamsCallAgent?
@State var teamsCall: TeamsCall?
@State var deviceManager: DeviceManager?
@State var localVideoStream:[LocalVideoStream]?
@State var teamsIncomingCall: TeamsIncomingCall?
@State var sendingVideo:Bool = false
@State var errorMessage:String = "Unknown"
@State var remoteVideoStreamData:[Int32:RemoteVideoStreamData] = [:]
@State var previewRenderer:VideoStreamRenderer? = nil
@State var previewView:RendererView? = nil
@State var remoteRenderer:VideoStreamRenderer? = nil
@State var remoteViews:[RendererView] = []
@State var remoteParticipant: RemoteParticipant?
@State var remoteVideoSize:String = "Unknown"
@State var isIncomingCall:Bool = false
@State var callObserver:CallObserver?
@State var remoteParticipantObserver:RemoteParticipantObserver?
var body: some View {
NavigationView {
ZStack{
Form {
Section {
TextField("Who would you like to call?", text: $callee)
Button(action: startCall) {
Text("Start Teams Call")
}.disabled(teamsCallAgent == nil)
Button(action: endCall) {
Text("End Teams Call")
}.disabled(teamsCall == nil)
Button(action: toggleLocalVideo) {
HStack {
Text(sendingVideo ? "Turn Off Video" : "Turn On Video")
}
}
}
}
// Show incoming call banner
if (isIncomingCall) {
HStack() {
VStack {
Text("Incoming call")
.padding(10)
.frame(maxWidth: .infinity, alignment: .topLeading)
}
Button(action: answerIncomingCall) {
HStack {
Text("Answer")
}
.frame(width:80)
.padding(.vertical, 10)
.background(Color(.green))
}
Button(action: declineIncomingCall) {
HStack {
Text("Decline")
}
.frame(width:80)
.padding(.vertical, 10)
.background(Color(.red))
}
}
.frame(maxWidth: .infinity, alignment: .topLeading)
.padding(10)
.background(Color.gray)
}
ZStack{
VStack{
ForEach(remoteViews, id:\.self) { renderer in
ZStack{
VStack{
RemoteVideoView(view: renderer)
.frame(width: .infinity, height: .infinity)
.background(Color(.lightGray))
}
}
Button(action: endCall) {
Text("End Call")
}.disabled(teamsCall == nil)
Button(action: toggleLocalVideo) {
HStack {
Text(sendingVideo ? "Turn Off Video" : "Turn On Video")
}
}
}
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
VStack{
if(sendingVideo)
{
VStack{
PreviewVideoStream(view: previewView!)
.frame(width: 135, height: 240)
.background(Color(.lightGray))
}
}
}.frame(maxWidth:.infinity, maxHeight:.infinity,alignment: .bottomTrailing)
}
}
.navigationBarTitle("Video Calling Quickstart")
}.onAppear{
// Authenticate the client
// Initialize the TeamsCallAgent and access Device Manager
// Ask for permissions
}
}
}
//Functions and Observers
struct PreviewVideoStream: UIViewRepresentable {
let view:RendererView
func makeUIView(context: Context) -> UIView {
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
struct RemoteVideoView: UIViewRepresentable {
let view:RendererView
func makeUIView(context: Context) -> UIView {
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
аутентификация клиента;
Для инициализации экземпляра TeamsCallAgent
требуется маркер доступа пользователя, который позволяет выполнять и получать вызовы. Если у вас нет маркера доступа, обратитесь к документации по маркеру доступа пользователя.
Получив маркер, добавьте приведенный ниже код в обратный вызов onAppear
в ContentView.swift
. Необходимо заменить <USER ACCESS TOKEN>
допустимым маркером доступа пользователя для ресурса:
var userCredential: CommunicationTokenCredential?
do {
userCredential = try CommunicationTokenCredential(token: "<USER ACCESS TOKEN>")
} catch {
print("ERROR: It was not possible to create user credential.")
return
}
Инициализация CallAgent Teams и доступ диспетчер устройств
Чтобы создать TeamsCallAgent
экземпляр из CallClient
объекта, используйте callClient.createTeamsCallAgent
метод, который асинхронно возвращает TeamsCallAgent
объект после инициализации. DeviceManager
позволяет получить список локальных устройств, которые можно использовать в вызове для передачи аудио- и видеопотоков. Он также позволяет запросить у пользователя разрешение на доступ к микрофону или камере.
self.callClient = CallClient()
let options = TeamsCallAgentOptions()
// Enable CallKit in the SDK
options.callKitOptions = CallKitOptions(with: createCXProvideConfiguration())
self.callClient?.createTeamsCallAgent(userCredential: userCredential, options: options) { (agent, error) in
if error != nil {
print("ERROR: It was not possible to create a Teams call agent.")
return
} else {
self.teamsCallAgent = agent
print("Teams Call agent successfully created.")
self.teamsCallAgent!.delegate = teamsIncomingCallHandler
self.callClient?.getDeviceManager { (deviceManager, error) in
if (error == nil) {
print("Got device manager instance")
self.deviceManager = deviceManager
} else {
print("Failed to get device manager instance")
}
}
}
}
Запрос разрешений
Чтобы запросить разрешения для аудио и видео, необходимо добавить в обратный вызов onAppear
следующий код.
AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
if granted {
AVCaptureDevice.requestAccess(for: .video) { (videoGranted) in
/* NO OPERATION */
}
}
}
Осуществление исходящего вызова
Метод startCall
задается в качестве действия, выполняемого при нажатии кнопки "Пуск вызова". В этом кратком руководстве исходящие вызовы по умолчанию являются аудиовызовами. Чтобы начать вызов с видео, необходимо задать VideoOptions
его LocalVideoStream
и передать с startCallOptions
ним, чтобы задать начальные параметры для вызова.
let startTeamsCallOptions = StartTeamsCallOptions()
if sendingVideo {
if self.localVideoStream == nil {
self.localVideoStream = [LocalVideoStream]()
}
let videoOptions = VideoOptions(localVideoStreams: localVideoStream!)
startTeamsCallOptions.videoOptions = videoOptions
}
let callees: [CommunicationIdentifier] = [CommunicationUserIdentifier(self.callee)]
self.teamsCallAgent?.startCall(participants: callees, options: startTeamsCallOptions) { (call, error) in
// Handle call object if successful or an error.
}
Подключение к собранию Teams
Этот join
метод позволяет пользователю присоединяться к собранию команд.
let joinTeamsCallOptions = JoinTeamsCallOptions()
if sendingVideo
{
if self.localVideoStream == nil {
self.localVideoStream = [LocalVideoStream]()
}
let videoOptions = VideoOptions(localVideoStreams: localVideoStream!)
joinTeamsCallOptions.videoOptions = videoOptions
}
// Join the Teams meeting muted
if isMuted
{
let outgoingAudioOptions = OutgoingAudioOptions()
outgoingAudioOptions.muted = true
joinTeamsCallOptions.outgoingAudioOptions = outgoingAudioOptions
}
let teamsMeetingLinkLocator = TeamsMeetingLinkLocator(meetingLink: "https://meeting_link")
self.teamsCallAgent?.join(with: teamsMeetingLinkLocator, options: joinTeamsCallOptions) { (call, error) in
// Handle call object if successful or an error.
}
TeamsCallObserver
и RemotePariticipantObserver
используются для управления событиями в процессе вызова и удаленными участниками. Мы устанавливаем наблюдателей setTeamsCallAndObserver
в функции.
func setTeamsCallAndObserver(call:TeamsCall, error:Error?) {
if (error == nil) {
self.teamsCall = call
self.teamsCallObserver = TeamsCallObserver(self)
self.teamsCall!.delegate = self.teamsCallObserver
// Attach a RemoteParticipant observer
self.remoteParticipantObserver = RemoteParticipantObserver(self)
} else {
print("Failed to get teams call object")
}
}
Ответ на входящий вызов
Чтобы ответить на входящий вызов, реализуйте интерфейс TeamsIncomingCallHandler
, чтобы отобразить баннер входящего вызова для ответа или отклонения вызова. Добавьте следующую реализацию в TeamsIncomingCallHandler.swift
.
final class TeamsIncomingCallHandler: NSObject, TeamsCallAgentDelegate, TeamsIncomingCallDelegate {
public var contentView: ContentView?
private var teamsIncomingCall: TeamsIncomingCall?
private static var instance: TeamsIncomingCallHandler?
static func getOrCreateInstance() -> TeamsIncomingCallHandler {
if let c = instance {
return c
}
instance = TeamsIncomingCallHandler()
return instance!
}
private override init() {}
func teamsCallAgent(_ teamsCallAgent: TeamsCallAgent, didRecieveIncomingCall incomingCall: TeamsIncomingCall) {
self.teamsIncomingCall = incomingCall
self.teamsIncomingCall.delegate = self
contentView?.showIncomingCallBanner(self.teamsIncomingCall!)
}
func teamsCallAgent(_ teamsCallAgent: TeamsCallAgent, didUpdateCalls args: TeamsCallsUpdatedEventArgs) {
if let removedCall = args.removedCalls.first {
contentView?.callRemoved(removedCall)
self.teamsIncomingCall = nil
}
}
}
Необходимо создать экземплярTeamsIncomingCallHandler
, добавив следующий код в обратный onAppear
вызов:ContentView.swift
Задайте делегат после TeamsCallAgent
успешного TeamsCallAgent
создания:
self.teamsCallAgent!.delegate = incomingCallHandler
После входящего вызова TeamsIncomingCallHandler
вызывает функцию showIncomingCallBanner
для отображения answer
и decline
кнопки.
func showIncomingCallBanner(_ incomingCall: TeamsIncomingCall) {
self.teamsIncomingCall = incomingCall
}
Действия, присоединенные к answer
и decline
реализованные в виде следующего кода. Чтобы ответить на звонок с видео, необходимо включить локальное видео и задать параметры AcceptCallOptions
с localVideoStream
помощью .
func answerIncomingCall() {
let options = AcceptTeamsCallOptions()
guard let teamsIncomingCall = self.teamsIncomingCall else {
print("No active incoming call")
return
}
guard let deviceManager = deviceManager else {
print("No device manager instance")
return
}
if self.localVideoStreams == nil {
self.localVideoStreams = [LocalVideoStream]()
}
if sendingVideo
{
guard let camera = deviceManager.cameras.first else {
// Handle failure
return
}
self.localVideoStreams?.append( LocalVideoStream(camera: camera))
let videoOptions = VideoOptions(localVideoStreams: localVideosStreams!)
options.videoOptions = videoOptions
}
teamsIncomingCall.accept(options: options) { (call, error) in
// Handle call object if successful or an error.
}
}
func declineIncomingCall() {
self.teamsIncomingCall?.reject { (error) in
// Handle if rejection was successfully or not.
}
}
Оформление подписки на события
Можно реализовать класс TeamsCallObserver
для подписки на коллекцию событий для уведомления при изменении значений во время вызова.
public class TeamsCallObserver: NSObject, TeamsCallDelegate, TeamsIncomingCallDelegate {
private var owner: ContentView
init(_ view:ContentView) {
owner = view
}
public func teamsCall(_ teamsCall: TeamsCall, didChangeState args: PropertyChangedEventArgs) {
if(teamsCall.state == CallState.connected) {
initialCallParticipant()
}
}
// render remote video streams when remote participant changes
public func teamsCall(_ teamsCall: TeamsCall, didUpdateRemoteParticipant args: ParticipantsUpdatedEventArgs) {
for participant in args.addedParticipants {
participant.delegate = self.remoteParticipantObserver
}
}
// Handle remote video streams when the call is connected
public func initialCallParticipant() {
for participant in owner.teamsCall.remoteParticipants {
participant.delegate = self.remoteParticipantObserver
for stream in participant.videoStreams {
renderRemoteStream(stream)
}
owner.remoteParticipant = participant
}
}
}
Выполнение кода
Вы можете создать и запустить приложение в симуляторе iOS, выбрав "Запуск продукта > " или с помощью сочетания клавиш ('-R).
Очистка ресурсов
Если вы хотите отменить и удалить подписку на Службы коммуникации, можно удалить ресурс или группу ресурсов. При удалении группы ресурсов также удаляются все связанные с ней ресурсы. См. сведения об очистке ресурсов.
Следующие шаги
Дополнительные сведения см. в следующих статьях:
- Ознакомьтесь с нашим примером функции веб-вызовов.
- Узнайте больше о возможностях пакета SDK для вызовов
- Узнайте больше о принципе работы функции вызовов.