WebRTC　チュートリアル(SkyWay)

## 概要
WebRTC初学者向けに、[SkyWayのチュートリアル](https://webrtc.ecl.ntt.com/documents/javascript-sdk.html#%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB)を参考にチュートリアルを作ってみました。
[SkyWayのチュートリアル](https://webrtc.ecl.ntt.com/documents/javascript-sdk.html#%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB)に細かい解説もありますので、ぜひ一読ください。

## 環境
環境はapacheの環境さえあれば問題ないのですが、再現しやすいように今回はDockerを利用しています。
また、見た目を整えるためにBootstrapも利用しています。

- maxOS BigSur 11.2.3
- Docker version 20.10.6
- docker-compose version 1.29.1
- skyway 4.4.1
- Bootstrap 4.3.1

## 構成
最終的には以下のような構成になります。
SkyWayやBootstrapはCDNを利用していますので、プロジェクト自体はシンプルです。

```
.
├── docker-compose.yml
└── web
    ├── assets
    │   └── js
    │       └── index.js
    └── index.html
```

## 成果物
左側に自分の映像、右側に相手の映像が映るようになります。
(画像では、余計なものが映っているので塗りつぶしています。)

|![スクリーンショット 2021-06-01 9.49.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290840/85fd1c72-3795-77e3-1f6b-f80687d42209.png)|
|:--|

### HTML
```html:index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
              integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
              crossorigin="anonymous">
        <title>WebRTCサンプル</title>
    </head>
    <body>
        <div class="container">
            <div class="row">
                <h1>WebRTCサンプル</h1>
            </div>
            <div class="row">
                <div class="col-6">
                    <div class="form-group">
                        <p class="col-form-label">自分のPeerID: <span id="my-id"></span></p>
                    </div>
                    <video id="my-video" width="100%" autoplay muted playsinline></video>
                </div>

                <div class="col-6">
                    <div class="form-group row">
                        <label class="col-3 col-form-label" for="their-id">相手のPeerID: </label>
                        <div class="col-7">
                            <input id="their-id" class="form-control">
                        </div>
                        <div class="col-2">
                            <button type="button" class="btn btn-primary" id="make-call">発信</button>
                        </div>
                    </div>

                    <video id="their-video" width="100%" autoplay muted playsinline></video>
                </div>
            </div>
        </div>

        <!-- Optional JavaScript -->
        <script src="https://cdn.webrtc.ecl.ntt.com/skyway-4.4.1.js"></script>
        <script src="assets/js/index.js"></script>
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
                integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
                crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
                integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
                crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
                integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
                crossorigin="anonymous"></script>
    </body>
</html>
```

### JS

```js:index.js
let localStream;

// カメラ映像取得
navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(stream => {
        // 成功時にvideo要素にカメラ映像をセットし、再生
        const videoElm = document.getElementById('my-video');
        videoElm.srcObject = stream;
        videoElm.play();
        // 着信時に相手にカメラ映像を返せるように、グローバル変数に保存しておく
        localStream = stream;
    })
    .catch(error => {
        // 失敗時にはエラーログを出力
        console.error('mediaDevice.getUserMedia() error:', error);
    });

//Peer作成
const peer = new Peer({
    key: 'APIキー',
    debug: 3
});

// 自分のPeerID取得
peer.on('open', () => {
    document.getElementById('my-id').textContent = peer.id;
});

// 発信処理
document.getElementById('make-call').onclick = () => {
    const theirID = document.getElementById('their-id').value;
    const mediaConnection = peer.call(theirID, localStream);
    setEventListener(mediaConnection);
};

// イベントリスナを設置する関数
const setEventListener = mediaConnection => {
    mediaConnection.on('stream', stream => {
        // video要素にカメラ映像をセットして再生
        const videoElm = document.getElementById('their-video');
        videoElm.srcObject = stream;
        videoElm.play();
    });
}

//着信処理
peer.on('call', mediaConnection => {
    mediaConnection.answer(localStream);
    setEventListener(mediaConnection);
});
```

## 作成手順
### 1. 準備

#### 1-1. SkyWayアカウントの作成、APIキー取得、利用可能ドメイン追加
[SkyWay](https://webrtc.ecl.ntt.com/)にてアカウントを作成しましょう。

|![スクリーンショット 2021-05-31 16.48.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290840/f8a7f4a5-7381-3e6f-47cb-e0c81d9636b3.png)|
|:--|

|![スクリーンショット 2021-05-31 16.44.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290840/cc2f88e0-4a34-f350-cdff-33b250a148bf.png)|
|:--|

アカウントを作成してログインしたら、「アプリケーション一覧」より「新しくアプリケーションを追加する」をクリックします。

|![スクリーンショット 2021-05-31 16.46.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290840/2bc19fef-8d6d-f4bc-44f0-513740037cc5.png)|
|:--|

登録画面では、「アプリケーション説明文」と「利用可能ドメイン名」を以下のように入力します。

|![スクリーンショット 2021-05-31 16.47.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290840/95af1895-32e7-e5c8-c148-91db4004ec4a.png)|
|:--|


権限は今回はデフォルトのままで、「アプリケーションを作成する」をクリックしましょう。

|![スクリーンショット 2021-05-31 16.47.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290840/5e96cc26-8ad5-a588-ce7c-2a9fc9818bf8.png)|
|:--|

すると以下のようにAPIキーが発行されますのでコピーしておきましょう。

|![スクリーンショット 2021-05-31 16.55.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290840/9750d1ed-dca5-e6d9-ce58-45938d6d8753.png)|
|:--|

#### 1-2. 環境構築
今回はHTMLやJSが動けばいいので、apache用のコンテナのみを用意します。
まずは、docker-compose.ymlを作ります。

```docker-compose.yml
version: "3.9"
services:
  web:
    image: httpd
    volumes:
      - ./web:/usr/local/apache2/htdocs/
    ports:
      - "80:80"
```

そして、確認用のindex.htmlをドキュメントルートに作ります。
BootstrapのCSSとJSをそれぞれ読み込むようにしています。

```html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
              integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
              crossorigin="anonymous">
        <title>WebRTCサンプル</title>
    </head>
    <body>
        <div class="container">
            <div class="row">
                <h1>WebRTCサンプル</h1>
            </div>
        </div>

        <!-- Optional JavaScript -->
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
                integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
                crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
                integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
                crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
                integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
                crossorigin="anonymous"></script>
    </body>
</html>
```

dockerを起動してみましょう。
docker-compose.ymlがあるディレクトリにて以下のコマンドを実行してください。

```sh
docker-compose up -d
```

ちなみに、dockerを停止させる場合は同じディレクトリにて以下のコマンドを実行してください。

```sh
docker-compose down
```

http://localhost にアクセスしてみましょう。
「WebRTCサンプル」と表示されていれば環境構築はOKです。

### 2. カメラ映像、マイク音声の取得
#### 2-1. HTML作成
`index.html`にvideoタグとscriptタグを追加します。

```html:index.html
<div class="container">
    <div class="row">
         <h1>WebRTCサンプル</h1>
    </div>
    <div class="row">
        <div class="col-6">
            <video id="my-video" width="100%" autoplay muted playsinline></video>
       </div>
    </div>
</div>

<!-- Optional JavaScript -->
<script src="assets/js/index.js"></script>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
```

`ハウリングを防ぐために、video要素にはmuted属性を加えています。`

#### 2-2. JS作成
続いては、`index.js`を`assets/js`に作ります。

```js:index.js
let localStream;
// カメラ映像取得
navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(stream => {
        // 成功時にvideo要素にカメラ映像をセットし、再生
        const videoElm = document.getElementById('my-video');
        videoElm.srcObject = stream;
        videoElm.play();
        // 着信時に相手にカメラ映像を返せるように、グローバル変数に保存しておく
        localStream = stream;
    }).catch(error => {
    // 失敗時にはエラーログを出力
    console.error('mediaDevice.getUserMedia() error:', error);
});
```

では、 http://localhost にアクセスしてみましょう。

すると、以下のようにカメラを使用して良いか確認が出ます。

![スクリーンショット 2021-05-31 16.06.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290840/2614aa11-6277-9535-16ef-da59709808cb.png)

**許可**
あなたの顔が映っていればOKです。(笑)

**ブロック**
以下のメッセージがコンソールに出力されます。
`index.js:7 mediaDevices.getUserMedia() error: DOMException: Permission denied`

### 3. Peerの作成

#### 3-1. SDKのインポートとAPIキーの設定
相手との通信を行うための処理を追加していきます。
また、SkyWayのSDKをCDNでインポートします。

SDKをインポートします。
SkyWayのCDNは`index.js`より前に記述してください。


```html:index.html
<!-- Optional JavaScript -->
<script src="https://cdn.webrtc.ecl.ntt.com/skyway-4.4.1.js"></script>
<script src="assets/js/index.js"></script>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
```

SkyWayで作成したアプリケーションのAPIキーを設定してください。

```js:index.js
navigator.mediaDevices.getUserMedia({video: true, audio: true})
    ...
});

//Peer作成
const peer = new Peer({
    key: 'APIキー',
    debug: 3
});
```

#### 3-2. PeerID取得
相手と接続するためのPeerIDを取得します。  

index.htmlに出力用のタグを用意します。

```html:index.html
<div class="col-6">
    <div class="form-group">
        <p class="col-form-label">自分のPeerID: <span id="my-id"></span></p>
    </div>
    <video id="my-video" width="100%" autoplay muted playsinline></video>
</div>
```

```js:index.js
//Peer作成
const peer = new Peer({
    key: 'APIキー',
    debug: 3
});
//PeerID取得
peer.on('open', () => {
    document.getElementById('my-id').textContent = peer.id;
});
```

http://localhost にアクセスしてみてください。  
画面上に16桁のランダム英数字が表示されれば成功です。

#### 3-3. 発信・着信処理
HTMLに以下の要素を追加します。
- 相手のPeerIDを記入するテキストエリア
- 発信ボタン
- 着信時に相手の映像を表示するvideo要素

```html:index.html
<div class="row">
    <div class="col-6">
        <div class="form-group">
            <p class="col-form-label">自分のPeerID: <span id="my-id"></span></p>
        </div>
        <video id="my-video" width="100%" autoplay muted playsinline></video>
    </div>

    <div class="col-6">
        <div class="form-group row">
            <label class="col-3 col-form-label" for="their-id">相手のPeerID: </label>
            <div class="col-7">
                <input id="their-id" class="form-control">
            </div>
            <div class="col-2">
                <button type="button" class="btn btn-primary" id="make-call">発信</button>
            </div>
        </div>

        <video id="their-video" width="100%" autoplay muted playsinline></video>
    </div>
</div>
```

```js:index.js
// 発信処理
document.getElementById('make-call').onclick = () => {
    const theirID = document.getElementById('their-id').value;
    const mediaConnection = peer.call(theirID, localStream);
    setEventListener(mediaConnection);
};

// イベントリスナを設置する関数
const setEventListener = mediaConnection => {
    mediaConnection.on('stream', stream => {
        // video要素にカメラ映像をセットして再生
        const videoElm = document.getElementById('their-video');
        videoElm.srcObject = stream;
        videoElm.play();
    });
}

//着信処理
peer.on('call', mediaConnection => {
    mediaConnection.answer(localStream);
    setEventListener(mediaConnection);
});
```

### 4. 確認
別タブでページを２つ表示して、他方のPeerIDをテキストエリアにコピー&ペーストし、発信ボタンを押下しましょう。
どちらのページにも映像が２つ表示されれば疎通成功です。
これで完成です。