SkyWay初心者からステップアップしよう

# はじめに

- この資料は[SkyWay UG Tokyo #3](https://skyway.connpass.com/event/83052/)の発表資料です。

- 資料URL: https://goo.gl/V7YtWJ

---

# 自己紹介

- なかゆうすけ（@yusuke84）
- 仕事:
  - [WebRTC Platform SkyWay](https://webrtc.ecl.ntt.com)
      - Technical solutions
      - Technical support
      - Developer relations
- コミュニティ
    - WebRTC Meetup Tokyo 主催
    - WebRTC Beginners Tokyo 主催

---

# この資料は何？

- サポート等で問い合わせが多いSkyWayのティップスを厳選して紹介します

---

# 達成したいゴール

- SkyWayを利用するエンジニアの**困った:tired_face:**を、1つでも多く解決し **ハッピー:heart_eyes:** になってもらう
- SkyWay初心者からSkyWay中級者へステップアップしてもらう

---

# 今日お話する内容

- SkyWaySDKの通信モデル
- SkyWayの通信要件
- SkyWayを利用したWebRTC通信のコネクティビティ（メディアコネクションの場合）を確認する
- SkyWayを利用する際の通信帯域
- SkyWayの動作確認済みブラウザとOS
- SkyWayを配信に利用する場合のティップス

---

# 本題に入る前に…お知らせ

---

## ダッシュボードで利用量の閲覧ができるようになりました

<img width="551" alt="スクリーンショット 2018-04-19 6.45.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/6651/9947a74b-6812-183b-d85d-aa9693afd5b7.png">

- APIキー毎に直近2ヶ月分閲覧可能
- 更新頻度は1日に数回程度
- 利用量は1MB単位で切り上げ
- Enterprise Edition利用のお客様は今まで通り「料金・請求」の明細画面からも確認可能

---

## Enterprise EditonへのAPIキー移行ができるようになりました

<img width="881" alt="スクリーンショット 2018-04-19 7.00.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/6651/2f611451-7752-8b90-8676-18eac7a026eb.png">

- Community EditionからEnterprise Editionへ移行可能
    - 逆はできない
- サービス停止無しで移行可能
- 利用量表示のデータはCommunity Editionでの実績が反映されますが、課金は移行完了した時点から開始

---

# SkyWaySDKの通信モデル

SkyWayを利用するにあたり、知っておくべき概念です。

---

## Peer

- SkyWayを利用して何らかのサービスを作る場合、最初にPeer(ピア)というインスタンスを作成する
- Peerは、SkyWayのシグナリングサーバや、他のクライアントとの接続を管理するエージェント

---

## PeerID

- Peerを作成(new Peer)すると、作成したPeerがシグナリングサーバへ接続する
- 接続が成功すると、Peer.openイベントの発火と共に、SkyWayサーバ側がランダムに作成するPeer IDという値を取得できる
- Peer IDはPeerを一意に識別するためのID
- 接続が失敗した場合はPeer.errorイベントが発火する

---

## 通信モデルその１「電話モデル」


- 電話で通話する場合と同様に1:1で通信するモデル
- 通信を開始する場合には、相手のPeerIDを何らかの手段で知る必要がある

---

### 音声・映像（メディアチャンネル）

1. 相PeerIDを指定して相手に発信（peer.call）
2. callイベントが着信側で発火
3. そのcall要求に対してanswer()を呼ぶことで通信確立
4. 音声・映像はStreamイベントの発火とともに取得できる

---

### データの場合（データチャンネル）

1. peer.connect()で相手に接続要求
2. 接続が確立するとconnectionイベントが発火

* メディアチャンネルと異なりanswer()を明示的に呼ぶ必要がない

---

## 通信モデルその２「ルームモデル」

- 同一ルームに存在するすべてのPeerで会話するモデル
- ルームの名前空間はAPIキー毎に独立している
- APIキーが違う場合は同一ルーム名でも別ルームと扱われて相互通信は出来ない
- ルームの実現方式にはフルメッシュとSFUの2種類がある
    - 両者の違いはの詳細は[こちら](https://webrtc.ecl.ntt.com/sfu.html)
    - 同一APIキー上にフルメッシュとSFUで方式で同時に同一ルーム名のルームを作ることは出来ない

---

1. ルーム名を指定してルームに参加（peer.joinRoom）
2. 他の参加者からメディアを受信した場合はRoom.streamイベントが発火する
3. Room.Close()でルームからの退出する

- ルームの削除は参加者が全員退出したら行われる
    - 明示的に消す方法はなし

---

## メディアストリームの扱い

- SkyWayのSDKではメディアストリームは入力時に渡して出力時に取得するのみが原則となる
  - iOS/Android SDKはメディア作成APIを搭載しているが入力はプログラム側から行う

![sdk-mediastream-processing.png](https://qiita-image-store.s3.amazonaws.com/0/6651/76fdf8e0-6e76-08d7-4a6d-f80343174be9.png)

---

# SkyWayの通信要件

---

## その１（管理系）

- ディスパッチャ
    - TCP443(HTTPS Protocol)
    - dispatcher.webrtc.ecl.ntt.com
- シグナリングサーバ
    - TCP443（プロトコルはSocket.io依存）
    - *.webrtc.ecl.ntt.com
    - シグナリングサーバは複数台構成のためワイルドカードで記載

---

## その２（WebRTC関連）

- P2P通信
    - ポート番号はUDPハイポートレンジを利用し毎回ランダムに決定
    - IPアドレスもクライアントアドレスは環境に合わせて変わる
- STUN通信
    - UDP3478(STUN Protocol)
    - stun.webrtc.ecl.ntt.com
    - 役割
        - 自分自身のIPアドレスとポート番号を取得
        - STUNサーバへアクセスが出来ずP2P通信も出来ない場合は、TURNサーバで代用

---

- TURN通信
    - TCP443,UDP443（低コストでつながる方を勝手に選択）
    - turn.webrtc.ecl.ntt.com
    - 役割
        - P2P通信できない場合の代替手段

---

### TURN通信は3パターン

- AさんのみTURN（プロトコル）を利用する場合

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/a2271f39-db24-e5f0-a68b-ff490f5f2a22.png)

---

- AさんとBさん両方が同一のTURN（プロトコル）を利用する場合

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/f5866da5-530e-aa44-2968-0f430a49b60b.png)

---

- AさんとBさん両方が別々のTURN（プロトコル）を利用する場合

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/4d4ae895-9886-7eac-9b80-aeaa9f0ac68e.png)

---

### TURNサーバは極力使わないようにネットワーク設計する

- UDPによるハイポートの通信を規制しない
- STUNサーバへのアクセスを許可する
- エンタープライズユースの場合はNATタイプに気を使う

---

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/5b418c22-949a-5f4a-2590-886633591fce.png)

