Azure Communication Servicesの「音声とビデオ」でHoloLens 2をTeamsにつなぐ

# カスタムアプリにコミュニケーション機能を実装するためのAzure Communication Services

Azureのクラウドサービスの中にAzure Communication Services(以下ACS)というサービスがあります。このサービスはRest APIやクライアントSDKを利用することで、カスタムアプリに通信機能を追加することができます。通話などのコミュニケーション技術を活用したアプリを構築したい場合はWebRTC等非同期通信の環境や実装等を行う必要があり、専門知識も必要です。ACSはクラウドサービスとして通信機能を手軽に組み込む手段として活用することができます。
ACSが提供する通信機能として大きく以下の手段を提供します。

| サービス |　概要 |
|:-:|:--|
|SMS| ショートメール(SMS)を送信する機能。送信には送信元、送信先の電話番号が必要にあります。一般的に利用している携帯電話のSMSのメッセージングをACS経由で利用することができます。<BR>ドキュメント：[Microsoft Learn/Azure  Communication Services/SMS](https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/sms/concepts?WT.mc_id=WDIT-MVP-5003104)  |
|電子メール| 電子メールを送信する機能。送信に関する機能を構築するためには組織で送信元として使用できるドメインの準備などが必要 <BR>ドキュメント：[Microsoft Learn/Azure  Communication Services/電子メール](https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/email/prepare-email-communication-resource?WT.mc_id=WDIT-MVP-5003104)  |
|音声とビデオ| ユーザのデバイスで音声とビデオを利用した通話を実現することができます。特徴的な点としては**Teams会議への参加**だと思います。この機能を利用することでTeams会議用リンクを利用してカスタムアプリからTeams会議に参加することが可能になります。 <BR>ドキュメント：[Microsoft Learn/Azure  Communication Services/音声とビデオ](https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/voice-video-calling/calling-sdk-features?WT.mc_id=WDIT-MVP-5003104)  |
|電話の通話| PSTN (公衆交換電話網) 音声通話をACS経由で利用することも可能です。あらかじめPSTNが利用できるようにACSに登録をしておく必要があります <BR>ドキュメント：[Microsoft Learn/Azure  Communication Services/電話の通話](https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/telephony/telephony-concept?WT.mc_id=WDIT-MVP-5003104)  |
|チャット| クロスプラットフォームでのリアルタイムテキスト通信を追加するための機能です。こちらも「音声とビデオ」機能と同じように**Teamsとの連携が可能**です。具体的にはTeamsチャットにメッセージを通信することが可能になります。また、アプリをバックアップ動作させている際にトースト通知する機能も利用することが可能です。<BR>ドキュメント：[Microsoft Learn/Azure  Communication Services/チャット](https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/chat/concepts?WT.mc_id=WDIT-MVP-5003104)   |
|ネットワークトラバーサル|<BR>ドキュメント：[Microsoft Learn/Azure  Communication Services/ネットワークトラバーサル](https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/network-traversal?WT.mc_id=WDIT-MVP-5003104)   |
|Job Router| <BR>ドキュメント：[Microsoft Learn/Azure  Communication Services/Job Router](https://learn.microsoft.com/ja-jp/azure/communication-services/concepts/router/concepts?WT.mc_id=WDIT-MVP-5003104)  |

最近はコミュニケーション手段としてのTeamsを活用する企業はあると思います。
オリジナルのカスタムアプリやシステムからTeamsへのアクションが可能な仕組みを構築できることが様々な恩恵を生み出せるかもしれません。今回はHoloLens 2にTeams会議に参加するための実装を入れたコンテンツを作成し、HoloLens2の装着車視点の映像（Mixed Reality Captureで見えている現実＋デジタルコンテンツの映像）をTemas会議で共有する簡単なサンプルコードを作成しその機能を検証します。

【メニュー】
1. ACSを利用してカスタムアプリでTeamsに参加する方法について
1. ACS SDKとACS Calling Unity SDKについて
1. ACSの作成とトークンの生成について
1. サンプルコードの実装
1. サンプルの主要コードの解説（Unity）
1. サンプルの主要コードの解説（関数アプリ)
1. Azure Communication Services Calling Unity SDKの実装部分の解説



## 1.ACSを利用してカスタムアプリでTeamsに参加する方法について

