*

WebRTC + Node によるルーム機能付きビデオチャット 1

公開日: : 最終更新日:2016/11/06 投稿者:raru WebRTC, web技術

現在googleで調べても日本語では微妙にかゆいところに手が届かないWebRTCと
Socket.io率いるNodeを使ってルーム機能付きビデオチャットを作ってみました。
細かい制御等は無視していますが、備忘録。
対応ブラウザはchromeのみ!
※知識量も深さもないので、全体的にイメージで記してます。

機能

・ルーム制御: Socket.io
・入室処理: Socket.io
・文字チャット: Socket.io
・ビデオチャット: WebRTC

機能的には上記がメインになるかと思います。
通常の文字チャットであれば Socket.io のデフォルトの機能だけで全て網羅出来るので非常に簡単に作ることが出来ます。問題はWebRTCです。

以下、簡易なモジュール説明

メディアアクセス

ビデオチャットを作成する為に、まずはカメラとマイクにアクセスしなければなりません。
HTML5では便利なことにデバイスへアクセスする機能が用意されています。
Navigator.getUserMediaというものがそれにあたります。

デバイスへアクセスするには以下のようにします。

if (!navigator.mozGetUserMedia) {
    navigator.webkitGetUserMedia(defaultOption, bootMediaSuccess, bootMediaError);
} else {
    navigator.mozGetUserMedia(defaultOption, bootMediaSuccess, bootMediaError);
}

defaultOptionには以下のようなjsonを代入しています。

var defaultOption = { video : true,
                      audio : true }

trueになっているものが有効となるので、例えば音声が必要ない場合はaudioをfalseにすることで制御可能です。

bootMediaSuccessはメディアへのアクセスが成功した場合のコールバック関数
bootMediaErrorはメディアへのアクセスが失敗した場合のコールバック関数が指定されています。

メディアへのアクセスが成功すると、関数の第一引数に取得した映像/音声が返ってきます。
私は以下のようにコールバック関数を記載しています。

function bootMediaSuccess(stream){
    localStream = stream;
}

あとで使い回せるようにグローバルな変数として確保した感じです。
初期宣言はしてあるので、あくまでこのモジュール化された無名関数内でのグローバルとして(のつもり)ですが。

メディアへアクセスして映像/音声を取得することは以上で完了です。
余談ですが、getUserMedia系のメソッドは複数呼べば複数ストリームが取得可能ですが、毎回確認文言が表示されます。(当たり前ですね)

次にストリームを画面に表示するための処理です。
そのイメージが以下になります。

var videoTag = $('#video')[0];

if (navigator.mozGetUserMedia) {
    videoTag.mozSrcObject = stream;
    videoTag.play();
} else {
    var url = webkitURL.createObjectURL(stream);
    videoTag.src = url;
    videoTag.play();
} 

videoタグはjQueryオブジェクトのまま使っても使い辛いので、[0]で普通のオブジェクトとして取得しています。
また火狐とchromeではストリームの扱い方が違うため分岐処理が入っています。(火狐で動くかは知らない)
videoにストリームをセットしているということなのでしょうね。

これで取得したストリームを再生することが出来ます。

では、起動しているデバイスの停止方法です。
何気にここを説明してくれているサイトが少なくて面倒でした。

localStream.stop();

これで、カメラやマイクが停止します。
非常に簡単ですね。
stream.stop()っていうのがなんか気持ち悪いです。
僕が勝手にストリームだと思って(命名して)いるオブジェクトは実はストリームではないのでしょうか?

以上でビデオチャット作成に必要なデバイス制御を満たせるかと思います。

RTCPeerConnection

WebRTCのP2P通信を行なうコネクションオブジェクトです。
何かと面倒くさいヤツです。最近は慣れてきましたが、それでも面倒くさい罠にハマったりします。
※今現在の私の使用方法も正しくない可能性がかなり高いです。

関数群

まず、今回私が使用した関数を一覧します。
※コメントは僕が勝手にそうやって思っているだけのものです。

・webkitRTCPeerConnection
RTCPeerConnectionを生成する。

