Начало работы с примером вызывающего героя

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

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

Скачать код

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

Развернуть в Azure

Обзор

Пример содержит клиентское и серверное приложения. Клиентское приложение — это веб-приложение React/Redux, использующее платформу пользовательского интерфейса Fluent от Майкрософт. Это приложение отправляет запросы в серверное приложение ASP.NET Core, которое помогает клиентскому приложению подключаться к Azure.

Вот как выглядит этот пример:

Снимок экрана: целевая страница примера приложения.

При нажатии кнопки "Start a call" (Начать вызов) веб-приложение получает маркер доступа пользователя из серверного приложения. Затем этот маркер используется для подключения клиентского приложения к Службам коммуникации Azure. После получения маркера вам будет предложено указать камеру и микрофон, которые вы хотите использовать. Вы можете отключить или включить устройства с элементами управления переключателями:

Снимок экрана: экран примера приложения перед звонком.

Настройте отображаемое имя и устройства, а затем присоединитесь к сеансу вызова. Вы увидите основной холст вызова, в котором живет основной интерфейс вызова.

Снимок экрана: главный экран примера приложения.

Ниже перечислены компоненты главного экрана вызова.

  • Коллекция мультимедиа: основной этап, на котором показаны участники. Если у участника есть камера и она включена, здесь отображается видеопоток. Для каждого участника создается отдельная плитка, на которой отображаются его отображаемое имя и видеопоток (если он есть).
  • Заголовок. Это место, где элементы управления основными вызовами находятся для переключения параметров и боковой панели участника, включения видео и отключения, совместного использования экрана и выхода из звонка.
  • Боковая панель: здесь отображаются сведения о участниках и параметрах при переключениях с помощью элементов управления в заголовке. Этот компонент можно закрыть, щелкнув "X" в правом верхнем углу. На боковой панели участников отображается список участников и ссылка, чтобы пригласить других пользователей в чат. Боковая панель с параметрами позволяет настроить параметры микрофона и камеры.

Ниже приведены дополнительные сведения о предварительных требованиях и шагах по настройке примера.

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

Действия перед первым запуском примера

  1. Откройте экземпляр PowerShell, Терминал Windows, командную строку или эквивалент и перейдите в каталог, в который вы хотите клонировать пример.

    git clone https://github.com/Azure-Samples/communication-services-web-calling-hero.git
    
  2. Connection String Получите из портал Azure или с помощью Azure CLI.

    az communication list-key --name "<acsResourceName>" --resource-group "<resourceGroup>"
    

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

  3. После получения Connection Stringфайла добавьте строка подключения в файл samples/Server/appsetting.json. Сохраните строку подключения в переменную: ResourceConnectionString.

  4. Endpoint string Получите из портал Azure или с помощью Azure CLI.

    az communication list-key --name "<acsResourceName>" --resource-group "<resourceGroup>"
    

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

  5. После получения Endpoint Stringстроки конечной точки добавьте строку конечной точки в файл samples/Server/appsetting.json . Ввод строки конечной точки в переменной EndpointUrl

Локальный запуск

  1. Установка зависимостей

    npm run setup
    
  2. Запуск вызывающего приложения

    npm run start
    

    Откроется клиентский сервер через порт 3000, обслуживающий файлы веб-сайта, и сервер API через порт 8080, который выполняет такие функции, как токены монетирования для участников вызова.

Устранение неполадок

  • В приложении отображается экран "Неподдерживаемый браузер", но я в поддерживаемом браузере.

    Если ваше приложение обслуживается по имени узла, отличному от localhost, необходимо обслуживать трафик по https и не http.

Публикация в Azure

  1. npm run setup
  2. npm run build
  3. npm run package
  4. Использование расширения Azure и развертывание каталога "Вызов/dist" в службе приложений

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

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

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

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

Дополнительные материалы

  • Примеры: дополнительные примеры см. на нашей странице обзора примеров.
  • Redux — управление состоянием на стороне клиента
  • FluentUI — библиотека пользовательского интерфейса, поддерживаемая корпорацией Майкрософт.
  • React — библиотека для создания пользовательских интерфейсов.
  • ASP.NET Core — платформа для создания веб-приложений.

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

Скачать код

Найдите проект для этого примера на сайте GitHub.

Обзор

Этот пример содержит собственное приложение iOS, которое использует пакеты SDK Служб коммуникации Azure для iOS для создания вызовов с поддержкой голоса и видео. Это приложение использует компонент на стороне сервера для подготовки маркеров доступа, которые затем используются для инициализации пакета SDK Служб коммуникации Azure. Чтобы настроить этот компонент на стороне сервера, вы можете воспользоваться учебником по созданию доверенной службы с Функциями Azure.

Вот как выглядит этот пример:

Снимок экрана: целевая страница примера приложения.

При нажатии кнопки "Пуск нового вызова" приложение iOS предложит ввести отображаемое имя, которое будет использоваться для вызова.

Снимок экрана: экран примера приложения перед звонком.

После нажатия кнопки "Далее" на экране "Пуск вызова" у вас есть возможность поделиться идентификатором группы звонка с помощью листа общего ресурса iOS.

Снимок экрана: экран идентификатора группы общих ресурсов примера приложения.

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