ACSを使ってTeams等と音声やビデオで会議をする手段については以下のサイトが参考になります。

https://learn.microsoft.com/ja-jp/azure/architecture/guide/mobile/azure-communication-services-architecture?WT.mc_id=WDIT-MVP-5003104#application-joins-a-scheduled-teams-call

### アーキテクチャ例

上記のサイトの中に「[アプリケーションがスケジュールされた Teams 通話に参加する](https://learn.microsoft.com/ja-jp/azure/architecture/guide/mobile/azure-communication-services-architecture?WT.mc_id=WDIT-MVP-5003104#application-joins-a-scheduled-teams-call)」という項目があります。このアーキテクチャは普段Teams会議等を利用する手順の中でカスタムアプリもその会議参加する場合のアーキテクチャ例になっています。


|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/9d900c7e-1bb6-8ca3-d6d5-6a68bb51daaf.png)<BR><font size="2px">引用元:Microsoft Learn,Azure Communication Services のアーキテクチャ設計,https://learn.microsoft.com/ja-jp/azure/architecture/guide/mobile/azure-communication-services-architecture?WT.mc_id=WDIT-MVP-5003104#application-joins-a-scheduled-teams-call, (参照2023/08/01)</font>|
|:-:|

処理のフローは次の通りになります。
1. グループ通話をスケジュールします。 いくつか手段ありますが、いずれの手段においても一般的なTeamsの通話リンクURLを生成します。<UL><LI>(1A) Microsoft Graph API を使用する場合</li><li>(1B) Outlook または Teams を使用する場合</li></ul>
1. コミュニケーション コントローラー サービスは、Teams 通話の詳細を Communication Services クライアントと共有します。
1. **Teams ユーザーは Teams UI から通話に参加**、必要に応じて外部ユーザーのロビー待機を許可します（こTeams テナントの構成と特定の会議設定に応じて）
1. **Communication Service ユーザーは、手順 2 で受け取った詳細情報を使用して Call クライアントを初期化し、Teams の会議に参加**します。
1. ユーザー同士が、音声と映像でコミュニケーションします。

ACSを使ってTeams会議に参加するアプリをつくる場合は、このアーキテクチャを参考に実装します。

## ACSからTeams会議に参加するために必要な情報

ACSを利用してTeamsにアクセスするためには、以下の情報が必要になります。

* 参加するTeams会議のリンク情報
* アクセストークン(＋ユーザ名)

基本的な考え方は一般的なTeamsと同じです。Teams会議リンクを利用して会議に参加します。Teams会議のリンクについてはOutlookやMicrosoft 365のGraph APIで生成します。
また、Teams会議には任意のユーザとしてアクセスする必要があるためそのためのトークンが必要になります。
ACSではトークンの生成方法が２つあります。

1. ACSからトークンを作成
1. Teamsユーザのアクセストークンを生成

### Teamsユーザからアクセストークンを生成

Azure ADなどのすでにあるアカウントの認証を利用してクライアントアプリようにトークンを生成する方法です。この方法ではカスタムアプリでACSを使うタイミングでMicrosoft Authentication Library (MSAL) ライブラリを利用しAzure ADの認証を通します。この際に発行されるトークンを利用してACSへのアクセスを実現します。
Teamsのユーザからのアクセスは以下のクイックスタートが参考になります。

https://learn.microsoft.com/ja-jp/azure/communication-services/quickstarts/manage-teams-identity?pivots=programming-language-csharp&WT.mc_id=WDIT-MVP-5003104


### ACSからトークンを生成する

