WebRTC アプリに必要なバックエンド サービスを構築する

シグナリングとは何ですか?

シグナリングとは、通信を調整するプロセスのことです。WebRTC アプリで通話を設定するには、クライアントが次の情報を交換する必要があります。

  • 通信の開始または終了に使用されるセッション制御メッセージ
  • エラー メッセージ
  • メディア メタデータ(コーデック、コーデック設定、帯域幅、メディアタイプなど)
  • 安全な接続を確立するために使用される鍵データ
  • ネットワーク データ(外部に提示されるホストの IP アドレスとポートなど)

このシグナリング プロセスでは、クライアントがメッセージをやり取りする方法が必要です。このメカニズムは WebRTC API では実装されていません。自分で構築する必要があります。この記事の後半では、シグナリング サービスを構築する方法について説明します。ただし、まず背景情報を少しご説明しましょう。

シグナリングが WebRTC で定義されていないのはなぜですか?

冗長性を回避し、確立されたテクノロジーと最大限の互換性を確保するため、シグナリング メソッドとプロトコルは WebRTC 標準で指定されていません。このアプローチは、JavaScript セッション確立プロトコル(JSEP)で概説されています。

JSEP のアーキテクチャでは、ブラウザが状態を保存する(つまり、シグナリング ステートマシンとして機能する)必要もありません。たとえば、ページが再読み込みされるたびにシグナリング データが失われると、問題が発生します。代わりに、シグナリング状態をサーバーに保存できます。

JSEP アーキテクチャ図
JSEP アーキテクチャ

JSEP では、上記のメディア メタデータであるofferanswer をピア間で交換する必要があります。オファーとアンサーは、次のようなセッション記述プロトコル(SDP)形式で通信されます。

v=0
o=- 7614219274584779017 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:W2TGCZw2NZHuwlnf
a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=mid:audio
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe
a=rtpmap:111 opus/48000/2

SDP の専門用語が何を意味するのか知りたいですか?Internet Engineering Task Force(IETF)の例をご覧ください。

WebRTC は、SDP テキストの値を編集してローカルまたはリモートの説明として設定する前に、オファーまたは回答を調整できるように設計されています。たとえば、appr.tcpreferAudioCodec() 関数を使用して、デフォルトのコーデックとビットレートを設定できます。SDP は JavaScript で操作するのがやや面倒で、今後のバージョンの WebRTC で JSON を使用するべきかどうかについて議論されていますが、SDP を使い続けることにはいくつかの利点があります。

RTCPeerConnection API とシグナリング: オファー、回答、候補

RTCPeerConnection は、ピア間の接続の作成と音声と動画の通信に WebRTC アプリが使用する API です。

このプロセスを初期化するために、RTCPeerConnection には次の 2 つのタスクがあります。

  • 解像度やコーデックの機能など、ローカル メディアの状態を確認します。これは、オファーと回答のメカニズムに使用されるメタデータです。
  • アプリのホストの候補となるネットワーク アドレスを取得します。

このローカルデータを確認したら、シグナリング メカニズムを通じてリモートピアと交換する必要があります。

アリスがイブに電話をかけようとしているとします。オファー/応答のメカニズムの詳細は次のとおりです。

  1. アリスは RTCPeerConnection オブジェクトを作成します。
  2. Alice は RTCPeerConnection createOffer() メソッドを使用してオファー(SDP セッション記述)を作成します。
  3. Alice は setLocalDescription() に電話をかけ、オファーを伝えます。
  4. Alice はオファーを文字列化し、シグナリング メカニズムを使用して Eve に送信します。
  5. イブはアリスのオファーを setRemoteDescription() に呼び出して、RTCPeerConnection がアリスの設定を把握できるようにします。
  6. Eve が createAnswer() を呼び出すと、この成功コールバックにローカル セッションの説明(Eve の回答)が渡されます。
  7. Eve は setLocalDescription() を呼び出して、回答をローカルの説明として設定します。
  8. その後、イブはシグナリング メカニズムを使用して、文字列化された回答をアリスに送信します。
  9. Alice は setRemoteDescription() を使用して、Eve の回答をリモート セッションの説明として設定します。

