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. Alice が RTCPeerConnection オブジェクトを作成します。
  2. Alice は RTCPeerConnection createOffer() メソッドを使用してオファー(SDP セッション記述)を作成します。
  3. Alice は setLocalDescription() に電話して、特典を提示します。
  4. Alice はオファーを文字列化し、シグナリング メカニズムを使用して Eve に送信します。
  5. Eve は Alice のオファーを setRemoteDescription() に送信し、RTCPeerConnection が Alice の設定を認識できるようにします。
  6. Eve が createAnswer() を呼び出し、この成功コールバックにローカル セッションの説明(Eve の回答)が渡されます。
  7. Eve は setLocalDescription() を呼び出して、回答をローカルの説明として設定します。
  8. Eve はシグナリング メカニズムを使用して、文字列化された回答を Alice に送信します。
  9. Alice は setRemoteDescription() を使用して、Eve の回答をリモート セッションの説明として設定します。

Alice と 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.toBrowser Meeting などのビデオチャット アプリでは、カスタムリンクを共有して通話に招待します。デベロッパーの Chris Ball 氏は、興味深い serverless-webrtc という実験を構築しました。この実験では、WebRTC 通話の参加者が IM、メール、伝書鳩など、任意のメッセージ サービスを使用してメタデータを交換できます。

シグナリング サービスを構築するにはどうすればよいですか?

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

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

WebRTC シグナリング サービスは、帯域幅の要件が比較的低いだけでなく、メッセージの中継と、接続されているクライアントなどの少量のセッション状態データの保持のみが必要なため、処理やメモリの消費も少なくなります。

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

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

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

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 などがあります)。JavaScript クライアント(Strophe.js など)は BOSH を使用して双方向ストリーミングをエミュレートしますが、さまざまな理由により、BOSH は WebSocket ほど効率的ではなく、同じ理由で、スケーリングも適切に行われない可能性があります)。(余談ですが、Jingle は音声と動画を有効にする XMPP 拡張機能です。WebRTC プロジェクトは、libjingle ライブラリ(Jingle の C++ 実装)のネットワーク コンポーネントとトランスポート コンポーネントを使用します。

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

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

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

(デベロッパーの Phil Leggetter 氏の Real-Time Web Technologies Guide には、メッセージング サービスとライブラリの包括的なリストが記載されています)。

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

以下は、Node 上の Socket.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)でコンソールにアクセスできます。

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

シグナリングの落とし穴

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

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

独自のサーバーをロールアウトしない場合は、Socket.IO を使用し(前の例を参照)、WebRTC クライアント JavaScript ライブラリと統合された WebRTC シグナリング サーバーがいくつかあります。

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

コードをまったく記述したくない場合は、vLineOpenTokAsterisk などの企業から、商用 WebRTC プラットフォームが提供されています。

参考までに、WebRTC の初期に Ericsson は Apache 上で PHP を使用してシグナリング サーバーを構築しました。これは現在ではやや時代遅れですが、同様のものを検討している場合は、コードを見てみる価値があります。

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

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

サルマン・ラシュディ

暗号化は、すべての 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 サーバーとは異なり、帯域幅を大量に消費します。つまり、TURN サーバーはより強力である必要があります。

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

次の図は、TURN の動作を示しています。純粋な STUN が成功しなかったため、各ピアは TURN サーバーの使用に切り替えます。

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

テスト用に、Google は appr.tc で使用されているパブリック STUN サーバー stun.l.google.com:19302 を実行しています。本番環境の 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. ローカル ファイアウォール構成を設定します(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 から restund を取得して解凍します。
  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. restund を構成します。
    1. LD_LIBRARY_PATH を設定します。
    2. restund.conf/etc/restund.conf にコピーします。
    3. restund.conf を 10 に設定します。IPアドレスが割り当てられます
  12. restund を実行する
  13. リモート マシンから stund クライアントを使用してテストします。./client IP:port

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

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

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

WebRTC アプリは複数の RTCPeerConnection を使用して、すべてのエンドポイントがメッシュ構成の他のすべてのエンドポイントに接続できるようにします。これは talky.io などのアプリで採用されているアプローチで、少数のピアに対して非常に有効です。それ以外の場合、処理と帯域幅の消費が過剰になります(特にモバイル クライアントの場合)。

Mesh: 小規模な N 方向通話
フルメッシュ トポロジ: すべてのユーザーがすべてのユーザーに接続されている

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

Chrome 31 と Opera 18 以降では、1 つの RTCPeerConnection からの MediaStream を別の RTCPeerConnection の入力として使用できます。これにより、ウェブアプリが接続するピアを選択して通話ルーティングを処理できるため、より柔軟なアーキテクチャが可能になります。実際の動作については、WebRTC サンプル Peer connection relayWebRTC サンプル Multiple peer connections をご覧ください。

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

多数のエンドポイントがある場合は、多地点制御装置(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 ライブラリに基づいています。これは、当初 Talk 向けに開発された Jingle の実装です。

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

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

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

関連リソース

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

WebRTC テクニカル リードの Justin Uberti による 2013 年の Google I/O での WebRTC プレゼンテーション

Chris Wilson 氏の SFHTML5 プレゼンテーション - Introduction to WebRTC Apps

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 のアーキテクチャ、ユースケース、パフォーマンスについて詳しく説明しています。