Teamsのアカウントからトークンを生成する方法以外に、ACSの機能として、このサービスに対して直接認証するためのトークン生成を行う機能も提供されています。このトークンは一時的なアクセス（期限付き）が可能なものです。
今回のHoloLens 2のサンプルでは以下のクイックスタートを参考にAzure Functionでトークンを取得する実装も行っています。またテスト用であれば[Azure PortalからGUI操作で生成](https://learn.microsoft.com/ja-jp/azure/communication-services/quickstarts/identity/access-tokens?tabs=windows&pivots=platform-azportal&WT.mc_id=WDIT-MVP-5003104)することも可能です。

https://learn.microsoft.com/ja-jp/azure/communication-services/quickstarts/identity/access-tokens?tabs=windows&pivots=programming-language-javascript&WT.mc_id=WDIT-MVP-5003104


## 2.Azure Communication Services Calling SDKとAzure Communication Services Calling Unity SDKについて

ACSにはサービスを利用するためのクライアントSDKとしてAzure Communication Services Calling SDKが提供されています。クロスプラットフォーム用に以下の言語で提供されています。

* JavaScript 用 Azure Communication Services Calling SDK(Web用)
* Android 用 Azure Communication Services Calling SDK
* iOS 用 Azure Communication Services Calling SDK
* Windows 用 Azure Communication Services Calling SDK

各言語用の機能を駆使するといろいろなデバイスや仕組みの中でTeamsとの連携や通品アプリを作成可能になっています。今回はHoloLens 2用に実装するのですが、このサービスがリリースされたあたりにすでに検証されている方がいらっしゃるので紹介します。

https://akihiro-document.azurewebsites.net/post/hololens2_azurecommunicationserviceswithunity/

内容としてはHoloLens 2用にUWP用のSDKから必要なライブラリを抽出しUnityに取り込んで利用するというものです。今回紹介する方法も中身の動作としてはこの手段と同じものになりますが、最近Unity用にAzure Communication Services Calling SDKのパッケージングとDLLのラッパークラスがセットになった「Azure Communication Services Calling Unity SDK」がβ版で提供されています。今回はこのUnity用のSDKでHoloLens 2をTeams会議に参加させてみたいと思います。

## 3.ACSの作成とトークンの生成について

最初にACSを作成します。

Azure ポータルを開き新しいリソースを追加します。[メニュー]-[リソースの作成]を選択します。検索項目に[Communication Services]と入力しCommunication Servicesを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/15fc69f5-2fdb-12cf-6324-b197e40420f1.png)

Communication Servicesを選択したら作成ボタンを押してサービスに必要な情報と設定してきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/3ec031cd-d1d8-571e-2dd1-b682fb21419e.png)

特殊な設定はありません。紐づけたいサブスクリプション、リソースグループ、リージョンを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/46f1bf95-960d-9668-1e93-910fa72eb346.png)

設定が問題なければ検証が完了し、作成することができるようになります。最後に[作成]を押してサービスのデプロイを実施します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/055948a4-ac18-8dd3-8214-0a656f86ebc5.png)

### トークンの作成について

ACSへクライアントを接続するためのトークンはAzure ポータル上で作成することが可能です。トークンは会議に参加している間のみ利用する前提のため有効期限は最大で24hになります。
このためAzure ポータル上でのトークン生成はあくまでテスト用に一時的に作成するために利用する形になります。
作成したACSを選択しメニューから[IDおよびユーザアクセストークン]を選択します。次に利用するサービスを選択するのですが、今回はTeams会議に音声とビデオを使って参加するため[音声およびビデオ通話(VoIP)]にチェックします。Teamsチャットも利用する場合はチャットもチェックします。次に[生成]を押すとIDとトークンが生成されます。トークンの有効期限は24hになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/40e2d227-b383-c5ee-8e64-1b61155cf4ae.png)

Azure Communication Services Calling SDKではアクセストークンを接続時に渡す形になります。

:::note info
実際にカスタムアプリケーションでトークンを利用する場合はAzure Communication Services Calling SDKの機能を使うことでトークンの発行を行うことができます。今回のGithubサンプルではAzure Functionを利用してJavascript用のSDKでトークンの生成を行っています。仕組み的には簡単に作成可能ですが、実際の業務アプリとして構成する場合はアクセストークンの生成についての認証周りも実装が必要になります。