アリスとイブはネットワーク情報を交換する必要があります。「候補の検出」という表現は、ICE フレームワークを使用してネットワーク インターフェースとポートを見つけるプロセスを指します。

  1. Alice は、onicecandidate ハンドラを使用して RTCPeerConnection オブジェクトを作成します。
  2. ハンドラは、ネットワーク候補が利用可能になったときに呼び出されます。
  3. ハンドラで、Alice はシグナリング チャネルを介して文字列化された候補データを Eve に送信します。
  4. Eve が Alice から候補メッセージを受信すると、addIceCandidate() を呼び出して候補をリモートピアの説明に追加します。

JSEP は ICE 候補のトリクルをサポートしています。これにより、呼び出し元は最初のオファーの後に候補を段階的に呼び出し先に提供でき、呼び出し先はすべての候補が到着するのを待たずに通話の処理を開始して接続をセットアップできます。

シグナリング用に WebRTC をコード化する

次のコード スニペットは、シグナリング プロセス全体をまとめた W3C コード例です。このコードは、なんらかのシグナリング メカニズム(SignalingChannel)が存在することを前提としています。シグナリングについては後で詳しく説明します。

// handles JSON.stringify/parse
const signaling = new SignalingChannel();
const constraints = {audio: true, video: true};
const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
const pc = new RTCPeerConnection(configuration);

// Send any ice candidates to the other peer.
pc.onicecandidate = ({candidate}) => signaling.send({candidate});

// Let the "negotiationneeded" event trigger offer generation.
pc.onnegotiationneeded = async () => {
  try {
    await pc.setLocalDescription(await pc.createOffer());
    // send the offer to the other peer
    signaling.send({desc: pc.localDescription});
  } catch (err) {
    console.error(err);
  }
};

// After remote track media arrives, show it in remote video element.
pc.ontrack = (event) => {
  // Don't set srcObject again if it is already set.
  if (remoteView.srcObject) return;
  remoteView.srcObject = event.streams[0];
};