Снимок экрана: экран вызова соединения примера приложения.

После присоединения к вызову вам будет предложено предоставить приложению разрешение на доступ к камере и микрофону, если он еще не авторизован. Помните, что, как и все приложения на основе AVFoundation, истинные функции аудио и видео доступны только на реальном оборудовании.

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

Снимок экрана: главный экран примера приложения.

Ниже перечислены компоненты главного экрана вызова.

  • Коллекция мультимедиа: основной этап, на котором показаны участники. Если у участника есть камера и она включена, здесь отображается видеопоток. Для каждого участника создается отдельная плитка, на которой размещаются его отображаемое имя и видеопоток (если он есть). Коллекция поддерживает несколько участников и автоматически обновляется при добавлении или удалении участников вызова.
  • Панель действий. Это место, где находятся основные элементы управления вызовами. Эти элементы управления позволяют включать и отключать видео и микрофон, предоставлять общий доступ к экрану и покидать сеанс вызова.

Ниже вы найдете дополнительные сведения о предварительных требованиях и шагах по настройке примера.

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

Локальное выполнение примера

Этот пример группового вызова можно запустить локально с помощью XCode. Разработчики могут использовать для тестирования приложения реальное устройство или эмулятор.

Действия перед первым запуском примера

  1. Установите зависимости, выполнив команду pod install.
  2. Откройте AzureCalling.xcworkspace в XCode.
  3. Создайте текстовый файл в корневом каталоге AppSettings.xcconfig и задайте значение:
    communicationTokenFetchUrl = <your authentication endpoint, without the https:// component>
    

Запуск примера

Создайте и запустите пример в XCode, используя целевой объект AzureCalling на выбранном симуляторе или устройстве.

(Необязательно) Защита конечной точки аутентификации

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

При дополнительной конфигурации этот пример поддерживает подключение к защищенной конечной точке идентификатора Microsoft Entra ID (Microsoft Entra ID), чтобы имя входа пользователя требовалось для получения маркера доступа Службы коммуникации Azure. Соответствующие действия описаны ниже.

  1. Включите проверку подлинности Microsoft Entra в приложении.
  2. Перейдите на страницу обзора зарегистрированного приложения в разделе "Регистрация приложений Microsoft Entra". Запишите значения Application (client) ID, Directory (tenant) ID, Application ID URI.

Конфигурация Microsoft Entra на портал Azure.

  1. Создайте файл в корневом AppSettings.xcconfig каталоге, если он еще не присутствует, и добавьте значения:
    communicationTokenFetchUrl = <Application ID URI, without the https:// component>
    aadClientId = <Application (client) ID>
    aadTenantId = <Directory (tenant) ID>
    

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

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

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

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

Дополнительные материалы

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

Скачать код

Найдите проект для этого примера на сайте GitHub.

Обзор

Примером является собственное приложение Android, использующее клиентская библиотека пользовательского интерфейса Android Службы коммуникации Azure для создания интерфейса вызова, который включает как голосовой, так и видеозвонок. Это приложение использует компонент на стороне сервера для подготовки маркеров доступа, которые затем используются для инициализации пакета SDK Служб коммуникации Azure. Чтобы настроить этот компонент на стороне сервера, вы можете воспользоваться учебником по созданию доверенной службы с Функциями Azure.

Вот как выглядит этот пример:

Снимок экрана: целевая страница примера приложения.

При нажатии кнопки "Пуск нового вызова" приложение Android предложит ввести отображаемое имя, которое будет использоваться для вызова.

Снимок экрана: экран примера приложения перед звонком.

После нажатия кнопки "Далее" на странице "Пуск вызова" у вас есть возможность поделиться идентификатором группового вызова.

Снимок экрана: экран идентификатора группового вызова для примера приложения.

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

Снимок экрана: экран вызова соединения примера приложения.

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

Снимок экрана: главный экран примера приложения.

Ниже перечислены компоненты главного экрана вызова.

  • Коллекция мультимедиа: основной этап, на котором показаны участники. Если у участника есть камера и она включена, здесь отображается видеопоток. Для каждого участника создается отдельная плитка, на которой размещаются его отображаемое имя и видеопоток (если он есть). Коллекция поддерживает несколько участников и автоматически обновляется при добавлении или удалении участников вызова.
  • Панель действий. Это место, где находятся основные элементы управления вызовами. Эти элементы управления позволяют включать и отключать видео и микрофон, предоставлять общий доступ к экрану и покидать сеанс вызова.

Ниже вы найдете дополнительные сведения о предварительных требованиях и шагах по настройке примера.

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

Локальное выполнение примера

Этот пример группового вызова можно запустить локально с помощью Android Studio. Разработчики могут использовать для тестирования приложения реальное устройство или эмулятор.

Действия перед первым запуском примера

  1. Запустите Android Studio и выберите Open an Existing Project
  2. Откройте папку AzureCalling в загруженном выпуске для примера.
  3. Разверните ресурс или приложение, чтобы обновить appSettings.properties. Задайте для ключа communicationTokenFetchUrl значение, соответствующее URL-адресу конечной точки проверки подлинности.

Запуск примера

Откройте пример в Android Studio.

(Необязательно) Защита конечной точки аутентификации

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

