開始使用呼叫主圖範例

Azure 通訊服務 群組通話主圖範例示範如何使用通訊服務通話 Web SDK 來建置群組通話體驗。

在本範例快速入門中,我們會先瞭解範例的運作方式,再於本機計算機上執行範例,然後使用您自己的 Azure 通訊服務 資源將範例部署至 Azure。

下載程序代碼

在 GitHub尋找此範例的專案。 您可以在個別的分支中找到目前公開預覽中包括Teams Interop通話錄製等功能之範例版本。

部署至 Azure

概觀

這個範例同時具有用戶端應用程式和伺服器端應用程式。 用戶端應用程式是使用 Microsoft Fluent UI 架構的 React/Redux Web 應用程式。 此應用程式會將要求傳送至 ASP.NET Core 伺服器端應用程式 ,以協助用戶端應用程式連線到 Azure。

以下是範例的外觀:

顯示範例應用程式登陸頁面的螢幕快照。

當您按下 [開始呼叫] 按鈕時,Web 應用程式會從伺服器端應用程式擷取使用者存取令牌。 接著,此令牌會用來將用戶端應用程式連線至 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 上開啟客戶端伺服器,並在埠 8080 上開啟 API 伺服器,以執行呼叫參與者的令牌等功能。

疑難排解

  • 應用程式會顯示「不支援的瀏覽器」畫面,但我位於支援的瀏覽器

    如果您的應用程式是透過localhost以外的主機名提供服務,您必須透過 HTTPs 而非 HTTP 提供流量。

發佈至 Azure

  1. npm run setup
  2. npm run build
  3. npm run package
  4. 使用 Azure 擴充功能並將通話/dist 目錄部署至您的應用程式服務

清除資源

如果您想要清除並移除通訊服務訂用帳戶,您可以刪除資源或資源群組。 刪除資源群組也會刪除與其相關聯的任何其他資源。 深入瞭解清除 資源

下一步

如需詳細資訊,請參閱下列文章:

延伸閱讀

  • 範例 - 在我們的範例概觀頁面上尋找更多範例和範例。
  • Redux - 客戶端狀態管理
  • FluentUI - Microsoft 支援的 UI 連結庫
  • React - 建置使用者介面的連結庫
  • ASP.NET Core - 建置 Web 應用程式的架構

適用於 iOS 的 Azure 通訊服務 群組通話主圖範例示範如何使用通訊服務通話 iOS SDK 來建置包含語音和視訊的群組通話體驗。 在本範例快速入門中,您將瞭解如何設定和執行範例。 提供內容範例的概觀。

下載程序代碼

在 GitHub尋找此範例的專案。

概觀

此範例是原生 iOS 應用程式,會使用 Azure 通訊服務 iOS SDK 來建置具有語音和視訊通話功能的通話體驗。 應用程式會使用伺服器端元件來布建存取令牌,然後用來初始化 Azure 通訊服務 SDK。 若要設定此伺服器端元件,請隨意遵循 受信任的服務與 Azure Functions 教學課程。

以下是範例的外觀:

顯示範例應用程式登陸頁面的螢幕快照。

當您按下 [開始新呼叫] 按鈕時,iOS 應用程式會提示您輸入要用於呼叫的顯示名稱。

顯示範例應用程式預先呼叫畫面的螢幕快照。

點選 [開始通話] 畫面上的 [下一步] 之後,您有機會透過 iOS 共用工作表共用通話的群組標識符。

顯示範例應用程式共用群組標識碼畫面的螢幕快照。

應用程式也可讓您藉由指定現有通話的標識碼或小組標識碼連結,加入現有的 Azure 通訊服務 通話。

顯示範例應用程式聯結呼叫畫面的螢幕快照。

加入通話之後,系統會提示您授與應用程式存取相機和麥克風的許可權,如果尚未獲得授權。 請記住,就像所有AVFoundation型應用程式一樣,真正的音訊和視訊功能只能在實際硬體上使用。

設定顯示名稱並加入通話後,您會看到核心通話體驗所在的主要通話畫布。