参考にしたコードは以下のサイトになります。
[Microsoft Learn - Azure Communication Services - クイック スタート:アクセス トークンを作成して管理する](https://learn.microsoft.com/ja-jp/azure/communication-services/quickstarts/identity/access-tokens?tabs=windows&pivots=programming-language-javascript&WT.mc_id=WDIT-MVP-5003104)

:::

## 4.サンプルコードの実装

次にシンプルな実装として以下の機能を実装したサンプルを構築しました。コードは以下のgithubに公開しています。

https://github.com/TakahiroMiyaura/ACSXRSamples

主な仕様としては以下の通りです。

1.　Azure Function経由でAzure Communication Services Calling SDKからトークンを取得
1.　ユーザ名の設定とTeamsへの接続に関するUI（通話開始/終了、Mute）
1.　Teams会議リンクはハードコーディング
1.　UIはMRTK3を使用

実行すると以下のようにHoloLens 2で見ている映像をTeams会議に共有することができます。

<iframe width="560" height="315" src="https://www.youtube.com/embed/6P5-9IZsik8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

また、Teams会議のURLについては事前にOutlookなどで作成したうえでURLをコピーしておきます（Unityプロジェクト内で設定します）。

### 開発環境

* ライブラリ
    * Azure Communication Services Calling Unity SDK 1.0.0-β1
    * Mixed Reality Toolkit 3 Pre.17
    * Mixed Reality OpenXR Plugin 1.7.2

* ツール
    * Mixed Reality Feature Tool v1.0.2209.0 Preview（[ダウンロード先](https://learn.microsoft.com/ja-jp/windows/mixed-reality/develop/unity/welcome-to-mr-feature-tool?WT.mc_id=WDIT-MVP-5003104))
    * Visual Studio 2022
    * Unity Hub 3.4.2
    * Unity 2021.3.16f1

### サンプルのセットアップ

Githubのコードを動かすためには以下の手順に従ってください。

最初にコードをクローンします。

```
PS D:\workspaces git clone https://github.com/TakahiroMiyaura/ACSXRSamples.git
```
次に必要な作業を進めていきます。

#### Azure Functionsに対してトークン生成用Function Appのデプロイとアプリケーション設定

今回のサンプルはトークン生成用Function Appを使ってACSからトークンを取得することも可能になっています。Visual Studio Codeを使えばAzure Functionsに対してコードをデプロイすることができます。Visual Studio Codeでクローンしたリポジトリの[ACSXRSamples\UserAccessTokenFunctions]フォルダを開き、Function Appをデプロイします。手順については以下のサイトを参考にしてください。

https://learn.microsoft.com/ja-jp/azure/azure-functions/create-first-function-vs-code-node?pivots=nodejs-model-v3&WT.mc_id=WDIT-MVP-5003104

このFunction AppはAzure Functionsのアプリケーション設定のキー値[ACS_CONNECTION_STRING]の接続情報を使ってACSにアクセスします。接続文字列は先ほど作成したACSをAzure ポータルで開きメニューから[キー]を選択すると確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/a18cd2ae-38ad-36d5-010f-480334f46fb6.png)

接続文字列はAzure Functionsの[設定]-[構成]を選択し[ACS_CONNECTION_STRING]で新しくキーを作成し設定を行ってください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/d9959dfe-aa10-d355-601a-9730f3ed54d9.png)

また、デプロイした関数アプリへの接続URLを合わせて控えておきます。（Unityプロジェクトの構築時に使用）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/10bca2f1-5e90-7978-c5e5-04a718784506.png)

#### Unity プロジェクトのセットアップとデプロイ

次にHololens 2にデプロイするUWPアプリを構築します。

1. Mixed Reality Feature Toolを起動しクローンしたUnity Projectにライブラリを適用します。
    1. ツールを開きクローンしたリポジトリの[ACSXRSamples\Unity]を選択し開きます。<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/5ab7b04a-b8ff-9157-824b-5936beb20aff.png)
    1. まずは今回のメイン[Azure Communication Services Calling SDK]です。<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/ccf488bc-a142-7446-c536-f62616154745.png)
    1. 次はMRTK3です。こちらはとりあえずすべてのライブラリを追加します。MRTK3の横にある[Select All]を押すことですべてチェックされます。<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/9e8a8752-6f7c-71da-d010-b94453f75a09.png)
    1. 最後にMixed Reailty OpenXR Pluginをチェック。<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/7d645b53-869f-3d02-0896-f3defc730ae5.png)
    1. すべてチェックしたら[Get feature]を押して次に進みます。後はImportすることでセットアップが完了します。<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/ff553359-1ff0-33e0-a6dd-3819e9656393.png)
