1-2. PeerJSの初期化
device.coffeeやmonitor.coffeeの冒頭のnew ns.DeviceClass()やnew ns.MonitorClass()によってDeviceClsssやMonitorClassがインスタンス化され、各々のconstructorが実行されますが、実質の処理はBaseClassのconstructorに委譲されています。
class BaseClass
constructor: ->
@peer = new Peer {host:HOST, port:PORT, path:PATH, debug:DEBUG, config:CONF}
...
class DeviceClass extends BaseClass
constructor: ->
super()
...
class MonitorClass extends BaseClass
constructor: ->
super()
...
BaseClassのconstructorでPeerオブジェクトを生成しインスタンス変数(@peer)へ保持しますが、PeerJSはこのタイミングでシグナリングサーバに自身を登録し、WebSocketを開設します。
シグナリングサーバでは接続してきたPeerごとにユニークIDを採番した後、WebSocket経由で@peer.on('open', callback)イベントが発生し、無名関数として登録されているcallback関数が呼び出されます。
class BaseClass
...
onOpen: (peerIDsetting = null) ->
@peer.on 'open', =>
console.log "peer.on 'open' peer.id=#{@peer.id}"
peerIDsetting(@peer.id) if peerIDsetting?
...
このcallback関数では、onOpen()の引数として何らかの関数が与えられていた場合、PeerのユニークID(@peer.id)を引数にその関数を呼び出します。
device.coffeeでは、採番されたユニークIDを画面に表示する関数をDeviceClass.onOpen()の引数として渡しているため、ユニークIDが画面に表示されます。
$ ->
dc = new ns.DeviceClass()
peerIDsetting = (id) ->
$('#device-id').text(id)
...
dc.onOpen(peerIDsetting)
一方、monitor.coffeeではコールバック関数を渡していないため、デバッグコンソールにログが出るだけで画面に変化は起きません。
$ -> dc = new ns.MonitorClass() ... dc.onOpen()
これらのPeerJS初期化処理もスマートグラス・監視端末の実行順序に依存関係はありませんので、お互い非同期に実行して構いません。
@peer.on('open', callback)のcallback関数が、=>(ファットアロー)で定義されていることに注意してください。CoffeeScriptの関数定義構文には「->(アロー)」と「=>(ファットアロー)」がありますが、=>(ファットアロー)で定義された関数では「呼び出し元のコンテキストのthis」が関数実行時のthisにアタッチされます。
上記のonOpenの場合、@peer.on('open', callback)を実行したthisはDeviceClassやMonitorClassから生成されたインスタンスを指しますので、=>(ファットアロー)でcallbackを定義した場合は@peer(@はthisの省略表記)でインスタンス変数として保持しているpeerオブジェクトが参照できます。
JavaScriptでは、以下のイディオムに相当します。
(remote-monitor.coffeeから実際にコンパイルされたJavaScriptとは異なります)
BaseClass.prototype.onOpen = function(peerIDsetting) {
var self = this;
this.peer.on('open', function() {
if (peerIDsetting != null) {
peerIDsetting(self.peer.id);
}
});
};
一方、->(アロー)でcallbackを定義した場合、呼び出し元のthisはアタッチされないため、このcallbackが所属するPeerオブジェクト自身がthisです。そのため @peer はundefinedになり、期待した動作とはなりません。
1-3. MediaStreamの取得
次にMediaStreamの取得を実装します。MediaStreamの取得はPeerの初期化とは並列して実行できるため、@peer.on('open', callback)イベントの発生を待たずに処理を開始して構いません。実際の処理はinitializeメソッドに定義されています。
class BaseClass
...
initialize: (video, initializing, waiting) ->
initializing() # MediaStream初期化画面の表示(device画面①、monitor画面①)
# getUserMediaのブラウザ間差異の吸収 ※3
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia
# MediaStreamの取得 ※4
navigator.getUserMedia {audio:true, video:true}
, (stream) =>
# MediaStreamの取得に成功
video.prop 'src', URL.createObjectURL(stream) # HTML5のvideoタグのsrcにMediaStreamを接続
@ls = stream # 取得したMediaStreamをインスタンス変数として保持
@ls.getAudioTracks()[0].enabled = false # 自身のマイクをOFF
waiting() # 接続待ち画面の表示(device画面②、monitor画面②)
, =>
# MediaStreamの取得に失敗
@eh "getUserMedia fail" if @eh? #エラーハンドラにメッセージを通知
MediaStreamを取得するAPIはブラウザごとに異なり、chromeやOpera for Androidはnavigator.webkitGetUserMedia、firefox for Androidではnavigator.mozGetUserMediaになります。その実装差異を吸収しなければならないことに注意してください。
ブラウザより映像と音声のMeidaStreamを取得します。複数のカメラを持つデバイスの場合、映像を取得するカメラを選択できる場合もあれば、どれか1つのカメラに決め打ちされてしまう場合もあります。これはブラウザの実装依存です。
またgetUserMediaで映像や音声を取得する際は、device画面①やmonitor画面①のように、カメラやマイクへのアクセスへ明示的に許可を与えなければなりません。不正なサイトのJavaScriptからカメラやマイクが知らない間に操作されると困りますので(許可を与える画面は、ブラウザ依存です)。
なおHTTPでアクセスしたサイトでは毎回許可を求められますが、HTTPSでアクセスした場合はカメラやマイクへのアクセス許可/拒否をブラウザが記憶する仕様になっています(が、これもブラウザ依存です)。
1-4. 初期化完了
PeerJSの初期化とMediaStreamの取得に成功すれば、device画面②やmonitor画面②のような接続待ち画面が表示されます。PeerJSの初期化やMediaStreamの取得に失敗した場合、エラーメッセージが表示されます。
実装編2では
今回はPeerJSを用いた「ブラウザ上で動作する遠隔作業支援システム」の前半部分(モジュールの全体構成と初期化処理)を、実装編1として取り上げました。次回の実装編2は、スマートグラスと監視端末のブラウザ間をP2P接続し、映像や音声、データを双方向に交換する部分を取り上げます。次回もお楽しみに!