顯示範例應用程式主畫面的螢幕快照。

主要通話畫面的元件:

  • 媒體庫:顯示參與者的主要階段。 如果參與者已啟用相機,則會在這裡顯示其視訊摘要。 每個參與者都有個別的圖格,其中顯示其顯示名稱和視訊串流(如果有時)。 資源庫支援多個參與者,並在將參與者新增或移除至呼叫時更新。
  • 動作列:這是主要呼叫控件所在的位置。 這些控制項可讓您開啟/關閉視訊和麥克風、共享螢幕,以及離開通話。

您可以在下方找到設定範例的必要條件和步驟的詳細資訊。

必要條件

在本機執行範例

群組呼叫範例可以使用 XCode 在本機執行。 開發人員可以使用其實體裝置或模擬器來測試應用程式。

第一次執行範例之前

  1. 執行 pod install來安裝相依性。
  2. 在 XCode 中開啟 AzureCalling.xcworkspace
  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 App Registrations 底下的已註冊應用程式概觀頁面。 記下Application (client) IDDirectory (tenant) IDApplication ID URI

Azure 入口網站 上的 Microsoft Entra 設定。

  1. AppSettings.xcconfig如果尚未存在,請在根目錄建立檔案,並新增值:
    communicationTokenFetchUrl = <Application ID URI, without the https:// component>
    aadClientId = <Application (client) ID>
    aadTenantId = <Directory (tenant) ID>
    

清除資源

如果您想要清除並移除通訊服務訂用帳戶,您可以刪除資源或資源群組。 刪除資源群組也會刪除與其相關聯的任何其他資源。 深入瞭解清除 資源

下一步

如需詳細資訊,請參閱下列文章:

延伸閱讀

適用於 Android 的 Azure 通訊服務 群組通話主圖範例示範如何使用通訊服務通話 Android SDK 來建置包含語音和視訊的群組通話體驗。 在本範例快速入門中,您將瞭解如何設定和執行範例。 提供內容範例的概觀。

下載程序代碼

在 GitHub尋找此範例的專案。

概觀

此範例是原生 Android 應用程式,會使用 Azure 通訊服務 Android UI 用戶端連結庫來建置同時具有語音和視訊通話功能的通話體驗。 應用程式會使用伺服器端元件來布建存取令牌,然後用來初始化 Azure 通訊服務 SDK。 若要設定此伺服器端元件,請隨意遵循 受信任的服務與 Azure Functions 教學課程。

以下是範例的外觀:

顯示範例應用程式登陸頁面的螢幕快照。

當您按下 [開始新呼叫] 按鈕時,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 App Registrations 底下的已註冊應用程式概觀頁面。 記下Package nameSignature hashMSAL Configutaion

Azure 入口網站 上的 Microsoft Entra 設定。

  1. 編輯 AzureCalling/app/src/main/res/raw/auth_config_single_account.json 並設定 isAADAuthEnabled 為啟用 Microsoft Entra ID。

  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. 從 Azure 入口網站 複製 MSAL Android 組態,並貼到 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並設定索引鍵Azure Active DirectoryaadScopesExpose an API的值

  6. 將中的 AzureCalling/app/assets/appSettings.propertiesgraphURL設定為圖形 API 端點,以擷取使用者資訊。

  7. 編輯 AzureCalling/app/src/main/assets/appSettings.properties 並設定金鑰 tenant 的值,以啟用無訊息登入,讓使用者不必在重新啟動應用程式時再次驗證。

清除資源

如果您想要清除並移除通訊服務訂用帳戶,您可以刪除資源或資源群組。 刪除資源群組也會刪除與其相關聯的任何其他資源。 深入瞭解清除 資源

下一步

如需詳細資訊,請參閱下列文章:

延伸閱讀

適用於 Windows 的 Azure 通訊服務 群組通話主圖範例示範如何使用通訊服務通話 Windows SDK 來建置包含語音和視訊的群組通話體驗。 在此範例中,您將瞭解如何設定和執行範例。 提供內容範例的概觀。

