Twilio WebRTCハンズオン 2020版

:::note alert
2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
:::

# はじめに

本資料は、過去に作成した「[Twilio WebRTCハンズオン](https://qiita.com/mobilebiz/items/db133aeb21f880ddfcdd)」の2020年リメイク版になります。

### 対象者

- Twilioが初めての方
- 名前は知っているけど、触ったことがない方
- WebRTCに興味のある方

# カリキュラム（座学）

- STEP0 ハンズオンの準備とTwilio Videoの概要
- STEP1 ブラウザでカメラ、マイクを利用してみよう
- STEP2 ピアツーピアルームのビデオチャットを実装してみよう
- STEP3 グループルーム機能を利用して複数人によるビデオチャットを実装してみよう

## 自己紹介

![katsumi_small_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/86046/1ef9874d-ccb6-d0bb-b56f-eca8d5e6af50.jpeg)

### 髙橋克己（たかはしかつみ）

（株）KDDIウェブコミュニケーションズ
Twilio事業部　エバンジェリスト
Facebook: [katsumi.takahashi](https://www.facebook.com/katsumi.takahashi)
GitHub: [mobilebiz](https://github.com/mobilebiz)

###### katsumi.takahashi@kddi-web.com

## ハンズオン用ソースコード

[こちら](https://github.com/mobilebiz/twilio-video-handson-2020/raw/master/assets/webrtc-handson-2020.zip)からwebrtc-handson-2020.zipをローカルにダウンロードして展開して下さい。

## STEP0 ハンズオンの準備とTwilio Videoの概要

このハンズオンの内容はトライアルアカウントでも利用できます。
トライアルアカウントの作成は、以下のURLの右上にある**サインアップ**より行なえます。
[https://cloudapi.kddi-web.com/](https://cloudapi.kddi-web.com/)

### 手順1. Twilioの管理コンソールにログインします。

- ブラウザで[ログイン画面](https://jp.twilio.com/login/kddi-web)を表示し、ログインをします。

## Programmable Video

- WebRTCをベースに構築された音声・動画・データ通信サービス
- JavaScript SDK / iOS SDK / Android SDKを提供
- アクセストークンを使った認証
- ルームベースでの実装（呼び出し機能はありません）
- 「ピアツーピア（P2P）ルーム」と、SFUベースの「グループルーム」「スモールグループルーム」の3種類が利用可能
- P2Pルームは最大10名まで、スモールグループルームは最大4名まで、グループルームは最大50名までの参加者を収容可能
- TURN/STUNサービスも提供
- 画面共有に対応（ChromeとFirefoxのみ）
- 録画機能に対応（グループルームのみ）
- メディアコーデックには、Opus（音声）、VP8 / H.264（映像）に対応

### <参考> P2P方式とSFU方式

- P2P(Peer to Peer)方式
 - ブラウザ同士が直接映像などをやり取りする
 - もっとも高解像度で自由度が高い
 - WebRTCが前提としている接続方式でリアルタイム通信に向いている
 - 接続する相手が増えてくるとブラウザ側の負担が大きくなる（特にモバイル）
 - Twilio Videoの「ピアツーピアルーム」で利用される方式 
- SFU（Selective Forwarding Unit）方式
 - 映像や音声を中継するサーバーを配置
 - 録画などの付加機能を実施することが可能
 - 上りのコネクションは１本、下りは相手の数分必要
 - ブラウザが増えた場合に、ブラウザ側の負担がP2Pに比べて少ない
 - SFU側では画像の加工などは行わないため、SFU自体の負担は低い
 - Twilio Videoの「スモールグループルーム」「グループルーム」で利用される方式

![上級編SFUとP2P.png](https://qiita-image-store.s3.amazonaws.com/0/86046/b3a6f8d2-e8f1-5c5e-0e44-169352427987.png)

### <参考> TURN/STUN

- NAT越えをするための仕組み
- ICE（Interactive Connectivity Establishment）として説明されることもある
- TURN
 - 直接通信できない場合に中継するサーバー 
- STUN
 - 自分が外部に出たときに、外部から見える情報を取得する
- Twilioは、Network TraversalとしてTURN/STUNを提供
 - TURN/STUNの利用料はVideoの利用料の含まれる
 - TwilioのTURNサーバーは9つのリージョンから最も近い場所を利用可能
 - Twilio Videoは使わずに、TURN/STUNのみの利用も可能（TURNは有料）

![スクリーンショット 2018-07-15 20.37.21.png](https://qiita-image-store.s3.amazonaws.com/0/86046/4ec7bf6f-1805-94fc-1f42-ac5e697396dd.png)

### Twilio Video SDKが対応するブラウザ

|  | CHROME | MS EDGE | FIREFOX | SAFARI |
|:--|:--|:--|:--|:--|
| Android | ✓ | - | ✓ | - |
| iOS | ※ | - | ※ | ✓ |
| Linux | ✓ | - | ✓ | - |
| macOS | ✓ | ✓※※ | ✓ | ✓ |
| Windows | ✓ | ✓※※ | ✓ | - |

※ iOSのChromeとFirefoxでは、WebRTC APIにはアクセスできません。
※※ [Chromium-based Edge](https://www.microsoftedgeinsider.com/)ブラウザにのみ対応しています。 

### ルームの設定を変更する

- Twilioの管理コンソールを開き、スライドメニューから**Programmable Video**を選択します。
- **部屋**（Rooms）の**設定**(Setting）を選択します。
- **Room Topology**を以下のように設定します。

|設定項目|設定値|備考|
|:--|:--|:--|
|ROOM TYPE|Peer-to-peer|ピアツーピアルーム|
|TURN|ENABLED|必要に応じてTURNを利用する|
|MAXIMUM PERTICIPANTS|2|最大参加者数を2に制限|

<img width="1186" alt="スクリーンショット 2018-07-21 00.48.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/6264b568-306c-0439-727e-7c85911befdd.png">

- **Save**ボタンを押します。

### Video APIの認証

- ユーザがルームに入るためには、アクセストークンが必要
- アクセストークンは、以下の情報を使って動的に生成する必要あり（デフォルトの有効期限は1時間）
 - ACCOUNT SID （Twilioのアカウントに紐付いたID）
 - API KEY
 - API SECRET
 - IDENTITY （セッション中にユーザを識別するための値）
- アクセストークンに対して、接続可能なルームを埋め込むことが可能
- API経由で生成する他、管理コンソール上でも生成が可能

## アクセストークンの生成プログラム（API経由）を作る

#### シナリオ

Programmable Videoで利用するアクセストークンを生成するプログラムを作りましょう。
今回は、実際にブラウザから動的に生成できるように、Twilioのサーバーレス環境であるTwilio Functionsを利用します。

### 手順1. APIキーを生成する

- 管理コンソールのスライドメニューから、**Programmable Video**を選択します。
- Programmable Videoメニューの**ツール** > **API Keys**を選択します。
- **新しいAPIキーを作成する**ボタンを押すか、赤い**+**アイコンを押して、新しいAPI Keyを作成します。
- 名前欄に「Video」と入力し、キータイプは「Standard」を選択します。

<img width="701" alt="スクリーンショット 2018-07-16 07.11.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/273c29d9-89d2-d9e5-205b-bd34c42d7eee.png">

- **APIキーを作成する**ボタンを押します。
- 表示される**SID**（SKから始まる文字列）と、**SECRET**に表示されている文字列の両方をメモ帳に保存します。
- **完了しました!**のチェックボックスにチェックを入れて、**終了**ボタンを押します。

![上級編VideoAPIKey.png](https://qiita-image-store.s3.amazonaws.com/0/86046/65185bbc-1ab1-735a-a2ad-7ff1c83d0926.png)

### 手順2. アクセストークン発行用Functionの作成

Programmable Videoを利用するためには、先程作成したAPIキーを使って、アクセストークンを動的に生成する必要があります。
今回は、Twilio Functionsを使って、特定のルームにだけアクセス可能なアクセストークンを生成してみましょう。

- スライドメニューから、**Functions**を選択します。
- **Functions**のサブメニューから**設定**を選択します。

![Functions_Configure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/dbb92084-b8e2-5890-944a-e62ebe06fb5f.png)

- **Enable ACCOUNT_SID and AUTH_TOKEN**のチェックボックスをONにします。

![スクリーンショット 2018-07-03 10.06.47.png](https://qiita-image-store.s3.amazonaws.com/0/86046/5913d0f6-0c75-11fd-8344-3cec6ddfa186.png)

- Environment Variablesの赤いプラスアイコンを２回押して、以下の変数を設定します。
- それぞれのKEY/VALUEを以下のように設定します。

|KEY|VALUE|
|:--|:--|
|TWILIO_VIDEO_KEY|SKから始まるAPIキー|
|TWILIO_VIDEO_SECRET|APIキーとセットで設定されたSECRET|

次に、アクセストークンを生成する際に、ユニークなIDENTITYを生成するためにUUIDモジュールを読み込みます。

- **Dependencies**項目の赤いプラスアイコンを1回押して、以下のモジュールを追加します。

|NAME|VERSION|
|:--|:--|
|uuid|7.0.3|

![Functions_Dependencies.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86046/27680a53-66cd-96cc-544d-5baea2643321.png)

- **Save**ボタンを押して設定を保存します。

次に、実際にアクセストークンを生成するFunctionを作成します。

- **Functions**のサブメニュー**管理**を選択します。
- **Create a Function**ボタンをクリックして、新しいFunctionを生成します。
- テンプレートの一覧から**Blank**を選択して、**Create**ボタンを押します。
- **FUNCTION NAME**に「VideoToken」と入力し、**PATH**には、「/video-token」と入力します。

<img width="601" alt="スクリーンショット 2018-07-16 07.28.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/052266ec-92fb-c305-85ef-e080490e680d.png">

- **ACCESS CONTROL**のチェックは外しておきます。
- **CODE**欄に書かれているコードを以下のコードに置き換えます。

```javascript
const uuid = require('uuid');
exports.handler = function(context, event, callback) {
    const ACCOUNT_SID = context.ACCOUNT_SID;
    const API_KEY = context.TWILIO_VIDEO_KEY;
    const API_SECRET = context.TWILIO_VIDEO_SECRET;
    const IDENTITY = uuid.v4();  // ランダムにクライアント名を生成

    const ROOM_NAME = 'VideoRoom';  // ルーム名は今回は固定

    const AccessToken = Twilio.jwt.AccessToken;
    const VideoGrant = AccessToken.VideoGrant;

    // 参加できるルームをトークンで限定
    const videoGrant = new VideoGrant({
        room: ROOM_NAME
    });

    const accessToken = new AccessToken(
        ACCOUNT_SID,
        API_KEY,
        API_SECRET
    );

    accessToken.addGrant(videoGrant);
    accessToken.identity = IDENTITY;
    callback(null, {
        token: accessToken.toJwt()
    });
};
```

- **Save**ボタンを押して、デプロイされるのを待ちます。
- デプロイが完了したら、**PATH**欄のURLをコピーして、別のブラウザタブで実行してみます。
- JSON形式で**token**（長い文字列）が表示されたら、トークンの生成は成功です。

```json
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImN0eSI6InR3aWxpby1mcGE7dj0xIn0.eyJqdGkiOiJTS2EwYjU2NmZiYmY0NDZiYTE1NDc3ZjIyMDJiOWQ3NDAzLTE1ODc3NzUzNjMiLCJncmFudHMiOnsiaWRlbnRpdHkiOiJiNWYxNjI5OC00N2M0LTQzYzktYjEwNS0xNTIyZTk4NmQ1YWMiLCJ2aWRlbyI6eyJyb29tIjoiVmlkZW9Sb29tIn19LCJpYXQiOjE1ODc3NzUzNjMsImV4cCI6MTU4Nzc3ODk2MywiaXNzIjoiU0thMGI1NjZmYmJmNDQ2YmExNTQ3N2YyMjAyYjlkNzQwMyIsInN1YiI6IkFDZmQ0Mzk1YzljZGNkMzczMWI4ZjZhYTkzNjY1NmZlYjgifQ.nVEn0EKJzD-Gri2L4bkTCslM4hGrIhlsdlFWw9moIyc"}
```

# Twilio Videoの実装

ここからは、実際にHTMLとJavaScriptを使って、Twilio VideoのJavaScript SDKを使った実装をしていきます。

### 手順1. HTMLとJSを設定する

- [こちらのURL](https://github.com/mobilebiz/twilio-video-handson-2020/raw/master/assets/webrtc-handson-2020.zip)からZIPファイルをダウンロードします。
- ダウンロードしたZIPファイルを解凍し、２つのファイルがあることを確認します。
 - video.html
 - video.js
- 管理コンソールの**Assets**を選択します。
- **+**アイコンを押して、video.htmlを選択します。**MAKE PRIVATE**のチェックは外しておきます。

<img width="541" alt="スクリーンショット 2018-07-16 12.09.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/dfcd2e73-7b4c-39b3-d07a-b94e31a5f044.png">

- **Upload**ボタンを押して、アップロードを完了します（デプロイが完了するまで待ちます）。
- 同様に、video.jsもアップロードします。

## 解説

`video.js` 9〜14行目付近

```javascript
// プレビュー画面の表示
navigator.mediaDevices.getUserMedia({video: true, audio: true})
.then(stream => {
    document.getElementById("myStream").srcObject = stream;
    localStream = stream;
});    
```

- getUserMedia()を利用してカメラとマイクを取得する
 - Promiseによる非同期処理を行うAPI
- VideoとAudioの選択

```{video: true, audio: true}```

- 最低でも640x480以上、出来たら1280x720が良いけどだめならよしなにやってください

```{ audio: true, video: { width: {min: 640, ideal: 1280}, height: {min: 480, ideal: 720} } }```

- 絶対に640x480がいいです…それ以外だったら動作しなくてもOK
 - min,maxの代わりに exact: 640 と指定することも可能

```{ audio: true, video: { width: {min: 640, max: 640}, height: {min: 480, max: 480} } }```

- フレームレートの設定（一例/Chrome限定）

```{ audio: true, video: { frameRate: { min: 10, max: 15 } } }```

- 初回は許可を求めるダイアログが出てくる
- 複数のカメラやマイクが接続されている場合は、適切なものを選択する必要あり

`video.html` 16行目

```
<script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
```

- Twilio Video JavaScript SDKの読み込み

`video.js` 22行目

```javascript
// アクセストークンを取得
axios.get(`https://${TWILIO_DOMAIN}/video-token`)
.then(async body => {
    const token = body.data.token;
```

- アクセストークンをAxios経由で取得する
- 取得したアクセストークンはJSON形式で返ってくる

`video.js` 27行目

```javascript
Video.connect(token, { name: ROOM_NAME })
.then(room => {
    console.log(`Connected to Room ${room.name}`);
    videoRoom = room;

    room.participants.forEach(participantConnected);
    room.on('participantConnected', participantConnected);

    room.on('participantDisconnected', participantDisconnected);
    room.once('disconnected', error => room.participants.forEach(participantDisconnected));
```

- アクセストークンを使って、ルームに接続します。
- オプションで、下記を渡すことが可能です。
 - オーディオおよびビデオのオプション: 有効時には、Roomへの接続すると直ちにローカルのカメラおよびマイクからのオーディオおよびビデオのトラックが作成および公開されます。
 - ローカルオーディオまたはビデオトラック: Roomへの接続に先立って作成済みのローカルメディアの他の参加者との共有を開始します。
 - ルーム名: 参加したいルームの名前を動的に指定できます。 （メモ: ルーム名をアクセストークンにエンコードできます。 こうすることでユーザーはトークンで指定されたRoomにのみ接続できます。）
 - ICEトランスポートポリシー: テスト用途として、強制的にTURNリレー経由で通話を行えるようになります。
 - デバッグレベル: デバッグ用のログレベルです。
 - オプションの詳細については、[こちら](https://media.twiliocdn.com/sdk/js/video/releases/1.11.1/docs/global.html#ConnectOptions)を御覧ください。
- ルームの名前には、参加したいルームを指定します。 その名前のルームがまだ存在していない場合、接続に先立って作成されます。 ルームがすでにアクティブな場合はルームに接続され、同じルームに接続されている他の参加者からの通知を受信します。ルーム名はアカウント内で一意でなければなりません。
- ルームに参加すると、roomオブジェクトの中に、perticipantマップリストが返りますので、それを使ってすでに参加しているユーザの情報を取得できます。
- ルームへの接続が完了するとルームのイベントが取得できるようになるので、イベントハンドラを指定します。
 - `participantConnected`イベント： 自分以外の参加者がルームに参加したとき
 - `participantDisconnected`イベント: 自分以外の参加者がルームから退出したとき
 - `disconnected`イベント: 接続が切れてしまったとき

## STEP3 グループルーム機能を利用して複数人によるビデオチャットを実装してみよう

STEP2では、ピアツーピアルームを利用して、最大２名までのビデオ会議を実装しましたが、このステップでは、グループルームを利用して、最大５０名までのビデオ会議に変更していきたいと思います。
しかし実際にはコードを変更する必要はありません。
管理コンソールの設定を変更するだけでグループルームになります。

### 手順1. 設定の変更

- Twilioの管理コンソールを開き、スライドメニューから**Programmable Video**を選択します。
- **部屋**（Rooms）の**設定**(Setting）を選択します。
- **Room Topology**を以下のように設定します。

|設定項目|設定値|備考|
|:--|:--|:--|
|ROOM TYPE|Group|グループルーム|
|VIDEO CODEC|VP8 & H.264|VP8が使えなければH.264|
|RECORDING|DISABLED|録画は無効|
|MAXIMUM PERTICIPANTS|50|最大参加者数を50に設定|

<img width="1186" alt="スクリーンショット 2018-07-21 16.56.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/05f876aa-ca41-e321-9d0b-bf7693322729.png">

- **Save**ボタンを押します。

### 手順２. テスト

- ブラウザを強制リロードして、３人以上でもビデオ会議ができることを確認します。

## 参考情報

今まで紹介してこなかったTwilio Videoの機能について、簡単に説明します。

- データトラック
- 画面共有
- 録画
- 料金

### データチャネル

- 音声や動画以外に、データ通信用のトラックを利用することできます。
- チャットのような文字列のやり取りや、マウスの動きを送信して共同編集が可能です。
- データトラック用に、[DataTrack API](https://jp.twilio.com/docs/video/javascript-v1-using-the-datatrack-api)が用意されています。
- ネットワークの状態によって、パケットのロスが発生することがあります。これを調整するためのパラメータとして、以下の２つを用意しています。
 - maxPacketLifeTime: パケットの生存時間（この時間内であれば再送の対象とする。単位はms）
 - maxRetransmits: パケットの再送回数（この回数を越えても送信できない場合はドロップ）  

### 画面共有

- ChromeかFirefoxを利用する場合、画面共有が可能です。
- Chromeの場合は、Chrome Extensionを利用します。
- Firefoxはバージョン52以上で利用可能です。
- Chromeを使った画面共有のついては、[こちら](https://www.twilio.com/blog/2017/10/screen-capture-in-google-chrome.html)の記事をご参照ください。
- Firefoxを使った画面共有については、[こちら](https://www.twilio.com/blog/2017/10/screen-capture-in-firefox.html)の記事をご参照ください。

![twilioVideo-screen-share2.gif](https://qiita-image-store.s3.amazonaws.com/0/86046/c1336273-5674-0605-f72e-1d0dcc3d0e2f.gif)

### 録画（Video Recording）

- 録画をするためには、グループルームの初期値で録画を有効にするか、REST APIでルームの作成時に録画を有効にします。
- 録画ファイルは、参加者ごとに、音声(mkaファイル）と動画（mkvファイル）の２つが作成されます。
- メディアファイルのダウンロードは、管理サイトから手動で行うか、以下のURLを使ってダウンロードすることができます。
 - ```https://video.twilio.com/v1/Recordings/${recordingSid}/Media```
 - 上記URLにアクセスするためには、APIキーとSECRETによるBASIC認証が必要です。
- メディアファイルをAWS S3に直接保存させることも可能です。
- Twilio側にメディアファイルを保存する場合は、ユーザの秘密鍵で暗号化することができます。
- これらの設定は、管理コンソール > Programmable Video > 録音 > 設定で行います。
- [Compositions API](https://jp.twilio.com/docs/video/api/compositions-resource)を利用することで、複数の参加者のメディアデータをまとめて、１つのmp4もしくはwebmに加工することができます。

### Programmable Videoの料金（ピアツーピア）

ピアツーピアを利用する場合、接続するユーザによって以下の料金が掛かります。

- 1参加者 1分あたりの接続料金： 0.22917円

### Programmable Videoの料金（グループルーム/スモールグループルーム）

Group Room を利用する場合、接続するユーザによって以下の料金が掛かります。

- 1参加者 1分あたりの接続料金
 - スモールグループルーム: 0.61111円
 - グループルーム: 1.52778円

録画機能を利用する場合には以下の追加料金が発生します。

- 1参加者 1分あたりの録画料金: 1.52778円
- 録画データ保存料金（GB/日）： 0.25514円
- 録画データダウンロード料金(GB): 15.27777円

コンポジット機能を利用する場合には以下の追加料金が発生します。

- コンポジット後の動画ファイル 1分あたりの料金: 1.52778円

データトラック機能を利用する場合には以下の追加料金が発生します。
※データトラックの最大送信メッセージサイズは16KBです。

- 1参加者がメッセージを送信する毎に以下の料金が発生
 - グループルーム: 1,527.7777円/1,000,000メッセージ
 - スモールグループルーム: 611.1111円/1,000,000メッセージ

例：スモールグループで、一人の参加者が1秒間に10メッセージを送信し、10分継続した場合

- メッセージ数: 10 x 60 x 10 = 6,000メッセージ
- 利用料: 611.1111円 x (6,000) / 1,000,000 = 3.6666円