1. 次にUnityプロジェクトを開きます。
    1. 先ほどMixed Reality Feature Toolでも設定したUnityプロジェクトをUnity Editorで開いてください<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/abd4373c-5171-1790-2407-8286d0846022.png)
    1. 次にサンプルシーンを開きます。シーンは[Project]パネルの中から[Assets\ACSSamples\Scenes\SampleScene]です。
    1. 最初開いたときTextMeshProのImporterが起動します。[Import TMP Essentials]を押して必要なリソースをインポートしてください。<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/e3a5dd66-0879-9f85-ee18-a75bda3d36e0.png)
    1. [Hierarchy]パネルの中から[TeamsCalling]オブジェクト選択します。[Inspector]パネルを選択し以下のプロパティに値を設定します。<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/051be4ac-29d0-78da-10c2-69c94dfbb5d9.png)<table><tr><th>コンポーネント名</th><th>プロパティ名</th><th>設定内容</th></tr><tr><td rowspan="3">Teams Calling Samples</td><td>Teams Link</td><td>参加するTeams会議URL</td></tr><tr><td>User Token</td><td>(任意)アクセストークン<br>Azure Functionsを使用しない場合は設定が必要。</td></tr><tr><td>Token Provider</td><td>(任意)ACS Tokenコンポーネントを設定<br>Azure Functionsを使用しない場合はこのプロパティを空にし、[User Token]を設定します。</td></tr><tr><td>ACS Token</td><td>ACS Functions URL</td><td>(任意)登録したACSトークン生成用Function AppのURL<br>Azure Functionsを使用しない場合は空欄でも可。その場合はUser Tokenを設定してください。</td></tr></table>
    1. [File]-[Build Settings]でビルド設定を開きます。UWPでビルドするために[Universal Windows Platform]を選択し[Switch Platform]を押します。プラットフォームを変更後ビルドを実施します。<BR>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/fb3cc265-cc7a-7363-a231-d16662df489d.png)
1. Unity Editor or Visual StudioでUWPプロジェクトをビルド時デプロイします。ビルドのアーキテクチャは**ARM64**を選択してください。

## 5.サンプルの主要コードの解説（Unity）

最後に今回のサンプルのコードの解説です。

### UI
UIについてはMRTK3を使用しています。MRTK3ではUGUIでのメニューパネルなども作りやすい仕組みがあるので今回はそれを利用しています。UIとしては空間に以下のようなものが表示されます。
User NameはTemas会議に参加した際のユーザ名として利用されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152548/463e2566-1854-7581-2b10-f10b470ba2f6.png)

### 実装コードについて

サンプルでは大きく2つのクラスで実装しています。

* TeamsCallingSamples<BR>Unityコンポーネント。UIアクションでTeamsとの接続実行等を実施
* AzureCommunicationUWPPlugin<BR>Azure Communication Services Calling Unity SDKを使用してTeams接続に必要なイベントや手続きを集約したクラス

### TeamsCallingSamples

作成したUIのアクションに対して様々な処理をするコンポーネントとして追加しています。後述のAzureCommunicationUWPPluginを経由してTeamsミーティングに参加したりします。

| メソッド名 | 機能 |
|:-:|:-|
| Start | Azure Functionsを利用してアクアストークンを設定している場合はアクセストークンの取得を試みます。 |
| Update  | ステータスの更新などを実施 |
| LeaveMeeting  | 指定されたTeamsミーティングから退出します。UIボタンなどのイベントとして設定 |
| Mute  | ミーティング参加中にマイクのミュート/アンミュートの変更を行います。UIボタンなどのイベントとして設定  |
| JoinCall  |  指定されたTeamsミーティングに参加します。UIボタンなどのイベントとして設定 |

#### AzureCommunicationUWPPlugin

[Azure Communication Services Calling Unity SDK]を利用してACSを利用します。このSDKなのですが現在はUWP用のAzure Communication Services Calling SDKのラッパーとして機能しているので基本的にはUWP時のみコードが有効化されるように#ifディレクティブ(#if WINDOWS_UWP～#endif)で実装する必要があります。

ACSへの制御
| メソッド名 | 機能 |
|:-:|:-|
| Init | ACSの初期化処理(InitCallAgent)を呼び出す　|
| JoinTeamsCallAsync  | Teams会議URLを渡して会議への参加を試みます |
| LeaveMeeting  | 接続中の会議から退出します。 |
| Mute  | 会議中のマイクをミュートします。 |
| UnMute  | 会議中のマイクをアンミュートします。 |
| InitCallAgent  | SDKを利用してACSへ接続するためのこのデバイス用ユーザを生成します。  |