При дополнительной конфигурации этот пример поддерживает подключение к защищенной конечной точке идентификатора Microsoft Entra ID (Microsoft Entra ID), чтобы имя входа пользователя требовалось для получения маркера Службы коммуникации Azure. Соответствующие действия описаны ниже.

  1. Включите проверку подлинности Microsoft Entra в приложении.

  2. Перейдите на страницу обзора зарегистрированного приложения в разделе "Регистрация приложений Microsoft Entra". Запишите Package name, Signature hash. MSAL Configutaion

Конфигурация Microsoft Entra на портал Azure.

  1. Измените AzureCalling/app/src/main/res/raw/auth_config_single_account.json и задайте для isAADAuthEnabled включения идентификатора Microsoft Entra.

  2. Измените AndroidManifest.xml и задайте android:path для хэша подписи хранилища ключей. (Необязательно. Текущее значение использует хэш из пакета debug.keystore. Если используется другое хранилище ключей, это необходимо обновить.)

    <activity android:name="com.microsoft.identity.client.BrowserTabActivity">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
                 <data
                     android:host="com.azure.samples.communication.calling"
                     android:path="/Signature hash" <!-- do not remove /. The current hash in AndroidManifest.xml is for debug.keystore. -->
                     android:scheme="msauth" />
             </intent-filter>
         </activity>
    
  3. Скопируйте конфигурацию Android MSAL из портала Azure и вставьте в AzureCalling/app/src/main/res/raw/auth_config_single_account.json. Включить "account_mode": "SINGLE"

       {
          "client_id": "",
          "authorization_user_agent": "DEFAULT",
          "redirect_uri": "",
          "account_mode" : "SINGLE",
          "authorities": [
             {
                "type": "AAD",
                "audience": {
                "type": "AzureADMyOrg",
                "tenant_id": ""
                }
             }
          ]
       }
    
  4. Измените AzureCalling/app/src/main/res/raw/auth_config_single_account.json и задайте для ключа communicationTokenFetchUrl значение, соответствующее URL-адресу конечной точки проверки подлинности.

  5. Изменение AzureCalling/app/src/main/res/raw/auth_config_single_account.json и установка значения ключа aadScopes из Azure Active DirectoryExpose an API область

  6. Задайте значение в graphURLAzureCalling/app/assets/appSettings.properties качестве конечной точки API Graph, чтобы получить сведения о пользователе.

  7. Измените AzureCalling/app/src/main/assets/appSettings.properties и задайте значение ключа tenant , чтобы включить автоматическое вход, чтобы пользователь не должен пройти проверку подлинности снова и снова при перезапуске приложения.

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

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

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

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

Дополнительные материалы

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

Из этого краткого руководства вы узнаете, как запустить видеозвонок 1:1 с помощью пакета SDK для вызовов Службы коммуникации Azure для Windows.

Пример кода UWP

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

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

Установка

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

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

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

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

Щелкните проект правой кнопкой мыши и перейдите к Manage Nuget Packages установке Azure.Communication.Calling.WindowsClientверсии 1.2.0-beta.1 или более поздней версии. Убедитесь, что предварительная подготовка включена проверка.

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

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

Добавьте следующий код в Package.appxmanifest, щелкнув правой кнопкой мыши и выбрав пункт "Просмотреть код".

<Extensions>
<Extension Category="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>RtmMvrUap.dll</Path>
<ActivatableClass ActivatableClassId="VideoN.VideoSchemeHandler" ThreadingModel="both" />
</InProcessServer>
</Extension>
</Extensions>

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

Необходимо настроить базовую структуру для подключения нашей логики. Чтобы разместить исходящий вызов, необходимо TextBox указать идентификатор пользователя вызываемого абонента. Будут также необходимы кнопки Start Call и Hang Up. Кроме того, потребуется предварительный просмотр локального видео и отрисовка удаленного видео собеседника. Поэтому нам нужно два элемента для отображения видеопотоков.

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

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

    <Grid x:Name="MainGrid" HorizontalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="200*"/>
            <RowDefinition Height="60*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" x:Name="AppTitleBar" Background="LightSeaGreen">
            <!-- Width of the padding columns is set in LayoutMetricsChanged handler. -->
            <!-- Using padding columns instead of Margin ensures that the background paints the area under the caption control buttons (for transparent buttons). -->
            <TextBlock x:Name="QuickstartTitle" Text="Calling Quickstart sample title bar" Style="{StaticResource CaptionTextBlockStyle}" Padding="4,4,0,0"/>
        </Grid>

        <TextBox Grid.Row="1" x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" TextWrapping="Wrap" VerticalAlignment="Center" />

        <Grid Grid.Row="2" Background="LightGray">
            <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" Margin="10">
                <TextBlock VerticalAlignment="Center">Cameras:</TextBlock>
                <ComboBox x:Name="CameraList" HorizontalAlignment="Left" Grid.Column="0" DisplayMemberPath="Name" SelectionChanged="CameraList_SelectionChanged" Margin="10"/>
            </StackPanel>
            <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"/>
                <CheckBox x:Name="BackgroundBlur" Content="Background blur" Width="142" Margin="10,0,0,0" Click="BackgroundBlur_Click"/>
            </StackPanel>
        </StackPanel>
        <TextBox Grid.Row="4" x:Name="Stats" Text="" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="0,2,0,0" BorderThickness="2" IsReadOnly="True" Foreground="LightSlateGray" />
    </Grid>
