HTML5 WebRTCを使ったライブチャットを作ってみよう

WebRTCとは

WebRTCは、Web Real-Time Communicationの略で、WebブラウザだけでJavaScript APIを介してリアルタイムコミュニケーション機能を実現するためのAPIです。WebRTCでは、P2Pのコネクション確立からデータ通信を行うための仕様が定義されています。
よって、WebRTCを使うとプラグイン無しで、双方向のリアルタイムコミュニケーション機能を必要とするコンテンツが容易に開発できます。
現状は、W3Cでドラフトが公開されています。http://www.w3.org/TR/webrtc/
WebRTCはいくつかの関連するAPIがあります。その中には、前回紹介したMedia Stream APIも含まれます。
まだドラフトなのでWebRTCに対応ブラウザは、下記のようになっています。

PC

  • Google Chrome 23
  • Mozilla Firefox 22
  • Opera 12

Android

  • Google Chrome 28 (29 から標準で有効)
  • Mozilla Firefox 24
  • Opera Mobile 12

WebRTCの使い方

まずはWebRTCを接続手順を説明します。

最初にベンダープレフィクスのついているクラスがいくつかあるので、使えるかどうか確認します。

window.RTCPeerConnection = ( window.webkitPeerConnection00 ||
                             window.webkitRTCPeerConnection ||
                             window.mozRTCPeerConnection);

window.RTCSessionDescription = ( window.mozRTCSessionDescription ||
                                 window.RTCSessionDescription);

window.RTCIceCandidate = ( window.mozRTCIceCandidate ||
                           window.RTCIceCandidate);

次に接続用のwindow.RTCPeerConnectionを生成します。 今回ローカル接続なので、引数は無しです。

var pc1 = new RTCPeerConnection(); //送信側
var pc2 = new RTCPeerConnection(); //受信側

ICEの候補が取得できるイベントをハンドリングしておきます。 ICEの候補が見つかったら受信側に設定します。

pc1.onicecandidate = onIceCandidate1;

function onIceCandidate1(evt) {
  if (evt.candidate) {
    pc2.addIceCandidate(new RTCIceCandidate(evt.candidate));
  }
}

受信側にもICEの候補が取得できるイベントをハンドリングしておきます。 ICEの候補が見つかったら送信側に設定します。

pc2.onicecandidate = onIceCandidate2;

function onIceCandidate2(event){
  if (event.candidate) {
    pc1.addIceCandidate(new RTCIceCandidate(event.candidate));
  }
}

次に受信側は、ストリームを受け取った時のハンドリングを行います。

pc2.onaddstream = onRemoteStreamAdded

function onRemoteStreamAdded(event) {
  video2.src = URL.createObjectURL(event.stream);
}

次に送信側は、事前に取得しておいたMediaStreamを設定します。

pc1.addStream(localMediaStream);

これで準備が整ったので、オファーを作成して受信側に設定します。 受信側は、オファーに応答するためのアンサーを作成して送信側に設定します。

pc1.createOffer(gotOffer);

function gotOffer(description){
  pc1.setLocalDescription(description);

  pc2.setRemoteDescription(description);
  pc2.createAnswer(gotAnswer);
}

function gotAnswer(description){
  pc2.setLocalDescription(description);
  pc1.setRemoteDescription(description);
}

上記で説明した接続するまでのソースです。 この処理の前にMediaStreamを取得しておく必要があります。詳しくはこちら

var pc1;
var pc2;
function createPeerConnection() {
    pc1 = new RTCPeerConnection();
    pc1.onicecandidate = onIceCandidate1;

    pc2 = new RTCPeerConnection();
    pc2.onicecandidate = onIceCandidate2;
    pc2.onaddstream = onRemoteStreamAdded;

    pc1.addStream(localMediaStream);
    pc1.createOffer(gotOffer);
}

function onIceCandidate1(evt) {
  if (evt.candidate) {
    pc2.addIceCandidate(new RTCIceCandidate(evt.candidate));
  }
}

function onIceCandidate2(event){
  if (event.candidate) {
    pc1.addIceCandidate(new RTCIceCandidate(event.candidate));
  }
}

function onRemoteStreamAdded(event) {
  remoteVideo.src = URL.createObjectURL(event.stream);
}

function gotOffer(description){
  pc1.setLocalDescription(description);
  pc2.setRemoteDescription(description);
  pc2.createAnswer(gotAnswer);
}

function gotAnswer(description){
  pc2.setLocalDescription(description);
  pc1.setRemoteDescription(description);
}

ライブデモ

ローカルのみでRTCPeerConnectionを試すデモです。ライブデモ

サンプル:ライブチャット

WebRTCをつかったライブチャットを作成してみましょう。