// Call start() to initiate.
async function start() {
  try {
    // Get local stream, show it in self-view, and add it to be sent.
    const stream =
      await navigator.mediaDevices.getUserMedia(constraints);
    stream.getTracks().forEach((track) =>
      pc.addTrack(track, stream));
    selfView.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}

signaling.onmessage = async ({desc, candidate}) => {
  try {
    if (desc) {
      // If you get an offer, you need to reply with an answer.
      if (desc.type === 'offer') {
        await pc.setRemoteDescription(desc);
        const stream =
          await navigator.mediaDevices.getUserMedia(constraints);
        stream.getTracks().forEach((track) =>
          pc.addTrack(track, stream));
        await pc.setLocalDescription(await pc.createAnswer());
        signaling.send({desc: pc.localDescription});
      } else if (desc.type === 'answer') {
        await pc.setRemoteDescription(desc);
      } else {
        console.log('Unsupported SDP type.');
      }
    } else if (candidate) {
      await pc.addIceCandidate(candidate);
    }
  } catch (err) {
    console.error(err);
  }
};

オファー/回答と候補交換プロセスの動作を確認するには、simpl.info RTCPeerConnection を参照し、シングルページのビデオ チャットの例のコンソールログをご覧ください。詳細を確認するには、Google Chrome の about://webrtc-internals ページまたは Opera の opera://webrtc-internals ページから、WebRTC シグナリングと統計情報の完全なダンプファイルをダウンロードします。

ピアの検出

「相談できる人を探すにはどうすればよいですか?」という質問を、別の言い方で尋ねている場合です。

電話の場合は、電話番号と電話帳があります。オンラインのビデオ チャットやメッセージングには、ID とプレゼンス管理システム、ユーザーがセッションを開始する手段が必要です。WebRTC アプリでは、クライアントが通話の開始または参加を相互に通知する方法が必要です。

ピア検出メカニズムは WebRTC で定義されていないため、ここではオプションについて説明しません。プロセスは、URL をメールまたはメッセージで送信するだけという簡単なものにできます。Talkytawk.toブラウザ会議などのビデオチャット アプリでは、カスタム リンクを共有して通話にユーザーを招待します。デベロッパーの Chris Ball は、WebRTC 通話の参加者が任意のメッセージ サービス(IM、メール、ハトポストなど)でメタデータを交換できるようにする、興味深い serverless-webrtc 実験を構築しました。

シグナリング サービスを構築する方法

繰り返しになりますが、シグナリング プロトコルとメカニズムは WebRTC 標準で定義されていません。どちらを選択する場合でも、クライアント間でシグナリング メッセージとアプリデータを交換するための中間サーバーが必要です。残念ながら、ウェブアプリは「友達とつなげて」とインターネットに大声で叫ぶことはできません。

幸い、シグナリング メッセージは小さく、ほとんどの場合、通話の開始時に交換されます。appr.tc で動画チャット セッションをテストしたところ、シグナリング サービスによって合計 30 ~ 45 件のメッセージが処理され、すべてのメッセージの合計サイズは約 10 KB でした。

WebRTC シグナリング サービスは、帯域幅の面で比較的負荷が軽いだけでなく、メッセージのリレーと、接続されているクライアントなどの少量のセッション状態データの保持のみを行うため、処理やメモリをあまり消費しません。

サーバーからクライアントにメッセージをプッシュする

シグナリング用のメッセージ サービスは、クライアントからサーバー、サーバーからクライアントの双方向である必要があります。双方向通信は HTTP クライアント/サーバーのリクエスト/レスポンス モデルに反しますが、ウェブサーバーで実行されているサービスからブラウザで実行されているウェブアプリにデータをプッシュするために、長年にわたって ロングポーリングなどのさまざまなハックが開発されてきました。

最近では、EventSource API広く実装されています。これにより、サーバー送信イベント(HTTP 経由でウェブサーバーからブラウザ クライアントに送信されるデータ)が可能になります。EventSource は単方向のメッセージング用に設計されていますが、XHR と組み合わせて使用することで、シグナリング メッセージを交換するサービスを構築できます。シグナリング サービスは、XHR リクエストによって配信された呼び出し元からのメッセージを、EventSource を介して呼び出し先に push します。

WebSocket は、全二重のクライアント / サーバー通信(メッセージが同時に双方向に送信される)用に設計された、より自然なソリューションです。純粋な WebSocket またはサーバー送信イベント(EventSource)で構築されたシグナリング サービスの利点の 1 つは、これらの API のバックエンドを、PHP、Python、Ruby などの言語のほとんどのウェブ ホスティング パッケージに共通するさまざまなウェブ フレームワークで実装できることです。

Opera Mini を除くすべての最新ブラウザは WebSocket をサポートしています。さらに重要なのは、WebRTC をサポートするすべてのブラウザは、デスクトップとモバイルの両方で WebSocket もサポートしていることです。すべての接続で TLS を使用すると、メッセージが暗号化されていない状態で傍受されないようにし、プロキシ トラバーサルの問題を軽減できます。(WebSocket とプロキシ トラバーサルの詳細については、Ilya Grigorik の High Performance Browser NetworkingWebRTC の章をご覧ください)。

WebRTC クライアントに Ajax を介してメッセージ サーバーを繰り返しポーリングさせることでシグナリングを処理することもできますが、その場合、ネットワーク リクエストが大量に重複するため、モバイル デバイスでは特に問題になります。セッションが確立された後も、他のピアによる変更やセッションの終了に備えて、ピアはシグナリング メッセージをポーリングする必要があります。WebRTC Book アプリの例では、このオプションを使用して、ポーリング頻度を最適化しています。

シグナリングのスケーリング

シグナリング サービスは、クライアントあたりの帯域幅と CPU を比較的少量消費しますが、人気のあるアプリのシグナリング サーバーは、さまざまなロケーションからの大量のメッセージを高い同時実行レベルで処理しなければならない場合があります。トラフィックの多い WebRTC アプリには、かなりの負荷を処理できるシグナリング サーバーが必要です。ここでは詳しく説明しませんが、大規模で高パフォーマンスのメッセージングには、次のようなオプションがあります。

  • 拡張メッセージングとプレゼンス プロトコル(XMPP)(元々は Jabber と呼ばれていました)は、シグナリングに使用できるインスタント メッセージング用に開発されたプロトコルです(サーバー実装には ejabberdOpenfire があります)。Strophe.js などの JavaScript クライアントは、BOSH を使用して双方向ストリーミングをエミュレートしますが、さまざまな理由により、BOSH は WebSocket ほど効率的ではなく、同じ理由でスケーリングがうまくいかない可能性があります。(余談ですが、Jingle は音声と動画を有効にする XMPP 拡張機能です。WebRTC プロジェクトは、Jingle の C++ 実装である libjingle ライブラリのネットワーク コンポーネントとトランスポート コンポーネントを使用します)。

  • オープンソース ライブラリ(ZeroMQ(TokBox の Rumour サービスで使用)や OpenMQ など)。NullMQ は、WebSocket を介した STOMP プロトコルを使用して、ZeroMQ のコンセプトをウェブ プラットフォームに適用します。

  • WebSocket を使用する商用クラウド メッセージング プラットフォーム(ただし、長時間ポーリングにフォールバックする場合があります)。PusherKaazingPubNub など(PubNub には WebRTC 用の API もあります)。

  • 商用 WebRTC プラットフォーム(vLine など)

(デベロッパーの Phil Leggetter による リアルタイム ウェブ技術ガイドには、メッセージング サービスとライブラリの包括的なリストが掲載されています)。

Node で Socket.io を使用してシグナリング サービスを構築する

以下は、NodeSocket.io で構築されたシグナリング サービスを使用する簡単なウェブアプリのコードです。Socket.io の設計により、メッセージを交換するサービスを簡単に構築できます。また、Socket.io には部屋のコンセプトが組み込まれているため、WebRTC シグナリングに特に適しています。この例は、本番環境グレードのシグナリング サービスとしてスケーリングするように設計されていません。ただし、比較的少数のユーザーに対しては理解しやすいものです。

Socket.io は、WebSocket と AJAX ロングポーリング、AJAX マルチパート ストリーミング、Forever Iframe、JSONP ポーリングのフォールバックを使用します。さまざまなバックエンドに移植されていますが、この例で使用されている Node バージョンで最もよく知られています。

この例には WebRTC はありません。このサンプルは、ウェブアプリにシグナリングを組み込む方法を示すことを目的としています。クライアントが部屋に参加してメッセージを交換する際に何が起こっているかを確認するには、コンソール ログを表示します。この WebRTC Codelab では、これを完全な WebRTC ビデオチャット アプリに統合する方法について、手順を追って説明します。

クライアント index.html は次のとおりです。

<!DOCTYPE html>
<html>
  <head>
    <title>WebRTC client</title>
  </head>
  <body>
    <script src='/socket.io/socket.io.js'></script>
    <script src='js/main.js'></script>
  </body>
</html>

クライアントで参照される JavaScript ファイル main.js は次のとおりです。

const isInitiator;

room = prompt('Enter room name:');

const socket = io.connect();

if (room !== '') {
  console.log('Joining room ' + room);
  socket.emit('create or join', room);
}

socket.on('full', (room) => {
  console.log('Room ' + room + ' is full');
});

socket.on('empty', (room) => {
  isInitiator = true;
  console.log('Room ' + room + ' is empty');
});

socket.on('join', (room) => {
  console.log('Making request to join room ' + room);
  console.log('You are the initiator!');
});

socket.on('log', (array) => {
  console.log.apply(console, array);
});

サーバーアプリ全体は次のようになります。

const static = require('node-static');
const http = require('http');
const file = new(static.Server)();
const app = http.createServer(function (req, res) {
  file.serve(req, res);
}).listen(2013);

const io = require('socket.io').listen(app);

io.sockets.on('connection', (socket) => {

  // Convenience function to log server messages to the client
  function log(){
    const array = ['>>> Message from server: '];
    for (const i = 0; i < arguments.length; i++) {
      array.push(arguments[i]);
    }
      socket.emit('log', array);
  }

  socket.on('message', (message) => {
    log('Got message:', message);
    // For a real app, would be room only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', (room) => {
    const numClients = io.sockets.clients(room).length;

    log('Room ' + room + ' has ' + numClients + ' client(s)');
    log('Request to create or join room ' + room);

    if (numClients === 0){
      socket.join(room);
      socket.emit('created', room);
    } else if (numClients === 1) {
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room);
    } else { // max two clients
      socket.emit('full', room);
    }
    socket.emit('emit(): client ' + socket.id +
      ' joined room ' + room);
    socket.broadcast.emit('broadcast(): client ' + socket.id +
      ' joined room ' + room);

  });

});