</Page>

Откройте файл App.xaml.cs (щелкните правой кнопкой мыши и выберите "Просмотреть код") и добавьте следующую строку в начало.

using CallingQuickstart;

Откройте файл MainPage.xaml.cs (щелкните правой кнопкой мыши и выберите "Просмотреть код") и замените его содержимое следующей реализацией.

using Azure.Communication.Calling.WindowsClient;
using System;
using System.Collections.Generic;
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 = "<Azure Communication Services auth token>";
    
        private CallClient callClient;
        private CallTokenRefreshOptions callTokenRefreshOptions;
        private CallAgent callAgent;
        private CommunicationCall call = null;

        private LocalOutgoingAudioStream micStream;
        private LocalOutgoingVideoStream cameraStream;

        #region Page initialization
        public MainPage()
        {
            this.InitializeComponent();
            
            // Hide default title bar.
            var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
            coreTitleBar.ExtendViewIntoTitleBar = true;

            QuickstartTitle.Text = $"{Package.Current.DisplayName} - Ready";
            Window.Current.SetTitleBar(AppTitleBar);

            CallButton.IsEnabled = true;
            HangupButton.IsEnabled = !CallButton.IsEnabled;
            MuteLocal.IsChecked = MuteLocal.IsEnabled = !CallButton.IsEnabled;

            ApplicationView.PreferredLaunchViewSize = new Windows.Foundation.Size(800, 600);
            ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
        }

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            await InitCallAgentAndDeviceManagerAsync();

            base.OnNavigatedTo(e);
        }
#endregion

        private async Task InitCallAgentAndDeviceManagerAsync()
        {
            // Initialize call agent and Device Manager
        }

        private async void Agent_OnIncomingCallAsync(object sender, IncomingCall incomingCall)
        {
            // Accept an incoming call
        }

        private async void CallButton_Click(object sender, RoutedEventArgs e)
        {
            // Start a call with video
        }

        private async void HangupButton_Click(object sender, RoutedEventArgs e)
        {
            // End the current call
        }

        private async void Call_OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            var call = sender as CommunicationCall;

            if (call != null)
            {
                var state = call.State;

                await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                {
                    QuickstartTitle.Text = $"{Package.Current.DisplayName} - {state.ToString()}";
                    Window.Current.SetTitleBar(AppTitleBar);

                    HangupButton.IsEnabled = state == CallState.Connected || state == CallState.Ringing;
                    CallButton.IsEnabled = !HangupButton.IsEnabled;
                    MuteLocal.IsEnabled = !CallButton.IsEnabled;
                });

                switch (state)
                {
                    case CallState.Connected:
                        {
                            break;
                        }
                    case CallState.Disconnected:
                        {
                            break;
                        }
                    default: break;
                }
            }
        }
        
        private async void CameraList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // Handle camera selection
        }
    }
}

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

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

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

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

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

Для целей этого краткого руководства замените <AUTHENTICATION_TOKEN> маркером доступа пользователя, который был создан для вашего ресурса Службы коммуникации Azure.

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

Добавьте в функцию InitCallAgentAndDeviceManagerAsync следующий код.