在本快速入門中,您將瞭解如何使用適用於 Windows 的 Azure 通訊服務 通話 SDK 來啟動 1:1 視訊通話。

UWP 範例程序代碼

必要條件

若要完成本教學課程,您需要下列必要條件:

設定

建立專案

在 Visual Studio 中,使用空白應用程式 (通用 Windows) 範本建立新專案,以設定單頁 通用 Windows 平台 (UWP) 應用程式。

顯示 Visual Studio 內 [新增 UWP 專案] 視窗的螢幕快照。

Install the package

以滑鼠右鍵按下您的專案,然後移至 Manage Nuget Packages 安裝 Azure.Communication.Calling.WindowsClient1.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 CallHang 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
        }
    }
}

物件模型

下列類別和介面會處理 Azure 通訊服務 呼叫 SDK 的一些主要功能:

名稱 描述
CallClient CallClient是呼叫客戶端連結庫的主要進入點。
CallAgent CallAgent用來啟動和聯結呼叫。
CommunicationCall CommunicationCall用來管理已放置或聯結的呼叫。
CallTokenCredential CallTokenCredential會當做權杖認證來具現化 CallAgent
CommunicationUserIdentifier CommunicationUserIdentifier用來表示使用者的身分識別,它可以是下列其中一個選項:CommunicationUserIdentifierPhoneNumberIdentifierCallingApplication

驗證用戶端

若要初始化 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。 我們需要使用 來設定 VideoOptionsLocalVideoStream ,並將它傳遞給 , 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 } }
    };
}

根據 方法上 CameraList_SelectionChanged 選取的相機,新增程序代碼以建立LocalVideoStream。

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 ,以接聽有視訊的來電,將 傳遞 LocalVideoStreamacceptCallOptions

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 上建置並執行程式代碼。 針對解決方案平台,我們支援 ARM64x64x86

您可以在文字欄位中提供使用者識別碼,然後按下 Start Call 按鈕,以進行輸出視訊通話。

注意:通話 8:echo123 會停止視訊串流,因為echo Bot不支持視訊串流。

如需使用者識別碼 (身分識別) 的詳細資訊,請參閱 使用者存取令牌 指南。

WinUI 3 範例程序代碼

必要條件

若要完成本教學課程,您需要下列必要條件:

設定

建立專案

在 Visual Studio 中,使用 空白應用程式、已封裝 (Desktop 中的 WinUI 3) 範本建立新專案,以設定單頁 WinUI 3 應用程式。

顯示 Visual Studio 內 [新增 WinUI 專案] 視窗的螢幕快照。

Install the package

以滑鼠右鍵按下您的項目,然後移至 Manage Nuget Packages 安裝 Azure.Communication.Calling.WindowsClient1.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 CallHang 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();
            });
        }
    }
}

物件模型

下列類別和介面會處理 Azure 通訊服務 呼叫 SDK 的一些主要功能:

名稱 描述
CallClient CallClient是呼叫客戶端連結庫的主要進入點。
CallAgent CallAgent用來啟動和聯結呼叫。
CommunicationCall CommunicationCall用來管理已放置或聯結的呼叫。
CallTokenCredential CallTokenCredential會當做權杖認證來具現化 CallAgent
CommunicationUserIdentifier CommunicationUserIdentifier用來表示使用者的身分識別,它可以是下列其中一個選項:CommunicationUserIdentifierPhoneNumberIdentifierCallingApplication

驗證用戶端

若要初始化 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。 我們需要使用 來設定 VideoOptionsLocalVideoStream ,並將它傳遞給 , 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 ,以接聽有視訊的來電,將 傳遞 LocalVideoStreamacceptCallOptions

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 上建置並執行程式代碼。 針對解決方案平台,我們支援 ARM64x64x86

您可以在文字欄位中提供使用者識別碼,然後按下 Start Call 按鈕,以進行輸出視訊通話。

注意:通話 8:echo123 會停止視訊串流,因為echo Bot不支持視訊串流。

如需使用者識別碼 (身分識別) 的詳細資訊,請參閱 使用者存取令牌 指南。