(このため、node-static について学習する必要はありません。この例では使用されていますが、必ずしも使用する必要はありません)。

このアプリを localhost で実行するには、Node、Socket.IO、node-static がインストールされている必要があります。Node は Node.js からダウンロードできます(インストールは簡単で迅速です)。Socket.IO と node-static をインストールするには、アプリ ディレクトリのターミナルから Node Package Manager を実行します。

npm install socket.io
npm install node-static

サーバーを起動するには、アプリ ディレクトリのターミナルから次のコマンドを実行します。

node server.js

ブラウザで localhost:2013 を開きます。任意のブラウザで新しいタブまたはウィンドウを開き、localhost:2013 をもう一度開きます。状況を確認するには、コンソールをチェックします。Chrome と Opera では、Google Chrome デベロッパー ツールの Ctrl+Shift+J(Mac では Command+Option+J)を使用してコンソールにアクセスできます。

シグナリングにどのアプローチを選択しても、バックエンド アプリとクライアント アプリは、少なくともこの例のようなサービスを提供する必要があります。

シグナリングの落とし穴

  • setLocalDescription() が呼び出されるまでは、RTCPeerConnection は候補の収集を開始しません。これは JSEP IETF ドラフトで義務付けられています。
  • Trickle ICE を活用する。応募者が到着したらすぐに addIceCandidate() を呼び出します。