this.callClient = new CallClient(new CallClientOptions() {
    Diagnostics = new CallDiagnosticsOptions() { 
        AppName = "CallingQuickstart",
        AppVersion="1.0",
        Tags = new[] { "Calling", "ACS", "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();

CameraList.ItemsSource = deviceManager.Cameras.ToList();

if (camera != null)
{
    CameraList.SelectedIndex = 0;
}

callTokenRefreshOptions = new CallTokenRefreshOptions(false);
callTokenRefreshOptions.TokenRefreshRequested += OnTokenRefreshRequestedAsync;

var tokenCredential = new CallTokenCredential(authToken, callTokenRefreshOptions);

var callAgentOptions = new CallAgentOptions()
{
    DisplayName = "Contoso",
    //https://github.com/lukes/ISO-3166-Countries-with-Regional-Codes/blob/master/all/all.csv
    EmergencyCallOptions = new EmergencyCallOptions() { CountryCode = "840" }
};


try
{
    this.callAgent = await this.callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);
    //await this.callAgent.RegisterForPushNotificationAsync(await this.RegisterWNS());
    this.callAgent.CallsUpdated += OnCallsUpdatedAsync;
    this.callAgent.IncomingCallReceived += OnIncomingCallAsync;

}
catch(Exception ex)
{
    if (ex.HResult == -2147024809)
    {
        // E_INVALIDARG
        // Handle possible invalid token
    }
}

Совершение видеозвонка

Добавьте реализацию в CallButton_Click начало вызова с видео. необходимо перечислить камеры с помощью экземпляра Диспетчера устройств и сформировать LocalOutgoingVideoStream. Нужно указать параметры VideoOptions с аргументом LocalVideoStream и передать их в startCallOptions, чтобы задать начальные параметры звонка. Подключив LocalOutgoingVideoStream к ней MediaElement, можно просмотреть предварительную версию локального видео.

var callString = CalleeTextBox.Text.Trim();

if (!string.IsNullOrEmpty(callString))
{
    if (callString.StartsWith("8:")) // 1:1 Azure Communication Services call
    {
        call = await StartAcsCallAsync(callString);
    }
    else if (callString.StartsWith("+")) // 1:1 phone call
    {
        call = await StartPhoneCallAsync(callString, "+12133947338");
    }
    else if (Guid.TryParse(callString, out Guid groupId))// Join group call by group guid
    {
        call = await JoinGroupCallByIdAsync(groupId);
    }
    else if (Uri.TryCreate(callString, UriKind.Absolute, out Uri teamsMeetinglink)) //Teams meeting link
    {
        call = await JoinTeamsMeetingByLinkAsync(teamsMeetinglink);
    }
}

if (call != null)
{
    call.RemoteParticipantsUpdated += OnRemoteParticipantsUpdatedAsync;
    call.StateChanged += OnStateChangedAsync;
}

Добавьте методы для запуска или присоединения к различным типам звонков (звонок 1:1 Службы коммуникации Azure, телефонный звонок 1:1, Службы коммуникации Azure групповой звонок, присоединение к собранию Teams и т. д.).

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

private async Task<CommunicationCall> StartPhoneCallAsync(string acsCallee, string alternateCallerId)
{
    var options = await GetStartCallOptionsAsynnc();
    options.AlternateCallerId = new PhoneNumberCallIdentifier(alternateCallerId);

    var call = await this.callAgent.StartCallAsync( new [] { new PhoneNumberCallIdentifier(acsCallee) }, options);
    return call;
}

private async Task<CommunicationCall> JoinGroupCallByIdAsync(Guid groupId)
{
    var joinCallOptions = await GetJoinCallOptionsAsync();

    var groupCallLocator = new GroupCallLocator(groupId);
    var call = await this.callAgent.JoinAsync(groupCallLocator, joinCallOptions);
    return call;
}

private async Task<CommunicationCall> JoinTeamsMeetingByLinkAsync(Uri teamsCallLink)
{
    var joinCallOptions = await GetJoinCallOptionsAsync();

    var teamsMeetingLinkLocator = new TeamsMeetingLinkLocator(teamsCallLink.AbsoluteUri);
    var call = await callAgent.JoinAsync(teamsMeetingLinkLocator, joinCallOptions);
    return call;
}

private async Task<StartCallOptions> GetStartCallOptionsAsynnc()
{
    return new StartCallOptions() {
        OutgoingAudioOptions = new OutgoingAudioOptions() { IsOutgoingAudioMuted = true, OutgoingAudioStream = micStream  },
        OutgoingVideoOptions = new OutgoingVideoOptions() { OutgoingVideoStreams = new OutgoingVideoStream[] { cameraStream } }
    };
}

private async Task<JoinCallOptions> GetJoinCallOptionsAsync()
{
    return new JoinCallOptions() {
        OutgoingAudioOptions = new OutgoingAudioOptions() { IsOutgoingAudioMuted = true },
        OutgoingVideoOptions = new OutgoingVideoOptions() { OutgoingVideoStreams = new OutgoingVideoStream[] { cameraStream } }
    };
}

Добавьте код для создания LocalVideoStream в зависимости от выбранной камеры метода CameraList_SelectionChanged .

var selectedCamerea = CameraList.SelectedItem as VideoDeviceDetails;
cameraStream = new LocalOutgoingVideoStream(selectedCamerea);

 var localUri = await cameraStream.StartPreviewAsync();
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
    LocalVideo.Source = MediaSource.CreateFromUri(localUri);
});

if (call != null)
{
    await call?.StartVideoAsync(cameraStream);
}

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

Добавьте реализацию в OnIncomingCallAsync ответ на входящий звонок с видео, передайте егоacceptCallOptionsLocalVideoStream.

var incomingCall = args.IncomingCall;

var acceptCallOptions = new AcceptCallOptions() { 
    IncomingVideoOptions = new IncomingVideoOptions()
    {
        IncomingVideoStreamKind = VideoStreamKind.RemoteIncoming
    } 
};

_ = await incomingCall.AcceptAsync(acceptCallOptions);

Удаленный участник и видеопотоки

Все удаленные участники доступны через коллекцию RemoteParticipants в экземпляре вызова. После подключения вызова можно получить доступ к удаленным участникам вызова и обработать удаленные видеопотоки.


private async void Call_OnVideoStreamsUpdatedAsync(object sender, RemoteVideoStreamsEventArgs args)
{
    foreach (var remoteVideoStream in args.AddedRemoteVideoStreams)
    {
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
        {
            RemoteVideo.Source = await remoteVideoStream.Start();
        });
    }

    foreach (var remoteVideoStream in args.RemovedRemoteVideoStreams)
    {
        remoteVideoStream.Stop();
    }
}

