スマホとPCでビデオ通話できるWebページを作ってみた

ブラウザ上でスマホ<->PC間のビデオ通話を試したので、過程等々を記録します。
WebRTCに触れるのは初めてでしたが、SkyWay公式のサンプルコードが最高で、ほとんど手を加えることなく実現できました。

同じ初学者の方が全体感を掴む助けになれば幸いです。


## 技術要素

* WebRTC: ブラウザ上でリアルタイム通信する技術
* SkyWay: WebRTCを簡単に使うためのプラットフォーム
* skyway.js: WebRTCを簡単に使うためのJavascriptライブラリ
* nginx: Webサーバ
* ngrok: ローカル環境を簡単に外部公開できるサービス

## 確認環境
PC: ubuntu18.04.3 LTS (Bionic Beaver) / Chrome 78.0.3904.97
iPhone: 12.4.1 / Safari
iPad: 13.1 / Safari
android: 7.1.1 / Chrome 70.3538.64
(バージョン古いですが。。。)


## 全体の流れ
* SkyWayのアカウントを作成
* SkyWayにログインし、アプリケーションをつくる
* SkyWay公式のサンプルコードをclone
* サンプルコードを微調整
* ローカルPCにWebサーバを立てる
* ngrokでローカルPCのWebサーバを外部公開
* スマホからアクセスして動作確認