既製のシグナリング サーバー

独自のサーバーを構築しない場合は、いくつかの WebRTC シグナリング サーバーが利用できます。これらのサーバーは、前述の例のように Socket.IO を使用し、WebRTC クライアント JavaScript ライブラリと統合されています。

  • webRTC.io は、WebRTC 向けの最初の抽象化ライブラリの 1 つです。
  • Signalmaster は、SimpleWebRTC JavaScript クライアント ライブラリで使用するために作成されたシグナリング サーバーです。

コードをまったく記述したくない場合は、vLineOpenTokAsterisk などの企業から、完全な商用 WebRTC プラットフォームを利用できます。

なお、Ericsson は WebRTC の初期に、Apache で PHP を使用したシグナリング サーバーを構築しました。これは現在ではやや時代遅れですが、同様のものを検討している場合はコードを確認することをおすすめします。

シグナリングのセキュリティ

「セキュリティとは、何も起こらないようにする技術である。」

Salman Rushdie

暗号化は、すべての WebRTC コンポーネントに必須です。

ただし、シグナリング メカニズムは WebRTC 標準で定義されていないため、シグナリングを安全に行うかどうかは開発者の判断に委ねられます。攻撃者がシグナリングを不正使用すると、セッションを停止したり、接続をリダイレクトしたり、コンテンツを記録、変更、挿入したりできます。

シグナリングを保護するうえで最も重要な要素は、HTTPS と WSS(TLS など)などの安全なプロトコルを使用することです。これにより、メッセージが暗号化されていない状態で傍受されることがなくなります。また、同じシグナリング サーバーを使用する他の呼び出し元がアクセスできる方法でシグナリング メッセージをブロードキャストしないように注意してください。

シグナリング後: ICE を使用して NAT とファイアウォールに対応する

メタデータ シグナリングでは、WebRTC アプリは中間サーバーを使用しますが、セッションが確立された後の実際のメディアとデータのストリーミングでは、RTCPeerConnection はクライアントを直接接続するか、ピアツーピアで接続しようとします。

単純に考えると、すべての WebRTC エンドポイントには、他のピアと交換して直接通信できる一意のアドレスがあります。

シンプルなピアツーピア接続
NAT とファイアウォールのない世界

