がねこまさし

シグナリングを拡張して、複数人で通信してみよう ーWebRTC入門2016

連載: WebRTC入門2016 (4)

こんにちは! 2014年に連載した「WebRTCを使ってみよう!」シリーズのアップデート記事も4回目となり、佳境に入りました。前回の1対1の通信をベースに、今回はより実用的なビデオチャットを目指して複数人で通信可能なように拡張してみましょう。

複数人、複数会議室を目指して

前回作ったのは、1つのシグナリングサーバーに対して、同時に1ペアだけが利用できる仕組みでした。これを複数人で、複数会議室で利用できるようにしていきましょう。こちらの図の左のBeforeの状態から、右のAfterの状態を実現します。

rtc_11_to_nn

複数人で通信するためには

WebRTCはPeer-to-Peer (P2P) で通信する仕組みです。なので複数人と通信するためには、複数のRTCPeerConnectionを用意する必要があります。図にするとこんな感じです。

PeerConnection_multi

また、P2P通信を開始するためにSDP(Offer/Answer)を交換する必要がありますが、それぞれの相手ごとに生成、交換しなければなりません。

PeerConnection_sdp_multi

P2Pなので当然かもしれませんが、私は最初に複数の相手と通信しようとして混乱してしまいました。この考え方を踏まえていれば、あとは力技になります。

シグナリングサーバーの対応

シグナリングサーバーは前回の1対1の時と同じく、Node.jsを使って用意しましょう。複数会議室を実現するのに便利なため、今回はwsモジュールではなく、より高機能のsocket.ioを使います。Node.jsのインストールは終わっていると思うので、コマンドプロンプト/ターミナルから、次のコマンドを実行してください。 ※必要に応じて、sudoなどをご利用ください。

2014年にはsocket.ioはv0.9でしが、今回はv1.4.xになっています。

シグナリングサーバーのコードは前回の1対1と同様にメッセージを中継するのが役目ですが、接続してきたクライアントがルーム(会議室)に入室要求を送ってきたら、socket.ioのサーバ側でそのルームに join() してあげます。

クライアントからのメッセージ送信には、次の2つのパターンがあります。

  • ルーム内の他のメンバー全員(接続している他のクライアントすべて)に送る
  • 特定のメンバー(特定のクライアント)だけに送る

シグナリングサーバーでは、送信先が指定されていればその相手だけに、指定されていなければルーム内の全員(送信者以外)にメッセージを送ります。 ※その際に、送信元を特定できるID(socket.ioが管理しているID)を追加しています。

サーバーの全体のソースは次の通りです。これを例えば signaling_room.js というファイル名で保存します。

コマンドプロンプト/ターミナルから、 次のように起動してください。(ファイル名は適宜置き換えてくださいね)

クライアント側の拡張

次はクライアントとなるブラウザ側の処理を拡張していきます。

socket.io サーバーへの接続

今回はシグナリングサーバーを同じPCの3002番ポートで動かしていると想定します。HTMLファイルの先頭で、socket.ioのクライアント用のjsファイルを読み込みます。

クライアントではsocket.ioのサーバー (localhost:3002) に接続します。このとき、ws://~ ではなく、http://~ となることがWebSocketを直接利用した場合と異なります。

※実際のシグナリングサーバーの環境に合わせて、URLやポート番号は変更してください。

ルーム(会議室)への入室

クライアンではsocket.ioのサーバーに接続したら、希望のルームに入室を依頼します。ルーム名は今回はURLの後ろに ?部屋名 という形で指定することにしてみました。(お好きな方法で指定してください)

複数通信の流れ

1対1の時は相手が1人しかいない前提だったので、ただちにOffer SDP / Answer SDPを送信していました。今回は相手が何人いるか分からない状況からスタートしますし、相手ごとに個々にOffer SDP / Answer SDPを送受信する必要があります。そこで、相手を確認するやりとりを追加しました。図にすると、次のような流れになります。

multi_callme_simple

