こんにちは! 2014年に連載した「WebRTCを使ってみよう!」シリーズのアップデートとしてお送りしているこの連載ですが、今回はもとの連載にはなかった内容を番外編としてお届けします。
httpsのハードル
前回は複数人、複数会議室で利用できるようにして、実用的なアプリを作る準備ができました。ところが実際に使おうとすると、Chromeのセキュリティポリシーと向き合わなくてはなりません。
「getUserMedia()やService Workerなどの強力なAPIは、セキュアな環境でなくては利用できない」というポリシーは今のWebの状況に合わせたものだと思います。では、その環境をどうやって用意すればよいのでしょうか?
もちろん証明書を取得して、きちんとサーバーを立てるのがまっとうなやり方です。最近はLet’s Encryptなど無料で証明書を発行するサービスもあります(参考:“Let’s Encrypt – how get to free SSL for WebRTC”)。とはいえ、試験的な利用でそこまで準備するのは大変というのも、正直なところです。そこで今回は、比較的とっつきやすい方法をご紹介します。
Webサーバー
HTMLやJavaScriptなどの静的コンテンツを配置できる手段はいくつかあります。エンジニアの皆さんであれば、次の2つをすでに利用されている方々も多いのではないでしょうか?
- GitHub Pages
- Google App Engine
どちらも http / https の両方でアクセスできますし、独自ドメインで利用することも可能です。(※利用方法についてはWebに多くの情報がありますので、そちらをご参照ください)
また、シグナリングで利用するFirebaseにも静的コンテンツのホスティング機能があります。
シグナリングサーバー
シグナリングにはsocket.ioなど、WebSocketを活用した仕組みが使われる例が多いようです。もちろん他の方法(例えば手動シグナリング)でもいいのですが、サーバーとクライアント(ブラウザ)で双方向に通信するにはWebSocketが適切なのでしょう。
https://~から取得されたHTML/JavaScriptからWebSocketサーバーに接続する場合には、そちらもセキュアでなくてはなりません。(ws://~ではなく、wss://~)。WebSocketを利用したシグナリングサーバーを自分で用意する場合、そちらでも証明書が必要になります。
そこで今回はリアルタイムメッセージングに利用できるBaaSであるFirebaseを使って、シグナリングの仕組みを構築したいと思います。
Firebaseでシグナリングを実現するには
まずはFirebaseにサインアップし、必要なキーやURLを入手しましょう。
(※手順については今回は省略させていただきます。あしらかず)
ブラウザでFirebaseの機能を利用するために、必要なライブラリを読み込んでおきます。今回はデータベースを利用するので、必要なjsファイルは次の通りとなります。
<script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-database.js"></script>
それから取得しておいたキーとURLを用いてFirebaseに接続します。
// Initialize Firebase let config = { apiKey: "yourAPIKey", // <-- please set your API key databaseURL: "https://yourapp.firebaseio.com", // <-- please set your database URL }; firebase.initializeApp(config); let database = firebase.database();
シグナリングで送りたいモノ
前回の記事にもあるように、シグナリングでは2つの通信ケースがあります。
- ルーム内の他のメンバー全員(接続している他のクライアントすべて)に送る
- 特定のメンバー(特定のクライアント)だけに送る
また後者のためには、特定のメンバーを識別するための何らかのIDが必要となります。
- クライアント側で、重ならない/重なりにくい ようにIDを決める(UUID、タイムスタンプ、乱数など)
- サーバー側でIDを振り出す
Firebaseを使うとデータベースに格納されるオブジェクトのすべてにIDが振られるので、今回はそれを利用します。
データベースの構造
Firebaseはデータベースによる階層構造をもったデータの永続化と、その追加/変更/削除のイベント通知が行えます。今回は次のようなアプリ/ルーム/メンバーの階層構造としました。
例えばルーム”test”にメンバー”bbb”が参加する場合、次の2カ所のイベントを待ち受けています。
- multi/room_test/_broadcast_ の child_added イベント
- multi/room_test/_direct_/member_bbb の child_added イベント
また、multi/room_test/_join_ はメッセージのやりとり以外の用途で使います。
メンバーのIDの決定
メンバーを特定してメッセージを送るには、メンバーを識別するIDが必要です。先ほどは仮に”bbb”としましたが、実際には衝突を避けるためにFirebase側で振り出されるキーを利用することにしました。
let databaseRoot = 'myapp/multi/'; let key = database.ref(databaseRoot + room + '/join').push({ joined : 'unknown'}).key clientId = 'member_' + key; database.ref(databaseRoot + room + '/join/' + key).update({ joined : clientId});
データベースにパスを指定してpush()すると、子要素が追加されて、そのキーが返っています。その値を使って先ほどの子要素の内容を更新しています。
ここまで終わった時のDatabaseの内容をFirebaseのコンソールで見てみると、次のようになっています。
ルーム内へのブロードキャスト
シグナリングの流れは前回と同じです。
- 新たに通信を開始したい人(member_xxxx)が、通信開始の合図のルーム内にブロードキャスする(“call me”)
“test”ルームにブロードキャストする場合には、 multi/room_test/_broadcast_ の下を使います。
let roomBroadcastRef = database.ref(databaseRoot + room + '/broadcast');// 通信開始の合図 function callMe() { emitRoom({type: 'call me'}); }
function emitRoom(msg) { msg.from = clientId; // メッセージに送信元(自分のID)をセット roomBroadcastRef.push(msg); }
各メンバーはこのブロードキャストのイベントを待ち受けていて、typeに応じて処理を行います。
roomBroadcastRef.on('child_added', function(data) { let message = data.val(); let fromId = message.from; if (fromId === clientId) { // ignore self message (自分自身からのメッセージは無視する) return; }if (message.type === 'call me') { // 接続処理 } });</pre>
socket.ioでは自分自身からメッセージは飛んで来ませんが、Firebaseでは自分でpush()してもイベントが飛んでくるので、それは無視しています。
特定のメンバーへのメッセージ
通信開始の合図である"call me"以外は、特定のメンバー宛のメッセージのやり取りになります。例えば member_bbb 宛のメッセージは、この下にpush()します。
- multi/room_test/direct/member_bbb
function emitTo(id, msg) { msg.from = clientId; // メッセージに送信元(自分のID)をセット database.ref(databaseRoot + room + '/direct/' + id).push(msg); }
Offer/Answerの交換やICE Candidateのやり取りは、すべてこちらのメンバー宛のメッセージになります。
Firebaseのルール設定
Firebaseのデータベースにはアクセスのルール指定があります。デフィルトではread/writeにAuth必要なルールが生成されていますが、今回のサンプルでは特定のパス以下はAuth不要で読み書きできるよう、ルールを追加しました。
{ "rules": { ".read": "auth != null", ".write": "auth != null", "myapp" : { "multi" : { ".read": true, ".write": true } } } }
NAT越えの設定 STUNの設定
せっかくFirebaseでメッセージをやり取りできるので、NATを超えてWebRTCで通信できるようにしましょう。
NATを超えて通信を行うには、ローカルネットワークでのIPアドレスではなく、グローバルIPを伝える必要があります。今回は詳しく説明しませんが、そのための仕組みがSTUNです。GoogleがSTUNサーバーを公開しているので、それを使わせてもらいましょう。
function prepareNewConnection(id) { let pc_config = {"iceServers":[{"urls": "stun:stun.l.google.com:19302"}]}; // for STUN server let peer = new RTCPeerConnection(pc_config); // ... 省略 ... }
※ 2014年の記事では"url" と指定していましたが、現在の仕様では"urls"と指定する必要があります。
このようにSTUNサーバーを指定することで、通信経路の候補(ICE Candidate)に、STUN経由で取得した情報も含まれるようになります。
動かしてみよう
今回のサンプルをGitHub Pagesで公開しています。Firebaseもしばらく(〜2016年9月末の予定)使える状態にしておきますので、皆さんもお試しください。
- GitHub Pages で試す multi_firebase.html (Chrome/Firefox)
- GitHub でソースを見る multi_firebase.html
※シグナリングの手段以外は、前回のソースと同様の処理になっています。
使い方
- URLの後ろに ?room という形で、好きなルーム名を指定してください
- https://mganeko.github.io/webrtcexpjp/basic2016/multi_firebase.html?お好きなルーム名
- ルーム名を指定せずに multi_firebase.html を開くと、ランダムにルーム名を決定します
- [Start Video]をクリックし、カメラの映像とマイクの音声を取得します
- 通信相手にも同じルーム名を指定してブラウザ(Chrome/Firebox)でアクセスしてもらいます
- ?room も含むURLを伝えてください
- "Mail link of this room" をクリックすると、URLを送るためのメーラーが開きます。宛先を指定して送信してください
- 通信相手にも[Start Video]をクリックし、カメラの映像とマイクの音声を取得してもらいます
- 自分、あるいは相手から[Connect]ボタンを押してください
- Firebase経由で情報が交換され、P2P通信が始まります
- このサンプルでは、同じルームに同時に4人まで(3人の相手と)通信することができます
トラブルシューティング
- 異なるPCで通信できない場合
- → ルーム名が一致しているか確認していください
- ルーム名を指定せずにブラウザでアクセスすると、ランダムにルーム名が決定されます
- 異なるPCでは、それぞれ異なるルーム名がランダムに決定されます
- 一度接続できたが、その後できなくなった場合
- ルーム内の過去のブロードキャストメッセージが残っている可能性があります
- ルーム名を変更して、再度接続してみてください
- (例えば)会社と自宅で通信できない場合
- → NATだけでなく、FirewallでUDP通信やポートが制限されている可能性があります
- ※Firewallを超えての通信については、次回の記事で取り上げる予定です
Edge同士での通信
webrtc.orgが提供しているadapter.jsを使うと、多くのブラウザの差異を吸収することができます。最新のhttps://webrtc.github.io/adapter/adapter-latest.jsを読み込めば、Microsoft EdgeのサポートしてるORTCを、WebRTCのオブジェクトのインターフェイスを通して利用することができます。
残念ながらサポートしているビデオコーデックの制限で、ChromeやFirefoxとのビデオ通信はできませんが、Edge同士であれば今回のFirebaseを使ったシグナリングでビデオ/オーディオのP2P通信を行うことができます。
- GitHub Pages で試す multi_firebase_adapter.html (Chrome/Firefox)
- GitHub でソースを見る multi_firebase_adapter.html
注意点
- Edgeでは、同一のカメラを複数のウィンドウ/タブで利用することができません。同一PCで通信する場合は、複数のカメラを用意してください
- Edgeでは現在のところSTUNはサポートされていない(TURNのみサポート)ため、今回のサンプルでNATを越えた通信はできません
次回は
今回はFirebaseを使ってシグナリングを実現しました。次回はNAT/Firewallを超えてのWebRTC通信についてお届けする予定です。