WebRTCとNode.jsを用いたリモート監視システムのサンプルを書きました。
WebRTCを自力実装するのは結構面倒なため、PeerJSを利用しています。
またシグナリングサーバもPeerJSが提供する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は、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で書かれています。
全体構造
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コードと同じです。
var ns = (function() {
var exports = {};
var host = 'xxx.xxx.xxx.xxx';
...
function RemoteMonitor() {...}
exports.RemoteMonitor = RemoteMonitor;
return exports;
})();
RemoteMonitor
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から利用されます。
処理の流れ
モニター側
初期化
- constructor実行により、シグナリングサーバとの接続する
- peerのerrorイベントリスナを登録する
- シグナリングサーバとの接続が確立するとpeerのopenイベントが発火する
- initialize実行により、ローカルカメラ・マイクのMediaStreamを取得する
- MediaStream取得に成功すると、monitor.htmlから渡されたwaitingコールバック関数を呼び出す(接続したいpeerのIDを入力するフォームが画面に表示される)
デバイスへ接続
- 画面から接続するデバイスのIDを入力し、CALLボタンを押す
- makeCall関数が起動し、指定したpeerへcall要求を送る
- call要求が成功すると、接続したデバイスのMediaStreamをラップするMediaConnectionオブジェクトが得られる
- monitor.htmlから渡されたconnectingコールバック関数を呼び出す(デバイスのカメラ映像が画面に表示される)
デバイスへMIC ON要求
- 画面上のMIC ONボタンを押す
- toggleMIC関数が起動し、ローカル(モニター側)のマイクをONにする
- 接続したpeerへconnect要求を送る
- connectionが確立した後、得られたDataChannelへ'mic-on'メッセージをsendする
デバイスを切断
- 画面上のEND CALLボタンを押す
- closeCall関数が起動し、接続時に得られたMediaConnectionオブジェクトをcloseする
デバイス側
初期化
- constructor実行により、シグナリングサーバとの接続する
- peerのopen, error, connection, callイベントリスナを登録する
- シグナリングサーバとの接続が確立するとpeerのopenイベントが発火し、ユニークIDが採番される
- initialize実行により、ローカルカメラ・マイクのMediaStreamを取得する
- MediaStream取得に成功すると、device.htmlから渡されたwaitingコールバック関数を呼び出す(自身のIDが画面に表示される)
モニター側から接続
- モニター側からのcall要求が届くとpeerのcallイベントが発火し、モニター側のカメラとマイクのMediaStreamが得られる
- device.htmlから渡されたconnectingコールバック関数を呼び出す(デバイスのカメラ映像が画面に表示される)
モニター側からのMIC ON要求
- モニター側からconnect要求が届くとpeerのconnectionイベントが発火し、モニターとデバイス間でDataChannelが確立する
- DataChannelを通じてデータが届くとconnectionのdataイベントが発火する
- 届いたdataが'mic-on'の場合、自身のマイクをONにする
モニター側からの切断
- モニター側から切断されるとcallのcloseイベントが発火し、waitingコールバック関数を呼び出す(カメラ映像が消え、自身のIDが再び画面に表示される)