・onicecandidate
STUNサーバから応答が返って来たときに呼ばれるイベント

・onaddstream
P2Pが確立し通信相手がストリームをコネクションにセットした時に呼ばれるイベント

・onremovestream
通信相手がストリームを削除したときに呼ばれるイベント(一癖あり

・createOffer
通信したいです、っていうオファーを作成する(だいたいついでにサーバに投げる

・setLocalDescription
通信相手に送ったofferなどをセットする関数

・setRemoteDescription
相手から受け取ったanswerなどをセットする関数

・createAnswer
オファーを受け取ったと相手に伝える返答オブジェクトを作る関数

・addIceCandidate
受け取ったIcdCandidateを追加する関数

・addStream
コネクションにストリームを追加する関数

・removeStream
引数に指定されたストリームを削除する関数

・close
コネクションを閉じる

処理の流れ

簡単に僕のイメージしている処理の流れを説明します。

1. RTCPeerConnectionを生成する。
2. on系イベントを設定する
3. コネクションにストリームを追加する
4. オファーを作成する
5. setLocalDescriptionで作成したオファーをセットする
6. オファーを通信相手へ投げる
7. STUNサーバから取得したcandidate情報を通信相手へ投げる
8. 通信相手からアンサーを取得する
9. setRemoteDescriptionで相手から受け取ったアンサーをセットする。
10. 通信相手からcandidateを受け取りセットする。
11. 映像や音声が相手に届く
12. ストリームを削除する or デバイスを止める
13. 何故か着火しないonremovestream
14. オファーを通信相手に再度送信する
15. 何故か着火するonremovestream

ここで大切なのが ストリームを追加するタイミングです。
私は当初
「普通に考えてP2P通信が始まってから、そのコネクションに対してストリームを送り込むだろ」
と考えて addStreamをアンサーを受け取ってからなどにしていました。
しかし、それでは通信が確立しても映像/音声が届くことはありませんでした。
どうやらofferを投げる前にはストリームを追加しておいた方が良いようです。
具体的にはここを見ましょう。
WebRTCをやるのなら日本語は諦めましょう。

またSTUNサーバとやりとりが行なわれると呼び出される
onicecandidateですが、そもそもSTUNサーバとやり取りを開始するのはいつだよ、という疑問がありました。
いろいろ任意のタイミングでSTUNサーバにリクエストを投げる関数などを探していたのですが、見当たらず。
調べた結果、どうもsetLocalDescriptionがトリガーとなっているようでした。

なので、candidateを取得したいときはsetLocalDescriptionに対してオファーなりをセットしてあげましょう。
オファーをセットするタイミングを任意にすることで制御できそうです。
※それが正攻法なのかはまた別

そしてもう一つ大切なのが、相手のcandidateを受け取ることです。
僕がonicecandidateを着火するタイミングを制御したかったのもこの為です。
つまり、こちらのオファーが相手に届いた時点で相手にSTUNサーバに問い合わせてもらう必要があったのです。

ここで僕が実施した処理が以下
※僕はMyRTCPeerConnectionとかいうクラスを模倣したオブジェクトを使用しています。

webSocket.on('offer', function(offer) {
    connection.sendAnswer(JSON.parse(offer.data), offer.socketId, offer.connectionId);
    $("#info").append("<span>get offer</span><br/>");
    connection.setLocalDescription();
});

offerを受け取ったら、answerを返してからsetLocalDescriptionを呼び出すということです。
ここでセットするオファーは別にこの時投げるわけではないのですが、candidateが欲しいが為に呼んでいます。
さらに、この後こちらからもオファーを送りたくなった場合には新たに作り直したオファーをsetLocalDescriptionにセットしてから新しいオファーを投げるという処理をしています。

上記処理については何か違和感がハンパではないために正しい使い方ではない可能性がとっても高いですが、一応動くことは間違いないです。

ここまでで話した関数の雰囲気と処理の流れを理解? してもらえれば、実際に実装を行なうときにかなり楽になると思います。

また実装を行なうときはRTCPeerConnectionをラップしたMyPeerConnectionみたいなのを作ると楽でした。

疲れたので今日はここまで、続きは明日にでも。

関連記事

no image

要求されたアクションを実行するには、WordPress が Web サーバーにアクセスする必要があります。

wordpressの更新を行おうとした時の上記のエラーが発生した場合に、いくつか対策方法があります。

記事を読む

no image

Mac OS X (Yosemite) にLaravelをインストール

Max OS 10.10(yosemite)にPHPフレームワークのLaravelをインストールして

記事を読む

no image

Facebook APIを試してみました

Facebook APIが気になったのでちょっと試してみました。 まず私が勘違いしていたこと

記事を読む

no image

Laravelでbladeを利用したViewを作成

今回はLaravelで採用されているviewテンプレートのBladeを利用して画面を作成してみます。

記事を読む

no image

MAMPでLaravelを動作させる

先日laravelをMAMPのhtdocsに配置しましたが、当然それでは動かなかったので設定しました

記事を読む

no image

システムの仕様書を作れそうなウェブアプリ

現在サービス開発を本気で取り組もうと思っているraruです。 本気が指すところは、事業として成立す

記事を読む

no image

PHPのcURLを利用してAPIをGET/POSTで叩く

PHPからcURLを利用してhttpのget, postで問い合わせを行ってみます。 今回はYAH

記事を読む

no image

Node.jsのSocket.io入門

前置き WebRTCを使用して通信を行なうときに、offerなどの情報を伝える為にサーバサイドのプ

記事を読む

no image

Facebookから日本語でデータ取得 (※だたしlocationは除く

Facebook Graph APIを通して取得したデータがローマ字や英語で困ることがあります。

記事を読む

no image

PHPでXMLをnamespaceを利用したxpathでnode取得

PHPでxmlを読み込んでxpathでnodeを取得して見たいと思います。 今回はyahooの形態

記事を読む

Comment

  1. gtk2k より:

    >火狐とchromeではストリームの扱い方が違うため分岐処理が入っています。
     これは最新のFirefoxにおいても
     videoTag.src = URL.createObjectURL(stream);
     でOKですので分岐は必要ありません。
     (記事中で”webkitURL”を使用していますが、現在のすべての最新のブラウザーではプレフィックスが取れましたので”URL”でOKです)

    >13. 何故か着火しないonremovestream
     oaddstreamやonremovestreamのイベントは、オファーやアンサーを受け取ってから発生します。
     具体的にはセッションデスクリプション(SDP)のストリームの情報(a=ssrc:の行)を見て、
     新しいストリーム情報あればonaddstreamイベントを、
     前回のSDPにはあったストリーム情報が今回のSDPにはない場合は、
     onremovestreamイベントを発生させるようです。(ちょっと確信が持てませんw)
     
    *ちなみに
     W3Cのサンプルコードを見てみると
     サンプルコード
    にonnegotiationneededというイベントがありそこでcreateOfferを実行しています。
     このonnegotiationneededイベントは、addStreamやremoveStreamを行うと発生するイベントです。
     実は、接続を確立した後にもカメラを切り替えたり、マイクを切り替えたりすることができるのですが、
     切り替えるたびにオファー/アンサーのやり取りが必要です。
     この処理がこのイベントによりでひとつのコードで済みます。
     ただし、ChromeやOperaはこのイベントがサポートされているのですがまだFirefoxではサポートされてません。

    • raru より:

      gtk2kさん、コメントありがとうございます。
      非常にためになりました。

      やはり日々機能の修正や追加がされているんですね。恥ずかしながら僕は全然追い切れていないです。

      onremovestreamについて、英語の質問掲示板でnegotiationがどうのこうのとやりとりされていたのが今理解出来ました。

      そのような便利イベントがあったのですね。
      頂いた情報を元に今度修正等加えてみようと思います。

      本当にありがとうございます。

  2. Johnd499 より:

    I went over this site and I conceive you have a lot of wonderful information, bookmarked . cfebfbcegkeb

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

PAGE TOP ↑