もう少し細かく見ると、次のような処理を行っています。

  • 新たに通信を開始したい人(ブラウザA)が、通信開始の合図のルーム内にブロードキャストする(“call me”)
  • 受け取った人(ブラウザB、ブラウザC)は、Offer SDPを生成して、ブラウザAを宛先に指定して送ります
    • RTCPeerConnectionを生成
    • RTCPeerConnection.createOffer()でOffer SDPを生成
    • setLocalDescription()で覚える
    • ブラウザA宛てに送信
  • ブラウザAは、Offer SDPを受け取り、Answer SDPを生成してそれぞれの相手に送り返します
    • 相手ごとにRTCPeerConnectionを生成
    • それぞれ受け取ったOffer SDPをRTCPeerConnection.setRemoteDescription()で覚える
    • RTCPeerConnection.createAnswer()でAnswer SDPを生成
    • setLocalDescription()で覚える
    • それぞれの相手にAnswer SDPを返信
  • ブラウザB、ブラウザCは、Answer SDPを受け取る
    • 受け取ったAnswer SDPをRTCPeerConnection.setRemoteDescription()で覚える

multi_callme

ソースの修正:複数通信の準備

それでは、ブラウザ側のソースも手を入れていきましょう。まず、複数のRTCPeerConnectionを扱えるように用意します。

相手の映像を表示するvideoタグも、動的に生成して複数管理できるようにします。

さらにRTCPeerConnectionの接続や、相手からのメディアストリーム、videoタグを連動して扱う処理も追加しておきましょう。

ソースの修正:シグナリングの変更

WebSocket直接利用から、socket.ioの利用に変わったので、シグナリングも変更します。

通信開始要求の”call me”や、切断要求の”bye”の処理を追加しています。また接続準備が整っていない場合(カメラやマイクを取得していない場合)や、すでに接続中の相手からの接続要求は無視するようにしています。

SDPやICE candidateのハンドリング

複数の相手とOffer/Answer SDPやICE candidateをやり取りするので、相手を意識した処理に拡張します。といっても違いは対応するRTCPeerConnectionのオブジェクトを生成したり取り出したりするところだけで、他は1対1の場合と同様です。すべてを掲載すると長いので全体はGitHubを参照してくただくとして、ここでは例を取り上げます。

Offer SDPの送信

新たに RTCPeerConnectionを生成し、Offer SDPの作成、送信を行います。

Answer SDPを受け取った場合の処理

対応するRTCPeerConnectionを取り出し、Answer SDPを覚えさせます。

ICE candidateのやりとりなども、同様に対応するRTCPeerConnectionのオブジェクトを取り出して行います。また、手動シグナリングで使っていたSDPをテキストエリアに表示する部分も取り除きました。

カメラ、マイクの取得

前回までは映像だけでしたが、今回はマイクの音声も取得してより実用的にしましょう。ただし1台のPCでやる場合はハウリングしてしまうので、ヘッドフォンを使ってください。

getDeviceStream()は、新旧getUserMedia()をラップするために用意した関数です。これにaudio:trueの指定を渡してマイクも取得しています。

今回マイクの音声も取得するようにしたため、メディアストリームにはビデオとオーディオの2つのトラックが含まれます。このため、相手側のRTCPeerConnectionontrack()イベントが2回呼び出されますが、2回目は無視するように修正しました。(RTCPeerConnection.ontrack()は現在Firefoxのみがサポートしています)

全体のソース

主要な部分は以上の通りですが、他にも細かい修正があります。全体のソースはGitHubでご覧ください。

接続してみよう

今回のサンプルでは4人まで同時に通信できるようにしてみました。それぞれブラウザを立ち上げて[Start Video]→[Connect]の順にボタンをクリックし接続してください。このように複数の相手と接続できるはずです。

multi_connected_4

※localhost以外に繋ぐときは、Chromeではカメラ/マイクの取得に失敗します。その場合はFirefoxをご利用ください。

次回は

今回のサンプルでは複数人で同時にビデオチャットができるようにしました。が、Chromeではhttp://~でgetUserMedia()が許可されていないため、他のPCと通信するのは厄介です。

Webサーバだけであれば、GitHub Pagesなどでhttps://~を利用することができますが、シグナリングサーバーのWebSocket通信も暗号化する必要がありまり、そちらは自分で証明書を取って対処しなけばなりません。

そこで次回は番外編として、Firebaseを使った暗号化通信をシグナリングに利用した例をご紹介したいと思います。

Powered byNTT Communications

tag list

アクセシビリティ イベント エンタープライズ デザイン ハイブリッド パフォーマンス ブラウザ プログラミング マークアップ モバイル 海外 高速化 Angular2 AngularJS Chrome Cordova CSS de:code ECMAScript Edge Firefox Google Google I/O 2014 HTML5 Conference 2013 html5j IoT JavaScript Microsoft Mozilla Node.js PhoneGap Polymer React Safari SkyWay TypeScript UI UX W3C W3C仕様 Webアプリ Web Components WebGL WebRTC WebSocket