ミーティング中に発生する各種イベントの制御
| メソッド名 | 機能 |
|:-:|:-|
| OnCallAgentIncomingCall  | ACSからこのデバイス用ユーザに呼び出しがあった場合に行うイベント処理 |
| OnCallsUpdatedAsync  | ACSからこのデバイス用ユーザが参加する場の呼び出し状態が変更された場合に実行されるイベント処理 |
| OnStateChangedAsync  | Teams会議中の様々な状態が変化（人が増えた、画面が共有された等）が発生した場合に実行されるイベント処理 |
| OnRemoteParticipantsUpdatedAsync  | Teams会議のリモート参加者の更新が発生した場合に行うイベント処理 |
| OnParticipantChangedAsync  | 会議の参加者の更新が発生した場合に実行されるイベント処理（サブルーチン）  |
| OnVideoStreamStateChanged  | 会議中のビデオの状態が変更した場合に実行されるイベント処理 |
| OnOutgoingVideoStreamStateChanged  | デバイスからACSに対してビデオストリーミングの状態を変更した場合に実行されるイベント処理 |
| OnIncomingVideoStreamStateChanged  | リモートの参加者のビデオストリーミングの状態が変更された場合に実行されるイベント処理  |

カメラの取得
| メソッド名 | 機能 |
|:-:|:-|
| GetCameraDevice  | デバイスのWebカメラに使用するデバイスを取得する |

## 6.サンプルの主要コードの解説（関数アプリ)

このgithubプロジェクトには1つのFunctionが実装されています。リクエストを投げるとACSへの接続しACSに接続するクライアントを生成しそのトークンを取得します。実装方法は以下のサイトのものをほぼそのまま使っています。

https://learn.microsoft.com/ja-jp/azure/communication-services/tutorials/trusted-service-tutorial?WT.mc_id=WDIT-MVP-5003104#access-token-generation

node.jsを扱える人であれば特に難しい話はありません。ACSに接続するためのjavascriptライブラリをインポートします。azure/communication-identityをインスタンス化しこの時にACSの接続文字列を設定します。
次にトークン生成に必要な（一時的な）ユーザオブジェクトを作成します。最後にそのユーザオブジェクトに対するアクセストークンを取得します。Function Appではこの処理を行い結果として取得したアクセストークンをレスポンスとして返します。

```javascript:ACSTokenDemo\index.js
const { CommunicationIdentityClient } = require('@azure/communication-identity');


//Azure Functionサービスのアプリケーション設定にキー名:ACS_CONNECTION_STRING,値:ACSの接続文字列を設定
const connectionString = process.env.ACS_CONNECTION_STRING;

module.exports = async function (context, req) {
    let tokenClient = new CommunicationIdentityClient(connectionString);
    const user = await tokenClient.createUser();

    //Teams会議に音声とビデオで参加する場合は第2引数に'voip'を指定
    const userToken = await tokenClient.getToken(user, ["voip"]);
    context.res = {
        body: userToken
    };
}
``` 

### Azure Communication Services Calling Unity SDKの実装部分の解説

基本的な使い方はAzure Communication Services Calling SDKと同じため以下のサイトが参考になります。

https://learn.microsoft.com/ja-jp/azure/communication-services/samples/calling-hero-sample?pivots=platform-windows&WT.mc_id=WDIT-MVP-5003104

ここでは、Unity用のライブラリ[Azure Communication Services Calling Unity SDK]で実装したコードをもとに解説します。

:::note warn
警告
Azure Communication Services Calling SDK自体は現時点ではβ版で提供されています。そのためAPIに仕様が変更されることがあります。[Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/communication-services/?WT.mc_id=WDIT-MVP-5003104)のドキュメントなどを確認する必要があります。また、本運用で利用するかについては十分な検討が必要になります。
:::

[Azure Communication Services Calling Unity SDK]で今回利用したクラスや機能は以下の通りです。