実際、ほとんどのデバイスは 1 つ以上の NAT レイヤの背後に存在し、一部のデバイスには特定のポートやプロトコルをブロックするウイルス対策ソフトウェアが搭載されており、多くのデバイスはプロキシや企業のファイアウォールの背後に存在します。ファイアウォールと NAT は、実際には同じデバイス(家庭用 Wi-Fi ルーターなど)で実装される場合があります。

NAT とファイアウォールの背後にあるピア
現実世界

WebRTC アプリは ICE フレームワークを使用して、現実のネットワーキングの複雑さを克服できます。これを実現するには、この記事で説明するように、アプリが ICE サーバー URL を RTCPeerConnection に渡す必要があります。

ICE は、ピアを接続するための最適なパスを見つけようとします。すべての可能性を並行して試し、機能する最も効率的なオプションを選択します。ICE はまず、デバイスのオペレーティング システムとネットワーク カードから取得したホストアドレスを使用して接続を試みます。これが失敗した場合(NAT の背後にあるデバイスでは失敗します)、ICE は STUN サーバーを使用して外部アドレスを取得します。これが失敗した場合、トラフィックは TURN リレー サーバー経由で転送されます。

つまり、STUN サーバーは外部ネットワーク アドレスの取得に使用され、TURN サーバーは直接(ピアツーピア)接続に失敗した場合のトラフィックのリレーに使用されます。

すべての TURN サーバーは STUN をサポートしています。TURN サーバーは、リレー機能が組み込まれた STUN サーバーです。ICE は、NAT 設定の複雑さにも対応します。実際には、NAT ホールパンチにはパブリック IP:ポート アドレス以外のものが必要になる場合があります。

STUN サーバーまたは TURN サーバーの URL は、RTCPeerConnection コンストラクタの最初の引数である iceServers 構成オブジェクトで WebRTC アプリによって(省略可)指定されます。appr.tc の場合、その値は次のようになります。

{
  'iceServers': [
    {
      'urls': 'stun:stun.l.google.com:19302'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=udp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=tcp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    }
  ]
}

RTCPeerConnection がその情報を取得すると、ICE マジックが自動的に実行されます。RTCPeerConnection は ICE フレームワークを使用してピア間の最適なパスを計算し、必要に応じて STUN サーバーや TURN サーバーと連携します。

STUN

NAT は、プライベート ローカル ネットワーク内で使用するための IP アドレスをデバイスに提供しますが、このアドレスは外部では使用できません。パブリック アドレスがないと、WebRTC ピアは通信できません。この問題を回避するため、WebRTC は STUN を使用します。

STUN サーバーは一般公開されているインターネット上に存在し、1 つの単純なタスクを実行します。それは、(NAT の背後で実行されているアプリからの)受信リクエストの IP:ポート アドレスを確認し、そのアドレスをレスポンスとして返すことです。つまり、アプリは STUN サーバーを使用して、パブリック ビューから IP:ポートを検出します。このプロセスにより、WebRTC ピアは一般公開されているアドレスを取得し、シグナリング メカニズムを通じて別のピアに渡して、直接リンクを設定できます。(実際には、NAT はそれぞれ異なる方法で動作し、複数の NAT レイヤが存在する場合もありますが、原則は同じです)。

STUN サーバーは多くの処理や記憶を行う必要がないため、比較的低スペックの STUN サーバーであっても大量のリクエストを処理できます。

ほとんどの WebRTC 呼び出しは STUN を使用して正常に接続します(Webrtcstats.com によると 86%)。ただし、ファイアウォールの背後にあるピア間の呼び出しや複雑な NAT 構成では、この割合が低くなる可能性があります。

STUN サーバーを使用したピアツーピア接続
STUN サーバーを使用してパブリック IP:port アドレスを取得する

TURN

RTCPeerConnection は、UDP を介したピア間の直接通信を試みます。失敗した場合、RTCPeerConnection は TCP にフォールバックします。これが失敗した場合は、TURN サーバーを代替手段として使用し、エンドポイント間でデータをリレーできます。

繰り返しになりますが、TURN はシグナリング データではなく、ピア間で音声、動画、データ ストリーミングをリレーするために使用されます。