## やったこと1 〜SkyWayのセットアップ〜
[SkyWay](https://webrtc.ecl.ntt.com/signup.html)のアカウントを作成し、アプリケーションをつくる。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162371/885fbb38-e9c7-4586-880b-7ece7d6835b7.png)


## やったこと2 〜コード微調整〜
[SkyWay公式のサンプルコード](https://github.com/skyway/skyway-js-sdk)をclone。

```
git clone https://github.com/skyway/skyway-js-sdk
cd skyway-js-sdk
# npm ci ←index.htmlとscript.jsだけなのでnpmは不要
```

examples/p2p-media に1対1のビデオ通話のサンプルコードがある。
ほぼほぼそのまま使えるが、以下の点を変更した。

#### APIキーの追加
SkyWayと接続できるよう、APIキーを指定する。

```script.js
window.__SKYWAY_KEY__ = '前項で作成したアプリケーションのAPIキー';
```

これだけで実機で問題なく動作した(神)。
以降は機能追加の記録です。

#### カメラの指定
getUserMedia()の引数によって、デバイスや帯域、サイズの指定ができる。(cf. [MediaStreamConstraints](https://developer.mozilla.org/ja/docs/Web/API/MediaStreamConstraints))
スマホ特有の内容として、`facingMode: 'user'`で液晶側のカメラ、
`facingMode: 'environment'`で背面のカメラを指定できる。

```diff
const localStream = await navigator.mediaDevices
  .getUserMedia({
    audio: true,
-   video: true,
+   video: { facingMode: 'user' }, // 液晶側のカメラ
  })
```

#### ミュート
カメラとマイクのミュートは、[公式ドキュメント](https://support.skyway.io/hc/ja/articles/236032168-JavaScript%E3%81%A7%E6%98%A0%E5%83%8F-%E9%9F%B3%E5%A3%B0%E3%82%92%E3%83%9F%E3%83%A5%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)通りにすれば問題なくできた。
今回は、適当に「Toggle Camera」「Toggle Microphone」ボタンを作って、オンオフを切り替えられるようにした。

```index.html
<div class="toggle-mute">
  <button id="js-toggle-camera">Toggle Camera</button>
  <span id="camera-status"></span>
  <br>

  <button id="js-toggle-microphone">Toggle Microphone</button>
  <span id="microphone-status"></span>
  <br>
</div>
```
```script.js
const toggleCamera = document.getElementById('js-toggle-camera');
const toggleMicrophone = document.getElementById('js-toggle-microphone');
const cameraStatus = document.getElementById('camera-status');
const microphoneStatus = document.getElementById('microphone-status');

toggleCamera.addEventListener('click', () => {
  const videoTracks = localStream.getVideoTracks()[0];
  videoTracks.enabled = !videoTracks.enabled;
  cameraStatus.textContent = `カメラ${videoTracks.enabled ? 'ON' : 'OFF'}`;
});

toggleMicrophone.addEventListener('click', () => {
  const audioTracks = localStream.getAudioTracks()[0];
  audioTracks.enabled = !audioTracks.enabled;
  microphoneStatus.textContent = `マイク${audioTracks.enabled ? 'ON' : 'OFF'}`;
});
```

#### videoタグの要素
videoタグは、muted, autoplayなどの要素をつけることで動作を設定できる。
ここは地味にハマりポイントだった。

* muted
  * trueだと、自分の音声が自分のスピーカーから聞こえなくなる
  * muted=trueでも自分の音声は相手に届く。相手の音声も自分に届く。
  * muted=trueにしないとiPhoneでは複数のvideoタグが使えない([参考](https://lealog.hateblo.jp/entry/2018/03/27/161141)) (←ハマりポイント)
* playsinline
  * インライン再生するための属性
  * iPhoneでvideoタグを使う場合は必須([参考](https://abrakatabura.hatenablog.com/entry/2017/09/23/092127)) (←ハマりポイント)
* autoplay
  * 自動再生(再生ボタンを押さなくてもビデオが再生される)
 
```index.html
<video id="js-local-stream"></video>
```
```script.js
const localVideo = document.getElementById('js-local-stream');

localVideo.srcObject = localStream;
localVideo.muted = true; // 自分の音声を自分のスピーカーから聞こえなくする。相手には届く。
localVideo.playsInline = true;
localVideo.autoplay = true;
```



## やったこと3 〜公開して動作確認〜
[ngrokが便利すぎる](https://qiita.com/mininobu/items/b45dbc70faedf30f484e)
を参考にローカルでWebサーバ(nginx)を起動し、ngrokで公開した。

```bash
# nginxの設定
$ vi /etc/nginx/sites-available/default
#--設定内容-------------
# 12345番ポートで接続待ち受け
listen 12345 default_server;
listen [::]:12345 default_server;

# サンプルコードを公開
# ※index.htmlとscript.jsだけなので、特にビルドはいらない
root (前略)/skyway-js-sdk/examples/p2p-media;
#---------------

# nginx起動
$ sudo nginx

# 起動確認(12345ポートでLISTENしている)
$ netstat -anp | grep 12345
tcp        0      0 0.0.0.0:12345           0.0.0.0:*               LISTEN      -  

# ngrok実行
$ ngrok http 12345
```
```
# ngrok実行結果
ngrok by @inconshreveable                                                                      (Ctrl+C to quit)
                                                                                                               
Session Status                online                                                                           
Account                        (Plan: Free)                                                                    
Update                        update available (version 2.3.35, Ctrl-U to update)                              
Version                       2.3.34                                                                           
Region                        United States (us)                                                               
Web Interface                 http://127.0.0.1:4040                                                            
Forwarding                    http://d21b048e.ngrok.io -> http://localhost:12345                               
Forwarding                    https://d21b048e.ngrok.io -> http://localhost:12345                              
                                                                                                               
Connections                   ttl     opn     rt1     rt5     p50     p90                                      
                              0       0       0.00    0.00    0.00    0.00    
```

## やったこと4 〜動作確認〜
ngrokを起動時に表示されるアドレス(↑だと`https://d21b048e.ngrok.io `)は、インターネット経由でアクセスできる。
PCとスマホ実機からアクセスして、動作確認を行った。
(注意: httpsのアドレスを使うこと。httpだと、WebRTCは使えない)

<img width="375" alt="iOS の画像.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162371/203e7b18-7188-4104-b482-257671549016.jpeg">

## 最後に
予想よりずっと簡単にビデオ通話が実現できて驚きました。
ぜひ試してみてください。