private async void Agent_OnCallsUpdatedAsync(object sender, CallsUpdatedEventArgs args)
{
    var removedParticipants = new List<RemoteParticipant>();
    var addedParticipants = new List<RemoteParticipant>();

    foreach(var call in args.RemovedCalls)
    {
        removedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    foreach (var call in args.AddedCalls)
    {
        addedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    await OnParticipantChangedAsync(removedParticipants, addedParticipants);
}

private async Task OnParticipantChangedAsync(IEnumerable<RemoteParticipant> removedParticipants, IEnumerable<RemoteParticipant> addedParticipants)
{
    foreach (var participant in removedParticipants)
    {
        foreach(var incomingVideoStream in  participant.IncomingVideoStreams)
        {
            var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
            if (remoteVideoStream != null)
            {
                await remoteVideoStream.StopPreviewAsync();
            }
        }
        participant.VideoStreamStateChanged -= OnVideoStreamStateChanged;
    }

    foreach (var participant in addedParticipants)
    {
        participant.VideoStreamStateChanged += OnVideoStreamStateChanged;
    }
}

private void OnVideoStreamStateChanged(object sender, VideoStreamStateChangedEventArgs e)
{
    CallVideoStream callVideoStream = e.CallVideoStream;

    switch (callVideoStream.StreamDirection)
    {
        case StreamDirection.Outgoing:
            OnOutgoingVideoStreamStateChanged(callVideoStream as OutgoingVideoStream);
            break;
        case StreamDirection.Incoming:
            OnIncomingVideoStreamStateChanged(callVideoStream as IncomingVideoStream);
            break;
    }
}

private async void OnIncomingVideoStreamStateChanged(IncomingVideoStream incomingVideoStream)
{
    switch (incomingVideoStream.State)
    {
        case VideoStreamState.Available:
        {
            switch (incomingVideoStream.Kind)
            {
                case VideoStreamKind.RemoteIncoming:
                    var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                    var uri = await remoteVideoStream.StartPreviewAsync();

                    await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                    {
                        RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                    });
                    break;

                case VideoStreamKind.RawIncoming:
                    break;
            }
            break;
        }
        case VideoStreamState.Started:
            break;
        case VideoStreamState.Stopping:
            break;
        case VideoStreamState.Stopped:
            if (incomingVideoStream.Kind == VideoStreamKind.RemoteIncoming)
            {
                var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                await remoteVideoStream.StopPreviewAsync();
            }
            break;
        case VideoStreamState.NotAvailable:
            break;
    }

}

Отрисовка удаленного видео

Каждый удаленный видеопоток необходимо подключить к MediaElement.

private async Task AddVideoStreamsAsync(IReadOnlyList<RemoteVideoStream> remoteVideoStreams)
{
    foreach (var remoteVideoStream in remoteVideoStreams)
    {
        var remoteUri = await remoteVideoStream.Start();

        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            RemoteVideo.Source = remoteUri;
            RemoteVideo.Play();
        });
    }
}

Обновление состояния вызова

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

private async void Call_OnStateChanged(object sender, PropertyChangedEventArgs args)
{
    switch (((Call)sender).State)
    {
        case CallState.Disconnected:
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                LocalVideo.Source = null;
                RemoteVideo.Source = null;
            });
            break;

        case CallState.Connected:
            foreach (var remoteParticipant in call.RemoteParticipants)
            {
                String remoteParticipantMRI = remoteParticipant.Identifier.ToString();
                remoteParticipantDictionary.TryAdd(remoteParticipantMRI, remoteParticipant);
                await AddVideoStreams(remoteParticipant.VideoStreams);
                remoteParticipant.OnVideoStreamsUpdated += Call_OnVideoStreamsUpdated;
            }
            break;

        default:
            break;
    }
}

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

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

var call = this.callAgent?.Calls?.FirstOrDefault();
if (call != null)
{
    try
    {
        await call.HangUpAsync(new HangUpOptions() { ForEveryone = true });
    }
    catch(Exception ex) 
    {
    }
}

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

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

Чтобы совершить исходящий видеозвонок, укажите в текстовом поле ИД пользователя и щелкните кнопку Start Call.

Примечание. Вызов 8:echo123 останавливает видеопоток, так как эхо-бот не поддерживает потоковую передачу видео.

Дополнительные сведения об идентификаторах пользователей см. в этом руководстве.

Пример кода WinUI 3

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

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

Установка

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

В Visual Studio создайте проект с шаблоном "Пустое приложение" (WinUI 3 в классическом приложении) для настройки одностраничного приложения WinUI 3.

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

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

Щелкните проект правой кнопкой мыши и перейдите к Manage Nuget Packages установке Azure.Communication.Calling.WindowsClientверсии 1.0.0 или более поздней версии. Убедитесь, что предварительная подготовка включена проверка.

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

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

Добавьте в ваш app.manifestкод следующий код:

<file name="RtmMvrMf.dll">
    <activatableClass name="VideoN.VideoSchemeHandler" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1" />
</file>

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

