SkyWayを使って多人数ビデオチャットを作ってみた

# はじめに

弊社の開発部門では、最近リモートワークが活発に行われ始めております。
その中で、ビデオチャットは頻繁に使われるのですが、なかなか良いものに巡り合っておりません。
というのも、ビデオチャットを行う上で懸念として２点が考えるためです。

- 通信速度
- 機能的制限

現在、[Appear.in](https://appear.in/)をメインに利用しております。
理由としては、[Google ハングアウト](https://hangouts.google.com/?hl=ja)よりは通信速度が出ており、多人数で行えるためです。
しかしながら、制限として８名までなっており、9名以上の拠点の場合、ブラウザを分けるなどを行わなければなりません。

それなら一層のこと試しに作ってみてはと思ったので、チュートリアルを真似しながらサクっと作ってみました。

[SkyWay](https://nttcom.github.io/skyway/)

# 参考

https://github.com/nttcom/SkyWay-MultiParty

# 環境

|#|OS/ソフトウェア/ライブラリ|バージョン|用途|
|:---|:---|:---|:---|
|1|Mac OS X|EI Capitan|ローカル環境での開発とHerokuへのプッシュなどを行ってます。|
|2|Heroku|-|言わずとしれたかもしれませんが、アプリケーションを動かす無料サーバです。|
|3|Heroku Git|-|今回は、こちらにPushをしております。|
|4|Node.js|v8.1.2|Webサーバとして利用しております。|
|5|SkyWay|-|Peerサーバと通信するためのライブラリです。|
|6|ChromeX|59.0.3071.115|ブラウザ保証としてはこちらのみとなっております。|
|7|jQuery|1系|実装はこちらを使っております。|
|8|Git|最新版|Heroku Gitへ色々操作するのに使います。|

# 準備

## ローカル環境

### anyenvの導入
```ｚｓｈ
git clone https://github.com/riywo/anyenv ~/.anyenv
```

### anyenvのPATH設定
```ｚｓｈ
if [ -d ${HOME}/.anyenv ] ; then
    export PATH="$HOME/.anyenv/bin:$PATH"
    eval "$(anyenv init -)"
    for D in `ls $HOME/.anyenv/envs`
    do
        export PATH="$HOME/.anyenv/envs/$D/shims:$PATH"
    done
fi
```

### ndenvの導入
```ｚｓｈ
anyenv install ndenv
exec $SHELL -l
```

### Node.jsのインストール
```ｚｓｈ
ndenv install --list
ndenv install v8.1.2
ndenv global v8.1.2
node -v
```

## Heroku

### アカウント登録とログイン

使ったことない方は、[Heroku初心者がHello, Herokuをしてみる](http://qiita.com/Arashi/items/b2f2e01259238235e187)

### アプリケーションの作成

ここでは、video-chatとします。

### ドメインの取得

アプリケーション作成後、画面右上の「Open App」をクリックすると、以下のURLにアクセスされます。

https://video-chat.herokuapp.com/

ドメインは、「video-chat.herokuapp.com」この部分となります。

## SkyWay

### アカウント登録

https://skyway.io/ds/registration/

こちらを見れば、すぐわかると思います。
SkyWayを利用するドメインという箇所に先程の「video-chat.herokuapp.com」を入力してください。
また、ローカル環境で利用したい場合は、「localhost」を入力して利用できます。

### キーの取得

登録完了後、メールが届きますのでそちらか本登録をすると、キーが取得できます。

# 準備

## Gitのインストール
```zsh
brew install git
```

## Heroku Gitのインストール
```zsh
brew install heroku
```

## Heroku Gitのログイン

Herokuに登録したアカウントをパスワードを入力してください。

```zsh
heroku login
```

## プロジェクトディレクトリの作成
```zsh
mkdir video-chat
```
## Gitファイルの作成
```zsh
cd video-chat
git init
```

## 公開ディレクトリの作成
```zsh
cd video-chat
mkdir public
```

# 実装

- multipartyライブラリを用いた多人数ビデオチャットおよびテキストチャット。
- また、ビデオと音声にはミュート機能を実装しております。
- スクリーンのシェアも実装はしておりますが、ブラウザのエクステンションが必要なため、そちらの動は行っておりません。ソースコードもコメントアウトしております。
- index.htmlはpublicディレクトリ以下に配置してください。

## ソースコード

```html:index.html
<!doctype html>
<html>
    <head>
        <script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
        <script src="https://skyway.io/dist/0.3/peer.min.js"></script>
        <script src="https://skyway.io/dist/multiparty.min.js"></script>
        <script src="https://skyway.io/dist/screenshare.min.js"></script>
        <style>
            body {
                margin: 0;
            }
            #message {
                width: 190px;
                margin: 10px;
            }
            #streams {
                position: absolute;
                top: 10px;
                margin-left: 200px;
            }
            .video{
                margin: 0px 0px 0px 5px;
                width: 300px;
                border: 1px solid #000000;
                border-radius: 10px;
            }
            #streams .my-video {
                -webkit-transform: scaleX(-1);
                -o-transform: scaleX(-1);
                -moz-transform: scaleX(-1);
                transform: scaleX(-1);
            }
        </style>
    </head>
    <body>
        <div id="message">
            <form>
                <input type="text"><button type="submit">send</button>
            </form>
            <p class="receive">
            </p>
        </div>
        <div id="streams">
            <div>
                <!-- <button id="start-screen" data-muted="false">start screen</button> -->
                <button id="video-mute" data-muted="false">video mute</button>
                <button id="audio-mute" data-muted="false">audio mute</button>
            </div>
        </div>
        <script>
            multiparty = new MultiParty( {
                "key": "{キー}",
                "reliable": true,
                "debug": 3
            });

            screen = new SkyWay.ScreenShare({debug: true});

            multiparty.on('my_ms', function(video) {
                var vNode = MultiParty.util.createVideoNode(video);
                vNode.setAttribute("class", "video my-video");
                vNode.volume = 0;
                $(vNode).appendTo("#streams");
            }).on('peer_ms', function(video) {
                console.log("video received!!")
                console.log(video);
                var vNode = MultiParty.util.createVideoNode(video);
                vNode.setAttribute("class", "video peer-video");
                $(vNode).appendTo("#streams");
                console.log($("#streams"))
            }).on('ms_close', function(peer_id) {
                $("#"+peer_id).remove();
            })

            multiparty.on('message', function(msg) {
                $("p.receive").append(msg.data + "<br>");
            });

            multiparty.on('error', function(err) {
                alert(err);
            });

            multiparty.start();

            $("#message form").on("submit", function(e) {
                e.preventDefault();

                var $text = $(this).find("input[type=text]");
                var data = $text.val();

                if (data.length > 0) {
                    data = data.replace(/</g, "&lt;").replace(/>/g, "&gt;");
                    $("p.receive").append(data + "<br>");
                    multiparty.send(data);
                    $text.val("");
                }
            });

            $('#start-screen').click(function () {
                if (screen.isEnabledExtension()) {
                    screen.startScreenShare({
                        Width: $('#Width').val(),
                        Height: $('#Height').val(),
                        FrameRate: $('#FrameRate').val()
                    },
                    function (stream) {
                        $('#my-video').prop('src', URL.createObjectURL(stream));

                        if(existingCall != null){
                            var _peerid = existingCall.peer;
                            existingCall.close();
                            var call = peer.call(_peerid, stream);
                            step3(call);
                        }

                        localStream = stream;

                    },function(error){
                        console.log(error);
                    },function() {
                        alert('ScreenShareを終了しました');
                    });
                } else {
                    alert('ExtensionまたはAddonをインストールして下さい');
                }
            });

            $('#stop-screen').click(function() {
                localStream.stop();
            });

            $("#video-mute").on("click", function(ev) {
                var mute = !$(this).data("muted");
                multiparty.mute({video: mute});
                $(this).text("video " + (mute ? "unmute" : "mute")).data("muted", mute);
            });

            $("#audio-mute").on("click", function(ev) {
                var mute = !$(this).data("muted");
                multiparty.mute({audio: mute});
                $(this).text("audio " + (mute ? "unmute" : "mute")).data("muted", mute);
            });
        </script>
    </body>
</html>
```

## Webサーバ設定

### expressのインストール

対話はすべてEnter押してます。もしかすると初回だけエラーが出力されますが、動作上は問題ないとおもいます。

```zsh
cd video-chat
npm init
npm install express
```

#### package.json
```zsh
{
  "name": "video-chat",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node app.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.15.3"
  }
}
```

### app.js

デフォルトは3000番なのですが、よくSlackや他のチャットを動かしてるとぶつかるので、他のポートを使うのがオススメです。こちらでは13000番を使ってます。

```zsh
var express = require('express');
var app = express();

app.use(express.static('public', { hidden: true }));

app.listen(process.env.PORT || 13000);
```

# 公開

## Heroku Gitへのファイルの追加
```zsh
cd video-chat
git add .
```

## Heroku Gitへのコミット
```zsh
git commit -am "new make"
```

## Heroku GitへのプッシュとHerokuサーバへデプロイ
```zsh
git push heroku master
```

# 実行

- 顔はモザイクいれてます。
- 多人数でできるかなどはお近くの人と試してみてください。

<img width="596" alt="スクリーンショット 2017-07-03 15.06.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/90598/adb4d10a-1c7a-2de0-5528-07a6e7562110.png">

# まとめ

- WebRTCを使ったチャットアプリを作っている方は、3,4年前くらいから多々おりますが、シグナリングサーバを作成したりするのに手間が掛かってました。しかし、最近ではSkyWayのようなお手軽簡単にできちゃうAPIやライブラリが揃っていたので苦労することなく構築できました。
- 速度としては、Herokuがどれほどのネットワークを備えているかは不明ですが、Appear.inより体感でも速く感じました。個人のツールとしては良いかもしれません。
- 社内ネットワーク制限がかかっている場合などはあるため、その辺は注意深く確認の必要がありそうです。

# 全ページリンク
- [OpenCVを使ってアン○ンマンになってみた](http://qiita.com/neriai/items/9155263f4b5e93d90906)
- [SkyWayを使って多人数ビデオチャットを作ってみた](http://qiita.com/neriai/items/b96bb88f39903a2abf85)
- [SkyWayを使ったビデオチャットに顔認識ができるか試してみた](http://qiita.com/neriai/items/e6e697efa01e9806757e)