webRTCで自前のビデオチャットサービスをつくる

最近のテレワーク推奨の影響でzoomやwherebyのようなwebRTC形式のビデオチャットの需要が急騰しているようです。

しかしベンダ側のサーバーリソースが追いついていなかったり、暗号化にベンダ側の秘密鍵を使っていたりと問題も顕在化しているようです。
[Zoom、テレワーク急増に伴い部分的な停止が発生](https://cafe-dc.com/security/zoom-suffers-partial-outage-amid-home-working-surge/)
[Zoomを安全に利用する4つのポイント。Zoom爆弾や情報漏えいへ対処する。](https://news.yahoo.co.jp/byline/ohmototakashi/20200406-00171691/)

ここではベンダのサーバーリソースに依存せず、暗号化も自ドメインの秘密鍵を使うことで、外部の影響を受けずに自前のビデオチャットサービスを用意しちゃえばいいよね、な発想に基づいたプロトタイプになります。
このくらいなら自分でも簡単につくれるご時世に感謝ですね。

![ss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282332/90b1f6c8-7c5d-26b9-9212-bd1715aaf903.png)

さっそく本題です。

# 事前に決めておくもの
* シグナリングサーバーのFQDN
* シグナリングサーバーの内部ポート（8888 等）
* ビデオチャットサービスのFQDN
* ビデオチャットサービスのファイルパス（~/webrtc 等）

# 実際の準備
## シグナリングサーバーサービスの用意
```
mkdir ~/webrtc/
cd {ビデオチャットサービスのファイルパス}/
git clone https://github.com/simplewebrtc/signalmaster.git

cd signalmaster/
npm i
vi config/production.json
	"port": {シグナリングサーバーの内部ポート},

nohup node server.js &
lsof -i:{シグナリングサーバーの内部ポート}
	(前略) TCP *:{シグナリングサーバーの内部ポート} (LISTEN)

curl localhost:{シグナリングサーバーの内部ポート}/socket.io/
	{"code":0,"message":"Transport unknown"}
```

## ビデオチャットサービスの用意
```
cd {ビデオチャットサービスのファイルパス}/
wget https://raw.githubusercontent.com/simplewebrtc/SimpleWebRTC/gh-pages/latest-v3.js

vi {ビデオチャットサービスのファイルパス}/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
<title>webRTCチャット</title>
<meta charset="UTF-8">
<script src="//code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="//webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="latest-v3.js"></script>
<script>
const signal_url = 'https://{シグナリングサーバーのFQDN}';
$(function(){
  var is_pause = false;
  var is_disconnect = false;
  var webrtc = null;
  $('#enter').click(function(){
    $('#entrance').hide('fast');
    $('#chat_canvas').show('fast');
    webrtc = new SimpleWebRTC({
      localVideoEl: 'localVideo',
      remoteVideosEl: 'remoteVideos',
      autoRequestMedia: true,
      url: signal_url
    });

    webrtc.on('readyToCall', function () {
      webrtc.joinRoom('your awesome room name');
    });
    webrtc.on('connectionReady', function (sessionId) {
      console.log('sessionId: ', sessionId);
    });

    webrtc.connection.on('message', function(data){
      if(data.type === 'chat'){
        console.log('chat received',data);
        $('#messages').append('<div class="you"><span>You</span>' + data.payload.message + "</div>\n");
      }
    });
    is_pause = false;
    is_disconnect = false;
  });

  $('#pause').click(function(){
    if (is_disconnect) {
      alert('すでに接続が切れています。再接続はリロードしてください');
      return false;
    }
    if (is_pause) {
      webrtc.resume();
      is_pause = false;
    } else {
      webrtc.pause();
      is_pause = true;
    }
  });
  $('#disconnect').click(function(){
    if (is_disconnect) {
      alert('すでに接続が切れています。再接続はリロードしてください');
    } else {
      webrtc.disconnect();
      webrtc = null;
      is_disconnect = true;
      $('#entrance').show('fast');
      $('#chat_canvas').hide('fast');
//      $('#remoteVideos video:visible').animate({opacity:0}, 'slow');
    }
  });
  $('#send').click(function(){
    var msg = $('#text').val();
    // webrtc.sendToAll('chat', {message: msg, nick: webrtc.config.nick});
    webrtc.sendToAll('chat', {message: msg});
    $('#messages').append('<div class="me"><span>Me</span>' + msg + "</div>\n");
    $('#text').val('');
  });

});
</script>
<style>
  h1 {
    font-size:1.2em;
  }
  #chat_canvas {
    display:none;
  }
  #remoteVideos video {
    height: 400px;
  }
  /*
  #remoteVideos video:first-child {
    display:none;
  }
  */
  #localVideo {
    height: 200px;
  }
  #text {
    width:300px;
    height:40px;
    vertical-align:bottom;
  }
  button, textarea {
    margin-bottom:5px;
    margin-right:5px;
  }
  #for_chat #messages div {
    border-bottom:1px dotted gray;
    margin-bottom:5px;
  }
  #for_chat div span {
    display:block;
  }
  #for_chat div span:after {
    content:':';
  }
  #for_me dt, #for_chat div.me {
    color:#339933;
  }
  #for_you dt, #for_chat div.you {
    color:#333399;
  }
</style>
</head>
<body>
  <h1>webRTCチャット</h1>
  <div id="entrance">
    <p>カメラとマイクの準備ができたら、ENTERを押してください</p>
    <button id="enter">ENTER</button>
  </div>
  <div id="chat_canvas">
    <dl id="for_me">
      <dt>This is Me</dt>
      <dd>
        <video id="localVideo"></video>
      </dd>
    </dl>
    <dl id="for_you">
      <dt>This is You</dt>
      <dd>
        <div id="remoteVideos"></div>
      </dd>
    </dl>
    <dl id="for_control">
      <dt>for Contrtol</dt>
      <dd>
        <textarea id="text"></textarea><button id="send">送信</button><br>
        <button id="pause">ポーズ/ポーズ解除</button><br>
        <button id="disconnect">接続断</button><br>
      </dd>
    </dl>
    <dl id="for_chat">
      <dt>Chat messages</dt>
      <dd>
        <div id="messages"></div>
      </dd>
    </dl>
  </div>
</body>
</html>
```

## WEBサーバの設定
```
sudo vi /etc/nginx/conf.d/webrtc.conf

server {
    listen 443 ssl http2;
    server_name {ビデオチャットサービスのFQDN};
    ssl_certificate /etc/letsencrypt/live/{ビデオチャットサービスのFQDN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{ビデオチャットサービスのFQDN}/privkey.pem;
    index index.html;
    root {ビデオチャットサービスのファイルパス};
}

server {
    listen 443 ssl http2;
    server_name {シグナリングサーバーのFQDN};
    ssl_certificate /etc/letsencrypt/live/{シグナリングサーバーのFQDN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{シグナリングサーバーのFQDN}/privkey.pem;
    ssl_verify_client off;
    proxy_ssl_verify off;
    location / {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_redirect http:// https://;
        proxy_pass http://127.0.0.1:{シグナリングサーバーの内部ポート};
    }
}
```

# 動作確認
ブラウザから以下にアクセス
https://{ビデオチャットサービスのFQDN}/

冒頭の画像のような画面が表示されたら成功です。
うまくP2P接続ができていないようなら、F12でコンソールを開いてソケット通信が問題なく開始されていることを確認してみてください。

# 参考資料
[simplewebrtc / SimpleWebRTC](https://github.com/simplewebrtc/SimpleWebRTC)
[simplewebrtc / signalmaster](https://github.com/simplewebrtc/signalmaster)