PeerJSを用いたリモート監視システムのサンプル

WebRTCとNode.jsを用いたリモート監視システムのサンプルを書きました。

https://raw.githubusercontent.com/nmatsui/remote-monitor/

WebRTCを自力実装するのは結構面倒なため、[PeerJS](http://peerjs.com/)を利用しています。
またシグナリングサーバもPeerJSが提供する[peerjs-server](https://github.com/peers/peerjs-server)を利用しました。

## 利用したライブラリ

* peer:0.2.5
* express:4.2.0
* node:0.10.28

## 動作検証した環境

* Google Chrome 35.0.1916.153 / Mac OS X 10.7.5 / Macbook Air Mid 2011
* Google Chrome for Android 35.0.1916.141 / Android 4.2.2 / HTL22
* Google Chrome for Android 35.0.1916.141 / Android 4.0.4 / Vuzix M100

### Vuzix M100

[Vuzix M100](http://www.vuzix.com/consumer/products_m100/)は、Vuzix社が販売する片眼非透過型のスマートグラスです。Android4.0系で動作しており、カメラ・マイク・スピーカーやGPS/3軸センサーなど、ひと通りのセンサー類が搭載されています。

ただし残念なことに、Vuzix M100にデフォルトインストールされている標準ブラウザではWebRTCが動作しません。またGoogle Playも導入されていないため、Play StoreからWebRTCが動作するブラウザをインストールすることもできません。

そこで今回は、別のAndroidデバイスからGoogle Chrome for AndroidのAPKファイルを`adb pull`し、Vuzix M100に`adb install`することで、Vuzix M100上でGoogle Chromeを利用できるようにしました。
（Vuzix M100のVendorIDは *"0x1bae"* です）

## コア部分

PeerJSを使いP2Pでカメラ映像や音声の交換、及びイベント送受信を行う部分です。CoffeeScriptで書かれています。

### 全体構造

```coffeescript:remote-monitor.coffee
ns = do ->
  exports = {}

  host = 'xxx.xxx.xxx.xxx' #シグナリングサーバのホスト名（or IPアドレス）'
  port = 0000 #シグナリングサーバを動作させているポート番号
  path = '/remote-monitor' #シグナリングサーバのPATH
  debug = 3

  class exports.RemoteMonitor
    ...

  exports
```

Globalにはnsという変数のみを公開し、これを今回のアプリの名前空間とします。hostやport等の変数は即時関数内のみ利用可能で、ns名前空間に公開するのはRemoteMonitorコンストラクタ関数のみとしています。

以下のJavascriptコードと同じです。

```js:remote-monitor.js
var ns = (function() {
  var exports = {};
  var host = 'xxx.xxx.xxx.xxx';
  ...
  function RemoteMonitor() {...}
  exports.RemoteMonitor = RemoteMonitor;
  return exports;
})();
```

### RemoteMonitor

```coffeescript:remote-monitor.cofee
  class exports.RemoteMonitor
    constructor: ->
      # peerオブジェクトの作成
      # シグナリングサーバへの接続実施
    initialize: (video, initializing, waiting) ->
      # WebRTCを用いてカメラとマイクのmediastreamを取得
      # stream取得成功後、マイクは一旦OFFにする
    onOpen: (peerIDsetting) ->
      # peerの'open'イベントリスナを登録する
      # シグナリングサーバとの接続が確立すると'open'イベントが発火し、接続元ごとのユニークIDが採番される
    onError: (showError, waiting) ->
      # peerの'error'イベントリスナを登録する
    onConnection: ->
      # peerの'connection'イベントリスナを登録する
      # 別のpeerからconnect要求が発行されると発火する
    onCall: (video, connecting, waiting) ->
      # peerの'call'イベントリスナを登録する
      # 別のpeerからcall要求が発行されると発火する
      # call要求元へ初期化済みのMediaStreamをanswerする
    makeCall: (callto, video, connecting, waiting) ->
      # calltoで指定したpeerへcall要求を送る
    closeCall: ->
      # 確立済みのcallを閉じる
    toggleMIC: ->
      # 自分自身のマイクのON/OFFをトグルする
      # 接続済みのpeerに対し、connect要求を発行して'mic-on','mic-off'メッセージを送信する
    terminate: ->
      # 全ての接続を破棄し、peerオブジェクトを無効化する

    __connect: (call, video, waiting) ->
      # 接続してきたリモートpeerに対し、MediaStreamが確立した場合のイベントハンドラとリモートpeerが切断された場合のイベントハンドラを登録する
````

WebRTC関連処理を実装したコンストラクタ関数です。device.htmlとmonitor.htmlから利用されます。

## 処理の流れ
### モニター側
#### 初期化
1. constructor実行により、シグナリングサーバとの接続する
2. peerのerrorイベントリスナを登録する
3. シグナリングサーバとの接続が確立するとpeerのopenイベントが発火する
3. initialize実行により、ローカルカメラ・マイクのMediaStreamを取得する
4. MediaStream取得に成功すると、monitor.htmlから渡されたwaitingコールバック関数を呼び出す（接続したいpeerのIDを入力するフォームが画面に表示される）

#### デバイスへ接続
1. 画面から接続するデバイスのIDを入力し、CALLボタンを押す
2. makeCall関数が起動し、指定したpeerへcall要求を送る
3. call要求が成功すると、接続したデバイスのMediaStreamをラップするMediaConnectionオブジェクトが得られる
4. monitor.htmlから渡されたconnectingコールバック関数を呼び出す（デバイスのカメラ映像が画面に表示される）

#### デバイスへMIC ON要求
1. 画面上のMIC ONボタンを押す
2. toggleMIC関数が起動し、ローカル（モニター側）のマイクをONにする
3. 接続したpeerへconnect要求を送る
4. connectionが確立した後、得られたDataChannelへ'mic-on'メッセージをsendする

#### デバイスを切断
1. 画面上のEND CALLボタンを押す
2. closeCall関数が起動し、接続時に得られたMediaConnectionオブジェクトをcloseする

### デバイス側
#### 初期化
1. constructor実行により、シグナリングサーバとの接続する
2. peerのopen, error, connection, callイベントリスナを登録する
3. シグナリングサーバとの接続が確立するとpeerのopenイベントが発火し、ユニークIDが採番される
3. initialize実行により、ローカルカメラ・マイクのMediaStreamを取得する
4. MediaStream取得に成功すると、device.htmlから渡されたwaitingコールバック関数を呼び出す（自身のIDが画面に表示される）

#### モニター側から接続
5. モニター側からのcall要求が届くとpeerのcallイベントが発火し、モニター側のカメラとマイクのMediaStreamが得られる
6. device.htmlから渡されたconnectingコールバック関数を呼び出す（デバイスのカメラ映像が画面に表示される）

#### モニター側からのMIC ON要求
1. モニター側からconnect要求が届くとpeerのconnectionイベントが発火し、モニターとデバイス間でDataChannelが確立する
2. DataChannelを通じてデータが届くとconnectionのdataイベントが発火する
3. 届いたdataが'mic-on'の場合、自身のマイクをONにする

#### モニター側からの切断
1. モニター側から切断されるとcallのcloseイベントが発火し、waitingコールバック関数を呼び出す（カメラ映像が消え、自身のIDが再び画面に表示される）