TURN サーバーはパブリック アドレスを持っているため、ピアがファイアウォールやプロキシの背後にある場合でも、ピアから接続できます。TURN サーバーのタスクは、概念的には単純で、ストリームをリレーすることです。ただし、STUN サーバーとは異なり、NAT サーバーでは多くの帯域幅が消費されます。つまり、TURN サーバーはより強力である必要があります。

STUN サーバーを使用したピアツーピア接続
フルモンティ: STUN、TURN、シグナリング

この図は、TURN の動作を示しています。ピュア STUN が成功しなかったため、各ピアは TURN サーバーを使用します。

STUN サーバーと TURN サーバーのデプロイ

テスト用に、appr.tc で使用されているパブリック STUN サーバー stun.l.google.com:19302 を Google が実行しています。本番環境の STUN/TURN サービスの場合は、rfc5766-turn-server を使用します。STUN サーバーと TURN サーバーのソースコードは GitHub で入手できます。また、サーバーのインストールに関する情報源へのリンクも GitHub で確認できます。Amazon Web Services 用の VM イメージも使用できます。

別の TURN サーバーとして restund があります。これは、ソースコードとして、また AWS 用にも利用できます。Compute Engine で restund を設定する手順は次のとおりです。

  1. tcp=443、udp/tcp=3478 に応じてファイアウォールを開きます。
  2. パブリック IP ごとに 1 つずつ、Standard Ubuntu 12.06 イメージのインスタンスを 4 つ作成します。
  3. ローカル ファイアウォール構成を設定します(任意のホストからの任意の接続を許可します)。
  4. ツールをインストールします。 shell sudo apt-get install make sudo apt-get install gcc
  5. creytiv.com/re.html から libre をインストールします。
  6. creytiv.com/restund.html から restund を取得して解凍します。
  7. wget hancke.name/restund-auth.patch を取得し、patch -p1 < restund-auth.patch で適用します。
  8. makesudo make install を実行して、libre と restund を取得します。
  9. restund.conf をニーズに合わせて調整し(IP アドレスを置き換えて、同じ共有シークレットを含めるようにします)、/etc にコピーします。
  10. restund/etc/restund/etc/init.d/ にコピーします。
  11. 払い戻しを構成します。
    1. LD_LIBRARY_PATH を設定します。
    2. restund.conf/etc/restund.conf にコピーします。
    3. 適切な 10 を使用するように restund.conf を設定します。IPアドレスが割り当てられます
  12. 払い戻しを実行する
  13. リモートマシンからスタンド クライアントを使用してテストします。./client IP:port

1 対 1 を超えて: マルチパーティ WebRTC

TURN サービスへのアクセス用の REST API に関する Justin Uberti の提案した IETF 標準もご覧ください。

単純な 1 対 1 の呼び出しを超えるメディア ストリーミングのユースケースは簡単に想像できます。たとえば、同僚のグループ間のビデオ会議や、1 人のスピーカーと数百人または数百万人の視聴者がいる公開イベントなどです。

WebRTC アプリは複数の RTCPeerConnection を使用できるため、すべてのエンドポイントがメッシュ構成内の他のすべてのエンドポイントに接続できます。これは talky.io などのアプリで採用されているアプローチであり、少数のピアに非常に適しています。それを超えると、特にモバイル クライアントで、処理と帯域幅の消費量が過剰になります。

メッシュ: 小規模な N 者通話
フルメッシュ トポロジ: すべてのノードが相互接続されている

また、WebRTC アプリは、スター構成で 1 つのエンドポイントを選択して、他のすべてのエンドポイントにストリームを配信することもできます。サーバーで WebRTC エンドポイントを実行し、独自の再配布メカニズムを構築することもできます(サンプル クライアント アプリは webrtc.org から提供されています)。

Chrome 31 と Opera 18 以降では、1 つの RTCPeerConnectionMediaStream を別の RTCPeerConnection の入力として使用できます。これにより、接続する他のピアを選択して、ウェブアプリが呼び出しルーティングを処理できるため、より柔軟なアーキテクチャを実現できます。実際の動作を確認するには、WebRTC サンプルのピア接続リレーWebRTC サンプルの複数のピア接続をご覧ください。

マルチポイント コントロール ユニット