接続に関連するクラス
| クラス | 概要 |
|:-:|:-|
| CallClient  | Calling SDKのエントリーポイントを表すメインクラス  |
| CallAgent  |  CallClientファクトリーメソッドcreateCallAgentによって作成される拡張コールエージェントクラスを表す共通コールエージェント基本クラス。 認証されたユーザーに代わってコールを管理する責任を負う。 |
| Call  | 各ユーザの呼出に関連する情報をもつクラス  |
| JoinCallOptions  | 会議に参加する際のビデオとオーディオのオプションを保持するクラス |
| AcceptCallOptions | 会議参加時の電話を受ける際に渡されるオプション |
| OutgoingVideoOptions  | OutgoingVideoStreams を構成するプロパティを保持する。  |
| TeamsMeetingLinkLocator  | Link Locatorを使用してTeams会議へ参加するための情報を保持  |
| CallTokenCredential  |  ACSの接続に使用する資格情報を保持するクラス |
| CallState  |   呼出しに関する状態<ul><li>None</li><li>Connecting</li><li>Ringing</li><li>Connected</li><li>LocalHold</li><li>RemoteHold</li><li>InLobby</li><li>Disconnecting</li><li>Disconnected</li><li>EarlyMedia</li></ul> |

ミーティング中のカメラや共有に関するクラス
| クラス | 概要 |
|:-:|:-|
| LocalVideoStream  | ローカルのビデオストリーム |
| RemoteVideoStream  |  リモートのビデオストリーム |
| OutgoingVideoStream  | ローカルからACSに向かって送信するビデオストリーム |
| IncomingVideoStream  | 会議内で使用されている参加者のビデオストリームの情報を持つクラス  |
| VideoStreamState  | 会議中に使用しているストリーミングビデオのステータス<ul><li>Available</li><li>Started</li><li>Stopping</li><li>Stopped</li><li>NotAvailable/li></ul>  |
| VideoStreamKind  |   会議中に使用しているストリーミングビデオの種類<ul><li>RemoteIncoming</li><li>RawIncoming</li><li>LocalOutgoing</li><li>VirtualOutgoing</li><li>ScreenShareOutgoing</li></ul> |

接続するクライアントデバイス
| クラス | 概要 |
|:-:|:-|
| DeviceManager  | クライアントデバイスのマイク、スピーカー、カメラ等を管理するクラス  |

#### 初期化

まず最初に、ACSに接続するためのCallClientクラスのインスタンス化とACSに接続用のユーザを生成します。
この実装はcsAzureCommunicationUWPPlugin.InitCallAgentメソッド内で実装します。また、ローカルのデバイス情報を取得するためのDeviceManagerも合わせてインスタンス化します。
手順としては以下の通りです。

1. CallClientのインスタンス化
1. DeviceManagerのインスタンス化
1. CallTokenCredentialにACSのアクセストークンを設定
1. ACS接続用ユーザの情報をCallAgentOptionsで作成（今回はユーザ名を設定）
1. CallClientで先ほどのCallTokenCredentialと、CallAgentOptionsの情報からACS接続用ユーザを生成
1. ACS接続ユーザが呼び出された際に呼び出すイベント処理を設定
1. ACS接続ユーザが受信した他ユーザの呼出情報の受信時に呼び出すイベント処理を設定

```csharp
private async Task InitCallAgent(string userToken, string userName)
{
    var tokenCredential = new CallTokenCredential(userToken);
    _callClient = new CallClient();
    _deviceManager = await _callClient.GetDeviceManager();
    _localVideoStream = new LocalVideoStream[1];

    var callAgentOptions = new CallAgentOptions
    {
        DisplayName = userName
    };
    _callAgent = await _callClient.CreateCallAgent(tokenCredential, callAgentOptions);
    _callAgent.CallsUpdated += OnCallsUpdatedAsync;
    _callAgent.IncomingCallReceived += OnCallAgentIncomingCall;
}
```

上記の実装を行うと、ACSに接続するための一時的なユーザ（有効期限はACSトークン設定時に指定。デフォルトは24h）を作成します。後はCallClientから生成したCallAgentを使ってTeams会議に参加すればOKです。
また、このユーザはいわゆるTeamsアカウントのようなもので、ACSからこのユーザに対してTeams会議内の状況や他ユーザからの呼出しも可能になります。そのためのイベントとしてCallsUpdated(他ユーザ呼出の更新)、IncomingCallReceived（着信呼出の受信）を実装することも可能です。

