Azure Communication Services の Windows 用のグループ通話のヒーロー サンプル は、Communication Services Calling Windows SDK を使用して、音声とビデオを含むグループ通話エクスペリエンスを構築する方法を示しています。 このサンプルでは、サンプルを設定して実行する方法について説明します。 背景情報については、サンプルの概要を参照してください。
このクイックスタートでは、Windows 用の Azure Communication Services Calling SDK を使用して 1 対 1 のビデオ通話を開始する方法について説明します。
このチュートリアルを完了するには、次の前提条件を用意しておく必要があります。
Visual Studio の [空白のアプリ (ユニバーサル Windows)] テンプレートを使用して新しいプロジェクトを作成し、シングルページ ユニバーサル Windows プラットフォーム (UWP) アプリをセットアップします。
プロジェクトを右クリックし、Manage Nuget Packages
に移動して、Azure.Communication.Calling.WindowsClient
1.2.0-beta.1 以上のバージョンをインストールします。 [プレリリースを含める] がオンになっていることを確認します。
[Package.appxmanifest
] に移動し、[Capabilities
](機能) をクリックします。
インターネットへのインバウンド アクセスとアウトバウンド アクセスを取得するには、[Internet (Client & Server)
](インターネット (クライアント & サーバー)) チェック ボックスをオンにします。
マイクの音声フィードにアクセスするには、[Microphone
](マイク) チェック ボックスをオンにします。
デバイスのカメラにアクセスするには、[WebCam
](Web カメラ) チェック ボックスをオンにします。
右クリックして [コードの表示] を選択し、Package.appxmanifest
に次のコードを追加します。
<Extensions>
<Extension Category="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>RtmMvrUap.dll</Path>
<ActivatableClass ActivatableClassId="VideoN.VideoSchemeHandler" ThreadingModel="both" />
</InProcessServer>
</Extension>
</Extensions>
ロジックをアタッチするには、基本的なレイアウトを構成する必要があります。 発信通話を行うには、呼び出し先のユーザー ID を指定するための TextBox
が必要です。 また、[Start Call
](通話を開始) ボタンと [Hang Up
](通話終了) ボタンも必要となります。
さらに、ローカル ビデオをプレビューすると共に、もう一方の参加者のリモート ビデオをレンダリングする必要があります。 そのため、ビデオ ストリームを表示するための要素が 2 つ必要となります。
プロジェクトの 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 Communication Services Calling SDK の主な機能のいくつかは、次のクラスとインターフェイスによって処理されます。
テーブルを展開する
名前
説明
CallClient
CallClient
は、通話クライアント ライブラリへのメイン エントリ ポイントです。
CallAgent
CallAgent
は、通話を開始して参加するために使用します。
CommunicationCall
CommunicationCall
は、開始した、または参加した通話の管理に使用されます。
CallTokenCredential
CallTokenCredential
は、CallAgent
をインスタンス化するためのトークン資格情報として使用されます。
CommunicationUserIdentifier
CommunicationUserIdentifier
はユーザーの ID を表すために使用され、オプション CommunicationUserIdentifier
、PhoneNumberIdentifier
、CallingApplication
のいずれかになります。
CallAgent
を初期化するには、ユーザー アクセス トークンが必要です。 通常、このトークンは、アプリケーション固有の認証を使用してサービスから生成されます。 ユーザー アクセス トークンの詳細については、ユーザー アクセス トークン のガイドを参照してください。
クイック スタートでは、<AUTHENTICATION_TOKEN>
を Azure Communication Service リソース用に生成されたユーザー アクセス トークンに置き換えます。
トークンが与えられたら、それを使用して 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
を構築する必要があります。 LocalVideoStream
を使用して VideoOptions
を設定し、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 Communication Services 通話、1:1 通話、Azure Communication Services Group 通話、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
に追加し、LocalVideoStream
を acceptCallOptions
に渡します。
var incomingCall = args.IncomingCall;
var acceptCallOptions = new AcceptCallOptions() {
IncomingVideoOptions = new IncomingVideoOptions()
{
IncomingVideoStreamKind = VideoStreamKind.RemoteIncoming
}
};
_ = await incomingCall.AcceptAsync(acceptCallOptions);
リモート参加者は全員、通話インスタンスの RemoteParticipants
コレクションを通じて利用できます。 通話が接続済み (CallState.Connected
) になると、通話のリモート参加者にアクセスし、リモート ビデオ ストリームを処理できます。
注意
ユーザーが通話に参加すると、RemoteParticipants
コレクションを通じて現在のリモート参加者にアクセスできます。 RemoteParticipantsUpdated
イベントはこれらの既存の参加者にはトリガーされません。 このイベントは、ユーザーが既に通話に参加している間に、リモート参加者が通話に参加または離脱した場合にのみトリガーされます。
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
](通話終了) ボタンがクリックされたら、現在の通話を終了します。 作成した callAgent との通話を終わらせるには実装を HangupButton_Click に追加し、参加者更新と呼び出し状態のイベント ハンドラーを破棄します。
var call = this.callAgent?.Calls?.FirstOrDefault();
if (call != null)
{
try
{
await call.HangUpAsync(new HangUpOptions() { ForEveryone = true });
}
catch(Exception ex)
{
}
}
コードは、Visual Studio でビルドして実行できます。 ソリューションのプラットフォームに関しては、ARM64
、x64
、x86
がサポートされます。
発信ビデオ通話を行うには、テキスト フィールドにユーザー ID を指定し、[Start Call
](通話を開始) ボタンをクリックします。
注: エコー ボットはビデオ ストリーミングに対応していないので、8:echo123
を呼び出すとビデオ ストリームが停止します。
ユーザー ID の詳細については、ユーザー アクセス トークン のガイドを参照してください。
このチュートリアルを完了するには、次の前提条件を用意しておく必要があります。
Visual Studio で、Blank App, Packaged (WinUI 3 in Desktop) テンプレートでプロジェクトを新規作成し、シングルページの WinUI 3 アプリを設定します。
プロジェクトを右クリックし、Manage Nuget Packages
に移動して、Azure.Communication.Calling.WindowsClient
1.0.0 以上のバージョンをインストールします。 [プレリリースを含める] がオンになっていることを確認します。
次のコードを app.manifest
に追加します。
<file name="RtmMvrMf.dll">
<activatableClass name="VideoN.VideoSchemeHandler" threadingModel="both" xmlns="urn:schemas-microsoft-com:winrt.v1" />
</file>
ロジックをアタッチするには、基本的なレイアウトを構成する必要があります。 発信通話を行うには、呼び出し先のユーザー ID を指定するための TextBox
が必要です。 また、[Start Call
](通話を開始) ボタンと [Hang Up
](通話終了) ボタンも必要となります。
さらに、ローカル ビデオをプレビューすると共に、もう一方の参加者のリモート ビデオをレンダリングする必要があります。 そのため、ビデオ ストリームを表示するための要素が 2 つ必要となります。
プロジェクトの 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 Communication Services Calling SDK の主な機能のいくつかは、次のクラスとインターフェイスによって処理されます。
テーブルを展開する
名前
説明
CallClient
CallClient
は、通話クライアント ライブラリへのメイン エントリ ポイントです。
CallAgent
CallAgent
は、通話を開始して参加するために使用します。
CommunicationCall
CommunicationCall
は、開始した、または参加した通話の管理に使用されます。
CallTokenCredential
CallTokenCredential
は、CallAgent
をインスタンス化するためのトークン資格情報として使用されます。
CommunicationUserIdentifier
CommunicationUserIdentifier
はユーザーの ID を表すために使用され、オプション CommunicationUserIdentifier
、PhoneNumberIdentifier
、CallingApplication
のいずれかになります。
CallAgent
を初期化するには、ユーザー アクセス トークンが必要です。 通常、このトークンは、アプリケーション固有の認証を使用してサービスから生成されます。 ユーザー アクセス トークンの詳細については、ユーザー アクセス トークン のガイドを参照してください。
クイック スタートでは、<AUTHENTICATION_TOKEN>
を Azure Communication Service リソース用に生成されたユーザー アクセス トークンに置き換えます。
トークンが与えられたら、それを使用して 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
を構築する必要があります。 LocalVideoStream
を使用して VideoOptions
を設定し、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
に追加し、LocalVideoStream
を acceptCallOptions
に渡します。
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
コレクションを通じて利用できます。 通話の接続後、通話のリモート参加者にアクセスし、リモート ビデオ ストリームを処理できます。
注意
ユーザーが通話に参加すると、RemoteParticipants
コレクションを通じて現在のリモート参加者にアクセスできます。 OnRemoteParticipantsUpdated
イベントはこれらの既存の参加者にはトリガーされません。 このイベントは、ユーザーが既に通話に参加している間に、リモート参加者が通話に参加または離脱した場合にのみトリガーされます。
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
](通話終了) ボタンがクリックされたら、現在の通話を終了します。 作成した callAgent との通話を終わらせるには実装を HangupButton_Click に追加し、参加者更新と呼び出し状態のイベント ハンドラーを破棄します。
this.call.OnRemoteParticipantsUpdated -= Call_OnRemoteParticipantsUpdatedAsync;
this.call.OnStateChanged -= Call_OnStateChangedAsync;
await this.call.HangUpAsync(new HangUpOptions());
コードは、Visual Studio でビルドして実行できます。 ソリューションのプラットフォームに関しては、ARM64
、x64
、x86
がサポートされます。
発信ビデオ通話を行うには、テキスト フィールドにユーザー ID を指定し、[Start Call
](通話を開始) ボタンをクリックします。
注: エコー ボットはビデオ ストリーミングに対応していないので、8:echo123
を呼び出すとビデオ ストリームが停止します。
ユーザー ID の詳細については、ユーザー アクセス トークン のガイドを参照してください。