ピア接続のスタートガイド

ピア接続は、異なるコンピュータ上の 2 つのアプリケーションを接続してピアツーピア プロトコルを使用して通信する WebRTC 仕様の一部です。ピア間の通信は、動画、音声、任意のバイナリデータ(RTCDataChannel API をサポートするクライアントの場合)にすることができます。2 つのピアが接続する方法を検出するには、両方のクライアントが ICE サーバー構成を提供する必要があります。これは STUN サーバーまたは TURN サーバーのいずれかであり、その役割は各クライアントに ICE 候補を提供し、それがリモート ピアに転送されることです。この ICE 候補の転送は、一般にシグナリングと呼ばれます。

シグナリング

WebRTC 仕様には ICE(Internet Connectivity Establishment)サーバーと通信するための API が含まれていますが、シグナリング コンポーネントは含まれていません。2 つのピアが接続方法を共有するには、シグナリングが必要です。通常、これは通常の HTTP ベースの Web API(REST サービスやその他の RPC メカニズムなど)を介して解決されます。この API を使用すると、ピア接続が開始される前に、ウェブ アプリケーションが必要な情報を中継できます。

次のコード スニペットは、この架空のシグナリング サービスを使用してメッセージを非同期で送受信する方法を示しています。この値は、このガイドの残りの例で必要に応じて使用されます。

// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
    // New message from remote client received
});

// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');

シグナリングはさまざまな方法で実装できます。WebRTC 仕様では、特定のソリューションは推奨されていません。

ピア接続の開始

各ピア接続は RTCPeerConnection オブジェクトによって処理されます。このクラスのコンストラクタは、単一の RTCConfiguration オブジェクトをパラメータとして受け取ります。このオブジェクトは、ピア接続の設定方法を定義し、使用する ICE サーバーに関する情報を含んでいる必要があります。

RTCPeerConnection が作成されたら、呼び出し側ピアか受信側ピアかに応じて、SDP オファーまたはアンサーを作成する必要があります。SDP オファーまたは回答が作成されたら、別のチャネルを介してリモート ピアに送信する必要があります。SDP オブジェクトをリモート ピアに渡すことはシグナリングと呼ばれ、WebRTC 仕様の対象外です。

呼び出し側からピア接続の設定を開始するには、RTCPeerConnection オブジェクトを作成し、createOffer() を呼び出して RTCSessionDescription オブジェクトを作成します。このセッションの説明は setLocalDescription() を使用してローカルの説明として設定され、シグナリング チャネルを介して受信側に送信されます。また、受信側から提供されたセッションの説明に対する回答を受信したときに、シグナリング チャネルのリスナーを設定します。

async function makeCall() {
    const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
    const peerConnection = new RTCPeerConnection(configuration);
    signalingChannel.addEventListener('message', async message => {
        if (message.answer) {
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
        }
    });
    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);
    signalingChannel.send({'offer': offer});
}

受信側では、着信したオファーを待ってから RTCPeerConnection インスタンスを作成します。完了したら、setRemoteDescription() を使用して受信したオファーを設定します。次に、createAnswer() を呼び出して、受信したオファーに対する回答を作成します。この回答は setLocalDescription() を使用してローカルの説明として設定され、シグナリング サーバーを介して呼び出し側に送信されます。

const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
    if (message.offer) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        signalingChannel.send({'answer': answer});
    }
});

2 つのピアがローカル セッションとリモート セッションの両方の説明を設定すると、リモート ピアの機能がわかります。これは、ピア間の接続の準備が整ったことを意味するものではありません。これを機能させるには、各ピアで ICE 候補を収集し、シグナリング チャネル経由で他のピアに転送する必要があります。

ICE 候補

2 つのピアが WebRTC を使用して通信するには、接続情報を交換する必要があります。ネットワークの状況はさまざまな要因によって変化する可能性があるため、通常は外部サービスを使用して、ピアに接続する候補を検出します。このサービスは ICE と呼ばれ、STUN サーバーまたは TURN サーバーを使用しています。STUN は NAT のセッショントラバーサル ユーティリティの略で、通常はほとんどの WebRTC アプリケーションで間接的に使用されます。

TURN(Traversal Using Relay NAT)は、STUN プロトコルを組み込んだより高度なソリューションです。ほとんどの商用 WebRTC ベースのサービスでは、ピア間の接続を確立するために TURN サーバーを使用しています。WebRTC API は STUN と TURN の両方を直接サポートしており、より完全な用語であるインターネット接続確立にまとめられています。WebRTC 接続を作成するときは、通常、RTCPeerConnection オブジェクトの構成で 1 つ以上の ICE サーバーを指定します。

トリクル ICE

RTCPeerConnection オブジェクトが作成されると、基盤となるフレームワークは、提供された ICE サーバーを使用して、接続確立の候補(ICE 候補)を収集します。RTCPeerConnection のイベント icegatheringstatechange は、ICE 収集がどの状態にあるか(newgatheringcomplete)を通知します。

ピアが ICE 収集の完了を待つことも可能ですが、通常は「トリクル ICE」手法を使用して、各 ICE 候補が検出されるたびにリモート ピアに送信する方がはるかに効率的です。これにより、ピア接続のセットアップ時間が大幅に短縮され、遅延を抑えてビデオ通話を開始できます。

ICE 候補を収集するには、icecandidate イベントのリスナーを追加するだけです。そのリスナーで発行される RTCPeerConnectionIceEvent には、リモート ピアに送信される新しい候補を表す candidate プロパティが含まれます(シグナリングを参照)。

// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
    if (event.candidate) {
        signalingChannel.send({'new-ice-candidate': event.candidate});
    }
});

// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
    if (message.iceCandidate) {
        try {
            await peerConnection.addIceCandidate(message.iceCandidate);
        } catch (e) {
            console.error('Error adding received ice candidate', e);
        }
    }
});

接続が確立されました

ICE 候補が受信されると、ピア接続の状態は最終的に接続状態に変わります。これを検出するために、connectionstatechange イベントをリッスンする RTCPeerConnection にリスナーを追加します。

// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});

RTCPeerConnection API のドキュメント