##### CallsUpdatedイベント
会議に参加しているユーザ呼出の情報をに変更があった場合に発生します。情報としては追加された呼出、削除された呼出等を取得することができます。Teams会議の参加者の増減が発生したときにこのイベントで情報を取得できる形です。

```csharp
private async void 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());

    foreach (var call in args.AddedCalls) addedParticipants.AddRange(call.RemoteParticipants.ToList());

    await OnParticipantChangedAsync(removedParticipants, addedParticipants);
}
```

##### IncomingCallReceivedイベント
他ユーザからの呼出、会議参加許可等クライアントのユーザに対して呼出があった場合に発生します。このイベントでIncomingCallReceivedEventArgsの情報を使って、受信した呼出への対応を行います。サンプルでは呼出を受信した場合、受付けた上でデバイスのカメラを共有する設定を行っています。

```cs
private async void OnCallAgentIncomingCall(object sender, IncomingCallReceivedEventArgs e)
{
    GetCameraDevice();

    var acceptCallOptions = new AcceptCallOptions();
    acceptCallOptions.OutgoingVideoOptions = new OutgoingVideoOptions
    {
        Streams = _localVideoStream
    };
    _call = await e.IncomingCall.AcceptAsync(acceptCallOptions);

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

#### Teamsへの参加

次にTeams会議URLを利用してTeams会議に参加するための処理になります。
こちらの処理はTeams会議に参加するオプションの設定で接続時にHoloLens 2のMixed Reality Captureの映像を共有する形で設定し、Teams会議URLを指定してCallAgentからTeams会議に参加します。Teams会議への参加に成功するとその呼出に関する情報を持つCallクラスが返却されます。
Teams会議中に発生する他のユーザの状態変更や入退室などはこのCallクラスのイベントとして処理することができます。今回の実装は空実装にはなるのですが、たとえば、Teamsに参加しているユーザ一覧をUIとして表示するといったことを行う場合はこのイベントに対する処理を実装します。
**なお、現時点ではHoloLens 2上で他ユーザのWebカメラの映像などをストリーミングする機能はまだ実装されていないようで利用することができません。**

```cs
internal async Task JoinTeamsCallAsync(string teamsMeetingUrl)
{
#if WINDOWS_UWP
    GetCameraDevice();
    var joinCallOptions = new JoinCallOptions();
    joinCallOptions.OutgoingVideoOptions = new OutgoingVideoOptions
    {
        Streams = _localVideoStream
    };

    var teamsMeetingLinkLocator = new TeamsMeetingLinkLocator(teamsMeetingUrl);
    _call = await _callAgent.JoinAsync(teamsMeetingLinkLocator, joinCallOptions);

    if (_call != null)
    {
        _call.RemoteParticipantsUpdated += OnRemoteParticipantsUpdatedAsync;
        _call.StateChanged += OnStateChangedAsync;
    }
#endif
}
```

##### RemoteParticipantsUpdated

Teams会議やグループ会議参加中に入退室するユーザがいた場合に更新情報がこのイベントで通知されます。
イベントの引数にはParticipantsUpdatedEventArgsオブジェクトがあります。このオブジェクトには入室した追加のユーザ、退室したユーザ情報のリストを得ることができます。例えば、UIとして参加者のリストを表示している場合はこのイベントを使い、そのリストの更新を行うといった操作を実施します。それぞれのユーザ情報にはカメラや共有に関するストリーミング情報へのアクセスも含まれています。

##### StateChanged

接続したユーザの呼出し状態が変更された場合にこのイベントが通知されます。
例えば、Teamsの開始に参加する場合、会議の種別によってはLobby待機になる場合があると思います。こういった場合このCallクラスにおけるStateは[InLobby]になります。こういった状態の変更に応じてUIや処理を変更する場合はこのイベントを利用します。

## まとめ
今回はAzure Communication Services Calling Unity SDKを利用してTeams会議にHoloLens 2を参加させるための実装を行いました。SDK自体がまだβ版ではあるのですが、カスタムアプリケーションでチャットや音声、ビデオなどを使ったコミュニケーション手段を簡単に導入できる仕組みはとても便利です。また、Teamsとの連携も容易であることも活用の幅が広がりそうです。