エンドポイントの数が多い場合は、マルチポイント制御ユニット(MCU)を使用することをおすすめします。これは、多数の参加者間でメディアを配信するブリッジとして機能するサーバーです。MCU は、ビデオ会議でさまざまな解像度、コーデック、フレームレートに対応し、トランスコーディングを処理し、選択的なストリーム転送を行い、音声と動画をミックスまたは録画できます。マルチパーティ通話では、特に複数の動画入力を表示し、複数のソースの音声をミックスする方法など、考慮すべき問題がいくつかあります。vLine などのクラウド プラットフォームも、トラフィック ルーティングの最適化を試みます。

MCU ハードウェア パッケージを丸ごと購入することも、自分で作成することもできます。

Cisco MCU5300 の背面
Cisco MCU の背面

利用可能なオープンソースの MCU ソフトウェア オプションはいくつかあります。たとえば、Licode(旧称 Lynckia)は、WebRTC 用のオープンソース MCU を生成します。OpenTok には Mantis があります。

ブラウザ以外: VoIP、電話、メッセージング

WebRTC は標準化されているため、ブラウザで実行されている WebRTC アプリと、電話やビデオ会議システムなどの別の通信プラットフォームで実行されているデバイスやプラットフォームとの間で通信を確立できます。

SIP は、VoIP システムとビデオ会議システムで使用されるシグナリング プロトコルです。WebRTC ウェブアプリと SIP クライアント(ビデオ会議システムなど)間の通信を有効にするには、WebRTC にシグナリングを仲介するプロキシ サーバーが必要な場合があります。シグナリングはゲートウェイを経由する必要がありますが、通信が確立されると、SRTP トラフィック(動画と音声)はピアツーピアで直接転送できます。

公衆交換電話網(PSTN)は、すべての「従来型」アナログ電話の回線交換ネットワークです。WebRTC ウェブアプリと電話間の通話では、トラフィックが PSTN ゲートウェイを経由する必要があります。同様に、WebRTC ウェブアプリは、IM クライアントなどの Jingle エンドポイントと通信するために、中間 XMPP サーバーが必要です。Jingle は、メッセージ サービスで音声とビデオを可能にするために XMPP の拡張機能として Google によって開発されました。現在の WebRTC 実装は、Talk 用に最初に開発された Jingle の実装である C++ libjingle ライブラリに基づいています。

多くのアプリ、ライブラリ、プラットフォームが、外部と通信する WebRTC の機能を利用しています。

  • sipML5: オープンソースの JavaScript SIP クライアント
  • jsSIP: JavaScript SIP ライブラリ
  • Phono: プラグインとして構築されたオープンソースの JavaScript 電話 API
  • Zingaya: 埋め込み可能なスマートフォン ウィジェット
  • Twilio: 音声とメッセージ
  • Uberconference: 会議

sipML5 のデベロッパーは、webrtc2sip ゲートウェイも構築しています。Tethr と Tropo は、OpenBTS セルを使用して、WebRTC を介してフィーチャー フォンとパソコン間の通信を可能にする「ブリーフケースに収まる」災害通信のフレームワークをデモしました。携帯通信会社を利用しない電話通信です。

補足説明

WebRTC Codelab では、Node で実行されている Socket.io シグナリング サービスを使用して、ビデオ チャット アプリとテキスト チャット アプリを作成する方法について、手順を追って説明します。

2013 年の Google I/O WebRTC プレゼンテーション(WebRTC テクニカルリード、Justin Uberti 氏)

Chris Wilson の SFHTML5 プレゼンテーション - WebRTC アプリの概要

350 ページに及ぶ書籍 WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web では、データとシグナリング パスウェイについて詳しく説明されており、詳細なネットワーク トポロジ図も多数掲載されています。

WebRTC とシグナリング: 2 年間で学んだこと - シグナリングを仕様から除外することが良いアイデアだった理由に関する TokBox のブログ投稿

Ben Strong の A Practical Guide to Building WebRTC Apps には、WebRTC のトポロジとインフラストラクチャに関する多くの情報が記載されています。

Ilya Grigorik 氏High Performance Browser NetworkingWebRTC の章では、WebRTC のアーキテクチャ、ユースケース、パフォーマンスについて詳しく説明しています。