---

### 絶対TURNサーバが必要な場合

- UDP通信が禁止
    - TURNプロトコル（TCP）を利用した通信をするしかない
- 通信相手をACL等で制限する必要がある
    - 通信相手を固定する場合はTURNサーバのアドレスで固定するしかない
- IPv6アドレスを持ったPeerがSFUやIPv４アドレスのPeerと通信する場合
    - NAT64/DNS64環境でTURNにアクセスできる場合
    - 詳しくは [SkyWayのIPv6環境での動作について
](https://support.skyway.io/hc/ja/articles/115013721688-SkyWay%E3%81%AEIPv6%E7%92%B0%E5%A2%83%E3%81%A7%E3%81%AE%E5%8B%95%E4%BD%9C%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)

---

# SkyWayを利用したWebRTC通信のコネクティビティ（メディアコネクションの場合）を確認する

---

## 予備知識〜SkyWay SDKのオブジェクト構造〜

### 全SDK共通

- Peer Class/Object
    - シグナリングサーバとの接続を管理する
- MediaConnection Class/Object
    - メディアコネクション（映像・音声のP2P通信）を管理する
- DataConnection Class/Object
    - データコネクション（データのP2P通信）を管理する
- SFURoom Class/Object
    - SFUのRoom接続を管理する
- MeshRoom Class/Object
    - MeshのRoom接続を管理する

---

### iOS/Android SDKのみ

- iOS
    - SKWNavigator
        - ブラウザのgetUserMedia APIに相当
    - SKWMediaStream
        - メディアストリーム
    - SKWVideo
        - 映像を表示するレンダラービューオブジェクト

- Android
    - Navigator
        - ブラウザのgetUserMedia APIに相当
    - MediaStream
        - メディアストリーム
    - Canvas
        - 映像を表示するレンダラービューオブジェクト

---

## シグナリングサーバとの接続が確立できたことを把握

- 全SDK共通
    - new Peer実行
    - Peer Class/Objectの`open`イベントが発火する
    - 以降の処理はすべてこのイベントが発火してから実施する

---

### （参考）new Peerを実行すると内部的には何が行われるのか？

1. ディスパッチャーサーバから接続対象のシグナリングサーバを取得
    - シグナリングサーバは複数台で冗長構成を取っているため接続対象のサーバをディスパッチャーサーバに毎回問い合わせる
    - `signaling-xxxxxxx.webrtc.ecl.ntt.com` 等のURLを取得
2. シグナリングサーバへの接続
    - 認証サーバ連携している場合は認証情報の検証
    - PeerIDの生成
    - TURNサーバ接続用の認証情報取得
3. シグナリングサーバとは常時接続された状態を維持
    - 定期的にping/pongを実施
4. `open`イベントが発火

---

## メディア通信が開始されたことを把握

- JS SDK
    - MediaConnectionオブジェクトまたはRoomオブジェクトの`stream`イベント

- iOS/Android SDK
    - P2Pの場合
        - `SKWMediaConnection on:SKW_MEDIACONNECTION_EVENT_STREAM`
        - `MediaConnection.MediaEventEnum.STREAM`
    - Roomの場合
        - `SKWRoom on:SKW_ROOM_EVENT_STREAM`
        - `Room.RoomEventEnum.STEAM`

これらのイベントが発火すると実際にメディアデータが送受信される

---

## 通信状況をモニターする

- iOS/Android SDK
    - 機能としては用意されてないが、Webアプリと相互接続する場合、ブラウザ側で確認可能
- JS SDK
    - ChromeまたはFirefoxのWebRTC開発者向けツールでモニタリング
    - 詳しくは次のページで

---

### Chrome WebRTC Internalsの使い方

- 情報量が多いのでChromeがオススメ
- WebRTCで通話中に別のタブで開発者向けツールにアクセス
    - `chrome://webrtc-internals/` 

---

#### WebRTC Internals　その1

![internals1.png](https://qiita-image-store.s3.amazonaws.com/0/6651/bc8af44c-ed9a-efe8-246b-4d486f126be7.png)

- WebRTC通信を行っているブラウザのタブ毎にタブが作られるので、該当部分を選択
  - TimeとEventはWebRTCの接続シーケンスがログとして出ている
  - リアルタイムで更新される

---

- ICE Connection Stateをチェック

![internals2.png](https://qiita-image-store.s3.amazonaws.com/0/6651/e4ea4847-24b4-84bf-8df4-c0fa121ea8a0.png)

- ICEとはInteractive Connectivity Establishmentの略で、WebRTCによる通信経路定めるためのプロトコル

---

- ICEのステータス遷移

<img width="625" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f363635312f61393966396635362d646166322d326132382d383465352d3336376236326230353436622e706e67.png" src="https://qiita-image-store.s3.amazonaws.com/0/6651/1e85a82e-6e3e-81e5-fe5b-98fb607ded9d.png">

|ステータス|内容|
|:--|:--|
|connnected|通信可能な接続を発見した<BR>でもまだ他の経路情報も探索中|
|completed|すべての経路を探査し終わり通信に利用する経路が確定した|

- completed以外のステータスで止まっている場合は通信経路に問題がある
- ICE Stateの詳細は [WebRTC 1.0: Real-time Communication Between Browsers](https://www.w3.org/TR/webrtc/#dom-rtcicetransportstate) を確認

---

#### WebRTC Internals　その2

![internals3.png](https://qiita-image-store.s3.amazonaws.com/0/6651/ca7cbc88-fb8d-88d8-3efa-b1f1439a9425.png)

- メディアの通信が行われている場合は、**必ず一つだけ太字になっている部分**がある
    - 無ければ接続がうまく行っていない

---

- チェックする箇所は以下の通り

|項目|意味|
|:--|:--|
|bytesReceived|受信バイト数<BR>カウンタがリアルタイムに更新される|
|bytesSent|送信バイト数<BR>カウンタがリアルタイムに更新される|
|googLocalAddress|自分自身の通信に利用しているIPアドレスとポート番号<BR>TURNサーバを利用している場合はTURNサーバのアドレスとなる|
|googLocalCandidateType|通信経路の種類を識別<BR>`relay`はTURNサーバ利用|
|googRemoteAddress|相手の通信に利用しているIPアドレスとポート番号<BR>TURNサーバを利用している場合はTURNサーバのアドレスとなる|
|googRemoteCandidateType|通信経路の種類を識別<BR>`relay`はTURNサーバ利用|

---

#### WebRTC Internals　その3

![internals4.png](https://qiita-image-store.s3.amazonaws.com/0/6651/685d8be8-e025-c1bd-359d-eb7e7b30618a.png)

- 通信するメディアのAnalyticsが表示される
    - 大まかな帯域等を把握することが出来る
    - 受信側、送信側でそれぞれ確認できる

---

# SkyWayを利用する際の通信帯域

---

## 通信帯域はあくまで目安です

|用途|帯域|
|:--|:--|
|1:1音声通話|40kbps|
|1:1ビデオ通話（SD）|500kbps|
|1:1ビデオ通話（HD）|1.5Mbps|
|4人ビデオ会議（SD）|1.5Mbps|

出展: [SkyWay利用モデル](https://webrtc.ecl.ntt.com/pricing.html#%E5%88%A9%E7%94%A8%E3%83%A2%E3%83%87%E3%83%AB)

---

##　実際どうなの？

- 計測条件
    - Chrome M66
    - コーデック：VP8
    - フレームレート：指定なし
    - MacBookPro 13inch 2016モデル
    - Logicool BRIO
- 計測方法
    - カメラの前に座って会議をやっている前提（動き少なめ）
    - 同一PC内でP2Pビデオチャットを実施しwebrtc-internalsで確認

---

|条件|帯域|
|:--|:--|
|320x240(QVGA)|![320_240_30.png](https://qiita-image-store.s3.amazonaws.com/0/6651/dea2461a-92c4-0d8f-30f1-35dd4603c2a5.png)<BR>450kbps前後|
|640x480(VGA)|![640_480_30.png](https://qiita-image-store.s3.amazonaws.com/0/6651/0114fc07-16d5-f6e8-7a5c-25ece0227e18.png)<BR>1.6Mbps前後|
|720x480(SD)|![720_480_30.png](https://qiita-image-store.s3.amazonaws.com/0/6651/415f510c-b0c8-6124-eb29-88d6507ce9aa.png)<BR>1.6Mbps前後|
|1024x768(XGA)|![1024_768_30.png](https://qiita-image-store.s3.amazonaws.com/0/6651/f0f6ba5d-77f8-ce85-8564-3080d95319d7.png)<BR>1.6Mbps前後|
|1280x720(HD)|![1280_720_30.png](https://qiita-image-store.s3.amazonaws.com/0/6651/76b7a16d-308d-9a43-4c63-97361e5e2d23.png)<BR>1.6Mbps前後|
|1920x1080(フルHD)|![1920_1080_30.png](https://qiita-image-store.s3.amazonaws.com/0/6651/27c86b25-28d0-f7cb-b528-f6dd88e1fa14.png)<BR>1.6Mbps前後|
|3840x2160(4K UHDTV)|![3840_2160_30.png](https://qiita-image-store.s3.amazonaws.com/0/6651/ec7daa65-078c-4c7b-ac99-51cd196ccabd.png)<BR>2Mbps前後|

- 4Kについてはマシンの性能の問題も有り安定していない可能性がある
- 解像度参考: [モニタ解像度 図解チャート＆一覧](https://www.quel.jp/etc/monitor-size/)


---

- 帯域を考える上でのポイント
    - アプリの作りである程度コントロール可能
    - 利用シーンによって変動幅が異なる
        - Web会議用は比較的変動が少ない
        - スマホやスマートグラス等動きがある場合はかなり変動幅が大きい

---

# SkyWayの動作確認済みブラウザとOS

- ChromeとFirefoxに正式対応
    - 安定版の最新2バージョンに対応
        - 例： 2018.04.19現在 最新の安定版は Version66　なので、動作確認済みブラウザは66と65
    - ブラウザは約6週間でアップデートされるため、JS SDKのアップデートへの追従はお早めに

- Edge（一部機能のみ対応）
    - P2PとRoom（Meshモード）のみ利用可能
    - Room（SFUモード）への対応予定：**無し**
        - EdgeがMultiStream機能に対応していないから

- Safari（一部機能のみ対応）
    - P2PとRoom（Meshモード）のみ利用可能
    - Room（SFUモード）への対応予定：**有り**
        - 現在のSFUはビデオコーデックがVP8固定のため利用不可
        - 今後コーデックの切り替え機能をリリース予定

---

- iOS
    - iOS 8+

- Android
    - Android 4.2+ (API Level 17+)

- EPSON MOVERIO BT-300/350
    - Android SDKで動作

---

# SkyWayを配信に利用する場合のティップス

---

## 配信に利用できる機能

---

### 受信のみモード

- **自身のMediaStreamをオプションに渡さずに**、Roomへjoinすることで受信のみモードになる

```js
// 例
const room = peer.joinRoom("roomName", {
  mode: 'mesh'
});
```
- この時、MediaStreamをオプションに渡して参加したメンバがいる場合、そのメンバから受信のみモードメンバへ片方向通信が実現できる

---

### videoReceiveEnabled/audiReceiveEnabled

- 映像と音声を単独で受信のみにすることが出来る

```js
// 例：音声だけ受信したいしたい
const room = peer.joinRoom("roomName", {
  mode: 'mesh',
  audioReceiveEnabled: true
});
```

---

- 映像と音声を単独で受信のみにすることが出来る

```js
// 例：音声は送受信、映像は受信だけしたい
const room = peer.joinRoom("roomName", {
  mode: 'mesh',
  stream: audioOnlyStream,
  videoReceiveEnabled: true
});
```

---

## おことわり…

- Room機能の**SFUモード**で受信のみモードを利用する場合はワークアラウンドが必要（2018.04.19現在）
- 参考: [SkyWayサポートコミュニティ:受信のみモードを併用した多人数でのSFUの利用](https://support.skyway.io/hc/ja/community/posts/115000612268)
- **SFUの不具合が解消されたため、このワークアラウンドは不要となりました(2019.04.09追記)**

---

- replaceStreamで、受信のみモードから通常モード（MediaStreamを送信するモード）への切り替えは**動作保障対象外**（2018.04.19現在）
    - 送信していたMediaStreamを単純に差し替える用途では問題なく利用可能

---

### それぞれの機能の利用可否まとめ

|機能|JS SDK|iOS SDK|Android SDK|
|:--|:--|:--|:--|
|受信のみモード|:white_check_mark:|:white_check_mark:|:white_check_mark:|
|videoReceiveEnabled/audiReceiveEnabled|:white_check_mark:|:x:|:x:|

---

|機能|meshモード|sfuモード|
|:--|:--|:--|
|受信のみモード|:white_check_mark:|:white_check_mark:<sup> ※1</sup>|
|videoReceiveEnabled/audiReceiveEnabled|:white_check_mark:|:x:|

- ※1:~~ワークアラウンドが必要~~(不要になりました)

---

配信等のユースケースで活用する場合、SFUモードが必要になることが多いと思います。以後、**受信のみモード☓SFUモード**、**replaceStream☓SFUモード**の組み合わせで利用する際のワークアラウンドにフォーカスします。

---

### 受信のみモード☓SFUモードのワークアラウンド

**2019.04.09 追記**
**SFUの不具合が解消されたため、このワークアラウンドは不要となりました。**

---

受信のみモードをSFUモードで利用する場合、送信される映像が受信のみモードのユーザ上で再生されない問題がある。この問題は、SkyWayのSFUサーバから映像の再生に必要なキーフレーム<sup>[※参考情報（外部サイト）](http://aviutl.info/keyframe/)</sup>が、受信のみモードのユーザに届かないため発生している。

次に示す方法で回避可能。また、**送信されるのが音声のみの場合は問題なく利用できる**。

---

[SkyWayサポートコミュニティ:受信のみモードを併用した多人数でのSFUの利用](https://support.skyway.io/hc/ja/community/posts/115000612268)で開発者の方が記載していただいている、ダミーのjoinRoomを実行する方法が一番良い。

```js
const skyway = new Peer({key:'APIKEY'});
const room = skyway.joinRoom('roomName',{mode: 'sfu'});
room.on('open',async (peerId) => {
    try {
        await dummyRoomJoin();
    } catch (err){
        console.error(err);
    }
});

function dummyRoomJoin(){
    return new Promise((resolve, reject) => {
        const dummyPeer = new Peer({key:'APIKEY'});
        dummyPeer.on('open',() => {
            const dummyRoom = dummyPeer.joinRoom('roomName',{mode: 'sfu'});
            dummyRoom.on('open',() => {
                dummyRoom.close();
            });
            dummyRoom.on('close',() => {
                dummyPeer.destroy();
                resolve();
            });
            dummyRoom.on('error',(err) => {
                reject(err);
            });
        });
    });
};
```

この他に、canvasで作成した1px X 1pxのダミーストリームを送信する方法もあるが、受信のみモードのユーザからSFUへのUpstreamのメディアコネクションが生成されるため、Roomに参加するユーザ全体の負荷が高くなる。おすすめしない。

---

### replaceStream☓SFUモード

SFUモードでreplaceStreamを利用し、受信のみモードから通常モードへの切り替えを行うのは動作対象外。つまり、以下のようなユースケースについては、現状正常に動作しない。

1. **全員が受信のみモードでRoom参加**
2. あるユーザがreplaceStreamを利用して映像・音声の送信者になる
3. 他の参加者は受信のみモードで視聴する

- この場合、3のタイミングで、**他の参加者に対して`streamイベント`が発火しない**
- JS SDKに関しては、誰かが一人でも通常モードで参加していれば、受信のみモードから通常モードへの切替時は可能

---

# まとめ

- SkyWaySDKの通信モデル
- SkyWayの通信要件
- SkyWayを利用したWebRTC通信のコネクティビティ（メディアコネクションの場合）を確認する
- SkyWayを利用する際の通信帯域
- SkyWayの動作確認済みブラウザとOS
- SkyWayを配信に利用する場合のティップス