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

シグナリングとは

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

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

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

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

冗長性を回避し、既存のテクノロジーとの互換性を最大限に高めるため、シグナリングの方法とプロトコルは WebRTC 標準では規定されていません。この手法は、JavaScript Session Establishment Protocol(JSEP)で概説されています。

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

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

JSEP では、前述のメディア メタデータである offeranswer のピア間での交換が必要です。オファーと回答は、次のような Session Description Protocol(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 のくだらない問題が実際に何を意味するのかを知りたい場合は、インターネット技術特別調査委員会(IETF)の例をご覧ください。

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

RTCPeerConnection API とシグナリング: オファー、回答、受験者

RTCPeerConnection は、WebRTC アプリで使用される API です。ピア間の接続を作成し、音声と動画の通信を行います。

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

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

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

アリスがイブに電話しようとしているところを想像してみてください。オファーと回答のメカニズムの全体像は次のとおりです。

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

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

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

JSEP は ICE Candidate Trickling をサポートしています。この機能では、発信者は最初のオファーの後に被受験者に段階的に候補者を提示し、呼び出し先はすべての候補者が到着するのを待たずにコールへの対応を開始して接続をセットアップできます。

シグナリング用の 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 をご覧ください。コンソールログで 1 ページのビデオチャットの例をご覧ください。詳細を確認するには、Google Chrome の about://webrtc-internals ページまたは Opera の opera://webrtc-internals ページから、WebRTC シグナリングと統計情報の完全なダンプをダウンロードしてください。

ピアの検出

これは、「話し相手を見つけるにはどうすればよいですか?」と尋ねるための派手な方法です。

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

ピア検出メカニズムは WebRTC では定義されていないため、ここでは詳しく説明しません。このプロセスは、URL をメールで送信するかメッセージを送信するだけでもかまいません。Talkytawk.toBrowser Meeting などのビデオチャット アプリの場合、カスタムリンクを共有して通話にユーザーを招待します。デベロッパーの Chris Ball は、WebRTC 呼び出しの参加者が、IM、メール、ホーミング ハトなどの任意のメッセージ サービスを使用してメタデータを交換できる、興味深い serverless-webrtc テストを構築しました。

シグナリング サービスの構築方法

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

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

WebRTC シグナリング サービスは、帯域幅の点で比較的要求されないだけでなく、メッセージを中継するだけでよく、どのクライアントが接続しているかなどのセッション状態データを保持すればよいため、処理やメモリをあまり消費しません。

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

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

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

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

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

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

スケール シグナリング

シグナリング サービスは、クライアントあたりの帯域幅と CPU を比較的わずかに消費しますが、一般的なアプリのシグナリング サーバーは、さまざまな場所からの大量のメッセージを高い同時実行性で処理する必要があります。大量のトラフィックを受信する WebRTC アプリには、かなりの負荷を処理できるシグナリング サーバーが必要です。ここでは詳しく説明しませんが、大容量で高パフォーマンスのメッセージには、次のようないくつかのオプションがあります。

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

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

  • PusherKaazingPubNub など、WebSocket を使用する(ただし、ロング ポーリングにフォールバックする可能性がある)商用クラウド メッセージング プラットフォーム(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 を完全な 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、Socket.IO、node-static をインストールする必要があります。ノードは 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 では、Ctrl+Shift+J(Mac の場合は Command+Option+J)を使用して Google Chrome デベロッパー ツールからコンソールにアクセスできます。

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

シグナリングの注意点

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

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

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

  • webRTC.io は、WebRTC の最初の抽象化ライブラリの一つです。
  • 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 は、WebRTC アプリによって(必要に応じて)RTCPeerConnection コンストラクタの最初の引数である iceServers 構成オブジェクトで指定されます。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 サーバーと連携し、ピア間の最適なパスを特定します。

気絶

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:ポート アドレスを取得する

TURN

RTCPeerConnection は、UDP を介してピア間で直接通信を設定しようとします。失敗した場合、RTCPeerConnection は TCP を使用します。失敗した場合は、TURN サーバーをフォールバックとして使用し、エンドポイント間でデータを中継できます。

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

TURN サーバーにはパブリック アドレスがあるため、ピアがファイアウォールやプロキシの背後にある場合でも、ピアから接続できます。TURN サーバーのタスクは、概念的には単純です。つまり、ストリームの中継です。ただし、STUN サーバーとは異なり、本質的に大量の帯域幅を消費します。つまり、TURN サーバーは強化する必要があります。

STUN サーバーを使用したピアツーピア接続
Monty の全体像: STUN、TURN、シグナリング

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

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

テストでは、appr.tc で使用されている一般公開 STUN サーバー stun.l.google.com:19302 を Google で実行しています。本番環境の STUN/TURN サービスの場合は、rfc5766-turn-server を使用します。STUN および TURN サーバーのソースコードは GitHub で入手できます。また、サーバーのインストールに関する複数の情報源へのリンクも掲載されています。アマゾン ウェブ サービス(AWS)用の VM イメージも使用できます。

別の TURN サーバーは再実行され、ソースコードとして利用可能で、AWS でも利用可能です。Compute Engine で復元を設定する手順は以下のとおりです。

  1. 必要に応じて tcp=443、udp/tcp=3478 のファイアウォールを開きます。
  2. パブリック IP ごとに 1 つ、計 4 つのインスタンスを作成します。標準 Ubuntu 12.06 イメージ。
  3. ローカル ファイアウォール構成をセットアップします(ANY から ANY を許可する)。
  4. ツールをインストールします。 shell sudo apt-get install make sudo apt-get install gcc
  5. creytiv.com/re.html から libre をインストールします。
  6. creytiv.com/restund.html から再取得して展開します。
  7. wget hancke.name/restund-auth.patch を選択し、patch -p1 < restund-auth.patch で適用します。
  8. libre と restund について、makesudo make install を実行します。
  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 を設定します。できます。
  12. 再実行
  13. リモートマシンから stund クライアントを使用してテストします: ./client IP:port

1 対 1 の枠を超える: マルチパーティ WebRTC

Justin Uberti が提案している TURN サービスへのアクセス用 REST API に関する IETF 標準もご覧になることをおすすめします。

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

WebRTC アプリでは複数の RTCPeerConnections を使用できるため、すべてのエンドポイントがメッシュ構成内の他のすべてのエンドポイントに接続できます。これは、talky.io などのアプリで採用されているアプローチで、少数の類似アプリにおいて非常にうまく機能します。さらに、特にモバイル クライアントの場合、処理と帯域幅の消費が過剰になります。

メッシュ: 小規模な N 方向の呼び出し
フルメッシュ トポロジ: 全員に接続

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

Chrome 31 と Opera 18 以降では、ある 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 の実装は、C++ の libjingle ライブラリをベースにしています。libjingle は、最初に Talk 用に開発された Jingle の実装です。

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

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

sipML5 のデベロッパーは webrtc2sip ゲートウェイもビルドしています。Tethr と Tropo は、OpenBTS セルを使用して、WebRTC を介してフィーチャー フォンとコンピュータの間の通信を可能にする災害情報通信のフレームワークを「ブリーフケース型」で実証しました。これは、携帯通信会社を使わない電話通信です。

補足説明

WebRTC Codelab では、Node.

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 の WebRTC アプリ構築実践ガイド(A Practical Guide to Building WebRTC Apps)には、WebRTC トポロジとインフラストラクチャに関する多くの情報が記載されています。

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