事前準備

WebRTCは、インターネット超えたP2P接続を行うためにNAT越えするためのサーバが必要になります。これは、P2P通信を開始するためには、お互いの端末のグローバルアドレスを知る必要があるからです。
そのために必要なのが、ICE( Interactive Connectivity Establishment )と呼ばれる仕組みです。
ICEは、STUNTURNなどのNAT越えの手順をまとめたものです。ICEで取得したグローバルIPアドレスとポート番号を通信するホスト間で交換するとNAT越えすることが可能です。
STUNサーバやTURNサーバは、自作できますが、今回は、STUNサーバーとして、googleが公開しているサーバーを利用します。stun.l.google.com です。
そして、STUNサーバから受け取ったICE情報を受け渡しするためのWebアプリケーションが必要になります。今後チャットサーバと呼びます。

オファー側

まずは、googleが公開しているSTUNサーバーを定義しておきます。

var peerConnectionConfig = {
    "iceServers" : [
        { "url" : "stun:stun.l.google.com:19302" }
    ]
};

次にピアコネクション間のデータチャンネルを使用する設定を定義しておきます。

var peerDataConnectionConfig = {
    "optional" : [
        { "RtpDataChannels" : true }
    ]
};

チャットを開始する前に前回の解説したMediaStreamを取得しておきます。

function requireUserMedia() {
    navigator.getUserMedia({
        video : true
    }, gotUserMedia, error);
}

MediaStreamの取得成功のコールバックでWebRTCの設定を行います。
1.ピアコネクションの生成
2.メッセージ用のDataChannelの生成
3.オファーを生成します。

function gotUserMedia(stream) {
    localVideo.src = window.URL.createObjectURL(stream);
    localMediaStream = stream;

    createPeerConnection(); //1
    sendChannel = peerCon.createDataChannel("sendDataChannel", {reliable: false}); //2
    peerCon.createOffer(gotOffer, error); //3
}

ピアコネクションの生成する引数に、STUNサーバーを定義とデータチャンネルを使用する設定を指定します。

function createPeerConnection() {
    peerCon = new RTCPeerConnection(peerConnectionConfig,peerDataConnectionConfig);
    peerCon.onicecandidate = peerCon_onIceCandidate;
    peerCon.onaddstream = peerCon_onAddStream;
    peerCon.addStream(localMediaStream);
}

オファーが生成できたらピアコネクションのsetLocalDescriptionにオファーを設定しておきます。 それが成功したら、チャットサーバにオファーを送信します。

function gotOffer(description) {
    peerCon.setLocalDescription(description, function() {
        sendMessage({
            type : 'offer',
            offer : description
        });
    }, error);
}

アンサーが返ってきたらピアコネクションのsetRemoteDescriptionにアンサーを設定しておきます。 これでハンドシェイクが完了します。

function setupAnswer(answer) {
    peerCon.setRemoteDescription(new RTCSessionDescription(answer));
}

アンサー側

アンサー側も同様にMediaStreamの取得成功のコールバックでWebRTCのピアコネクションの生成しておきます。

そして、オファーを受け取るとピアコネクションのsetRemoteDescriptionにオファーを設定しておきます。 オファーを設定したらその応答となるアンサーを生成します。

function createAnswer(offer) {
    peerCon.setRemoteDescription(new RTCSessionDescription(offer));
    peerCon.createAnswer(gotAnswer, error);
}

アンサーの生成が成功したらピアコネクションのsetLocalDescriptionにオファーを設定しておきます。

function gotAnswer(answer) {
    peerCon.setLocalDescription(answer, function() {
        sendMessage({
            type : 'answer',
            answer : answer
        });
    }, error);
}

データ送受信

データ送受信するためには、データチャンネルを取得します。

sendChannel = peerCon.createDataChannel(...);

送信は、データチャンネルのsendメソッドつかって送信します。

sendChannel.send(value);

受信は、データチャンネルのonmessageのイベントで取得します。

sendChannel.onmessage = function (event) {
    console.log( event.data );
};

ライブチャットデモ

名称とルーム名を入れて参加ボタンをクリックしてください。1ルームにつき2人までは入れます。ライブチャットデモ

まとめ

今回は、WebRTCを使ってリアルタイムコミュニケーション機能をつかってみました。 まだ全てのブラウザでは使えませんが、サーバなしでP2P機能を使えるとなるとビデオ会議室にデータ交換などに応用してみてはいかがでしょうか。

また、WebRTCのAPIは、複雑なのでライブラリを使うことをお勧めします。

  • webRTC.io (https://github.com/webRTC/webRTC.io)
  • simpleWebRTC (https://github.com/HenrikJoreteg/SimpleWebRTC)