Необходимо настроить базовую структуру для подключения нашей логики. Чтобы разместить исходящий вызов, необходимо TextBox указать идентификатор пользователя вызываемого абонента. Будут также необходимы кнопки Start Call и Hang Up. Кроме того, потребуется предварительный просмотр локального видео и отрисовка удаленного видео собеседника. Поэтому нам нужно два элемента для отображения видеопотоков.

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

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

    <Grid x:Name="MainGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="32"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="200*"/>
            <RowDefinition Height="60*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Grid Grid.Row="0" x:Name="AppTitleBar" Background="LightSeaGreen">
            <!-- Width of the padding columns is set in LayoutMetricsChanged handler. -->
            <!-- Using padding columns instead of Margin ensures that the background paints the area under the caption control buttons (for transparent buttons). -->
            <TextBlock x:Name="QuickstartTitle" Text="Calling Quickstart sample title bar" Style="{StaticResource CaptionTextBlockStyle}" Padding="4,4,0,0"/>
        </Grid>

        <TextBox Grid.Row="1" x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" TextWrapping="Wrap" VerticalAlignment="Center" />

        <Grid Grid.Row="2" Background="LightGray">
            <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" Margin="10">
                <TextBlock VerticalAlignment="Center">Cameras:</TextBlock>
                <ComboBox x:Name="CameraList" HorizontalAlignment="Left" Grid.Column="0" DisplayMemberPath="Name" SelectionChanged="CameraList_SelectionChanged" Margin="10"/>
            </StackPanel>
            <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"/>
                <CheckBox x:Name="BackgroundBlur" Content="Background blur" Width="142" Margin="10,0,0,0" Click="BackgroundBlur_Click"/>
            </StackPanel>
        </StackPanel>
        <TextBox Grid.Row="4" x:Name="Stats" Text="" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="0,2,0,0" BorderThickness="2" IsReadOnly="True" Foreground="LightSlateGray" />
    </Grid>    
</Page>

Откройте файл App.xaml.cs (щелкните правой кнопкой мыши и выберите "Просмотреть код") и добавьте следующую строку в начало.

using CallingQuickstart;

Откройте файл MainWindow.xaml.cs (щелкните правой кнопкой мыши и выберите "Просмотреть код") и замените его содержимое следующей реализацией.

using Azure.Communication.Calling.WindowsClient;
using Azure.WinRT.Communication;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Media.Core;

namespace CallingQuickstart
{
    public sealed partial class MainWindow : Window
    {
        CallAgent callAgent;
        Call call;
        DeviceManager deviceManager;
        Dictionary<string, RemoteParticipant> remoteParticipantDictionary = new Dictionary<string, RemoteParticipant>();

        public MainWindow()
        {
            this.InitializeComponent();
            Task.Run(() => this.InitCallAgentAndDeviceManagerAsync()).Wait();
        }

        private async Task InitCallAgentAndDeviceManagerAsync()
        {
            // Initialize call agent and Device Manager
        }

        private async void Agent_OnIncomingCallAsync(object sender, IncomingCall incomingCall)
        {
            // Accept an incoming call
        }

        private async void CallButton_Click(object sender, RoutedEventArgs e)
        {
            // Start a call with video
        }

        private async void HangupButton_Click(object sender, RoutedEventArgs e)
        {
            // End the current call
        }

        private async void Call_OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            var state = (sender as Call)?.State;
            this.DispatcherQueue.TryEnqueue(() => {
                State.Text = state.ToString();
            });
        }
    }
}

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

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

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

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

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

Для целей этого краткого руководства замените <AUTHENTICATION_TOKEN> маркером доступа пользователя, который был создан для вашего ресурса Службы коммуникации Azure.

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

Добавьте в функцию InitCallAgentAndDeviceManagerAsync следующий код.

var callClient = new CallClient();
this.deviceManager = await callClient.GetDeviceManagerAsync();

var tokenCredential = new CallTokenCredential("<AUTHENTICATION_TOKEN>");
var callAgentOptions = new CallAgentOptions()
{
    DisplayName = "<DISPLAY_NAME>"
};

this.callAgent = await callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);
this.callAgent.OnCallsUpdated += Agent_OnCallsUpdatedAsync;
this.callAgent.OnIncomingCall += Agent_OnIncomingCallAsync;

Совершение видеозвонка

Добавьте реализацию в CallButton_Click начало вызова с видео. необходимо перечислить камеры с помощью экземпляра Диспетчера устройств и сформировать LocalVideoStream. Нужно указать параметры VideoOptions с аргументом LocalVideoStream и передать их в startCallOptions, чтобы задать начальные параметры звонка. Подключив LocalVideoStream к ней MediaPlayerElement, можно просмотреть предварительную версию локального видео.

var startCallOptions = new StartCallOptions();

if (this.deviceManager.Cameras?.Count > 0)
{
    var videoDeviceInfo = this.deviceManager.Cameras?.FirstOrDefault();
    if (videoDeviceInfo != null)
    {
        var selectedCamerea = CameraList.SelectedItem as VideoDeviceDetails;
        cameraStream = new LocalOutgoingVideoStream(selectedCamerea);

        var localUri = await cameraStream.StartPreviewAsync();
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            LocalVideo.Source = MediaSource.CreateFromUri(localUri);
        });

        startCallOptions.VideoOptions = new OutgoingVideoOptions(new[] { cameraStream });
    }
}

var callees = new ICommunicationIdentifier[1]
{
    new CommunicationUserIdentifier(CalleeTextBox.Text.Trim())
};

this.call = await this.callAgent.StartCallAsync(callees, startCallOptions);
this.call.OnRemoteParticipantsUpdated += Call_OnRemoteParticipantsUpdatedAsync;
this.call.OnStateChanged += Call_OnStateChangedAsync;

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

Добавьте реализацию в Agent_OnIncomingCallAsync ответ на входящий звонок с видео, передайте егоacceptCallOptionsLocalVideoStream.

var acceptCallOptions = new AcceptCallOptions();

if (this.deviceManager.Cameras?.Count > 0)
{
    var videoDeviceInfo = this.deviceManager.Cameras?.FirstOrDefault();
    if (videoDeviceInfo != null)
    {
        var selectedCamerea = CameraList.SelectedItem as VideoDeviceDetails;
        cameraStream = new LocalOutgoingVideoStream(selectedCamerea);

        var localUri = await cameraStream.StartPreviewAsync();
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            LocalVideo.Source = MediaSource.CreateFromUri(localUri);
        });

        acceptCallOptions.VideoOptions = new OutgoingVideoOptions(new[] { localVideoStream });
    }
}

call = await incomingCall.AcceptAsync(acceptCallOptions);

Удаленный участник и видеопотоки

Все удаленные участники доступны через коллекцию RemoteParticipants в экземпляре вызова. После подключения вызова можно получить доступ к удаленным участникам вызова и обработать удаленные видеопотоки.

private async void Call_OnVideoStreamsUpdatedAsync(object sender, RemoteVideoStreamsEventArgs args)
{
    foreach (var remoteVideoStream in args.AddedRemoteVideoStreams)
    {
        this.DispatcherQueue.TryEnqueue(async () => {
            RemoteVideo.Source = MediaSource.CreateFromUri(await remoteVideoStream.Start());
            RemoteVideo.MediaPlayer.Play();
        });
    }

    foreach (var remoteVideoStream in args.RemovedRemoteVideoStreams)
    {
        remoteVideoStream.Stop();
    }
}

private async void Agent_OnCallsUpdatedAsync(object sender, CallsUpdatedEventArgs args)
{
    foreach (var call in args.AddedCalls)
    {
        foreach (var remoteParticipant in call.RemoteParticipants)
        {
            var remoteParticipantMRI = remoteParticipant.Identifier.ToString();
            this.remoteParticipantDictionary.TryAdd(remoteParticipantMRI, remoteParticipant);
            await AddVideoStreamsAsync(remoteParticipant.VideoStreams);
            remoteParticipant.OnVideoStreamsUpdated += Call_OnVideoStreamsUpdatedAsync;
        }
    }
}

private async void Call_OnRemoteParticipantsUpdatedAsync(object sender, ParticipantsUpdatedEventArgs args)
{
    foreach (var remoteParticipant in args.AddedParticipants)
    {
        String remoteParticipantMRI = remoteParticipant.Identifier.ToString();
        this.remoteParticipantDictionary.TryAdd(remoteParticipantMRI, remoteParticipant);
        await AddVideoStreamsAsync(remoteParticipant.VideoStreams);
        remoteParticipant.OnVideoStreamsUpdated += Call_OnVideoStreamsUpdatedAsync;
    }

    foreach (var remoteParticipant in args.RemovedParticipants)
    {
        String remoteParticipantMRI = remoteParticipant.Identifier.ToString();
        this.remoteParticipantDictionary.Remove(remoteParticipantMRI);
    }
}

Отрисовка удаленного видео

Каждый удаленный видеопоток необходимо подключить к MediaPlayerElement.

private async Task AddVideoStreamsAsync(IReadOnlyList<RemoteVideoStream> remoteVideoStreams)
{
    foreach (var remoteVideoStream in remoteVideoStreams)
    {
        var remoteUri = await remoteVideoStream.Start();

        this.DispatcherQueue.TryEnqueue(() => {
            RemoteVideo.Source = MediaSource.CreateFromUri(remoteUri);
            RemoteVideo.MediaPlayer.Play();
        });
    }
}

Обновление состояния вызова

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

private async void Call_OnStateChanged(object sender, PropertyChangedEventArgs args)
{
    switch (((Call)sender).State)
    {
        case CallState.Disconnected:
            this.DispatcherQueue.TryEnqueue(() => { =>
            {
                LocalVideo.Source = null;
                RemoteVideo.Source = null;
            });
            break;

        case CallState.Connected:
            foreach (var remoteParticipant in call.RemoteParticipants)
            {
                String remoteParticipantMRI = remoteParticipant.Identifier.ToString();
                remoteParticipantDictionary.TryAdd(remoteParticipantMRI, remoteParticipant);
                await AddVideoStreams(remoteParticipant.VideoStreams);
                remoteParticipant.OnVideoStreamsUpdated += Call_OnVideoStreamsUpdated;
            }
            break;

        default:
            break;
    }
}

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

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

this.call.OnRemoteParticipantsUpdated -= Call_OnRemoteParticipantsUpdatedAsync;
this.call.OnStateChanged -= Call_OnStateChangedAsync;
await this.call.HangUpAsync(new HangUpOptions());

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

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

Чтобы совершить исходящий видеозвонок, укажите в текстовом поле ИД пользователя и щелкните кнопку Start Call.

Примечание. Вызов 8:echo123 останавливает видеопоток, так как эхо-бот не поддерживает потоковую передачу видео.

Дополнительные сведения об идентификаторах пользователей см. в этом руководстве.