はじめてのWebRTC

# **WebRTCって何？**

「Web Real-Time Communication」

Webブラウザ同士でリアルタイムにデータのやり取りをする仕組みのこと。
大容量の映像や音声データをリアルタイムに送受信できる。
WebRTC は P2P を利用したブラウザ同士を前提とした技術として作られている。

現在は、デスクトップとモバイルの「Google Chrome」「Safari」「Firefox」「Opera」等ほぼすべてのブラウザで利用できるようになっており、余計なプラグインのインストールは不要。ブラウザさえあれば使用可能。

WebRTCは通信をP2Pで行うため、サーバーを介さずリアルタイムのやり取りが可能。P2Pが使われるサービスは、たとえば「LINE」「Spotify」「BitCoin」「Ethereum」など。

※上記のサービス例は、WebRTCは使用されていないが、通信はP2P

# **WebRTCでできることの例**

・リアルタイムなコミニュケーションの為のデバイス（マイク・カメラ）管理・スクリーンキャプチャ
・デバイス認識・選択
・デバイスから取得した大量の映像や音声のデータの圧縮と送信（コーデック）
・P2Pで相手と通信
など・・・

WebRTC は沢山の技術の集合体と考えてもらって問題ない。 それらをブラウザから簡単に使えるようにした仕組みが WebRTC API である。

#### WebRTC にはメディアチャネルとデータチャネルの二つがある。
1. 映像と音声のデータ通信方式がメディアチャネル
2. 好きなデータをやりとりできるのがデータチャネル



#### **シグナリングサーバーとは**

「通信相手に関する情報を得る」のが役割のサーバー

#### **STUNサーバーとは**

外部ネットワークから見た際の自身のIPアドレスを教えてくれます。そのアドレスと自身のPCのアドレスを比較してNAT越えが必要かを判断します。

#### **NATとは**

ネットワークアドレスを変換する機能のこと。
例えば、自宅でWi-Fiをつないだ際、端末にグローバルIPアドレスが付与されていないケースがあります。これは、プロバイダーから割り振られるIPアドレスが一つのため、複数の端末がインターネットに接続できるよう、ルーターがIPをLANの端末へ振り分けているためです。
そのため、端末で表示されるIPアドレスがグローバルIPアドレスと異なることがあります。しかし、これでは情報を送りたい側は困ってしまいます。なぜなら、通信したい先の本当のIPアドレスが変換されているため、わからないからです。
そこで、NATによって変換されたアドレスをセキュアに関連づける「NAT超え」が必要となります。その際に必要となるのが、STUNサーバーとTURNサーバーです。

#### **TURNサーバーとは**

STUNサーバーを使えば、NAT超えが必要かどうかがわかります。実際に企業で導入しているネットワークにはNAT越えが必要な場合が多く、且つ、Firewallを超える必要もでてきます。

#### **Firewallを超える**

Firewallは、ほとんどのパソコンに組み込まれた基本的なセキュリティ対策の1つです。特に、企業においては、サイバー攻撃などのリスクを防ぐため、ポート制御やウイルス感染を防ぐ役割を担います。
しかし、Firewallをセキュアに超えられる仕組みがなければ、ビジネスシーンでP2Pのような通信ができません。そこで、考えられたのが「TURNサーバー」です。


[引用](https://jp.vcube.com/sdk/blog/webrtc-general-server-configuration-signaling-turn-sfu.html#:~:text=WebRTC%E3%81%AF%E6%A7%98%E3%80%85%E3%81%AA%E3%82%B7%E3%83%BC%E3%83%B3,%E3%81%A7%E3%82%82%E5%88%A9%E7%94%A8%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82)

下記はデータチャネルのデモソース
深掘れば深掘るほど、奥が深く、理解がしづらいですが・・・
P2P通信でリアルタイムチャットを実際に動かしてみることで、P2Pについての理解が深まった気がします・・・
なにより、HTMLとJavascriptだけでP2Pのリアルタイム通信ができるってすごい！

**HTML**
```html

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>WebRTC Demo</title>
</head>
<body>
    <h1>WebRTC Data Channel Demo</h1>

    <h2>シグナリング</h2>
    <p>状態: <input type="text" id="status" value="" readonly="readonly" /></p>

    <h3>SDPの生成</h3>
    <p>(手順1) ブラウザ1で Start を押し，SDP (offer) を生成する。</p>
    <button type="button" onclick="startPeerConnection()">Start</button>
    <h3>自端末のSDP (Read-only)</h3>
    <p>(手順2) ブラウザ1からこのSDP (offer) をコピーする。</p>
    <p>(手順4) ブラウザ2で生成したこのSDP (answer) をコピーする。</p>
    <textarea id="localSDP" cols="80" rows="5" readonly="readonly"></textarea>
    <h3>他端末のSDP (手動でセットする)</h3>
    <p>(手順3) ブラウザ2で，コピーしたブラウザ1のSDP (offer) を貼り付け Set を押すと，自端末のSDPに返答用 SDP (answer) が生成される。</p>
    <p>(手順5) ブラウザ1で，コピーしたブラウザ2のSDP (answer) を貼り付け Set を押す。</p>
    <textarea id="remoteSDP" cols="80" rows="5"></textarea>
    <button type="button" onclick="setRemoteSdp();">Set</button>

    <h2>データチャネルでの通信</h2>
    <form action="javascript:sendMessage()">
        <input type="text" id="message" size="30" value="" />
        <input type="submit" value="Send" />
    </form>
    <textarea id="history" cols="80" rows="10" readonly="readonly"></textarea>

</body>
</html>

```


**javascript**

```javascript

// ICE server URLs
let peerConnectionConfig = {'iceServers': [{"urls": "stun:stun.l.google.com:19302"}]};

// Data channel オプション
let dataChannelOptions = {
    ordered: false,
}

// Peer Connection
let peerConnection;

// Data Channel
let dataChannel;

// ページ読み込み時に呼び出す関数
window.onload = function() {
    document.getElementById('status').value = 'closed';
}

// 新しい RTCPeerConnection を作成する
function createPeerConnection() {
    let pc = new RTCPeerConnection(peerConnectionConfig);

    // ICE candidate 取得時のイベントハンドラを登録
    pc.onicecandidate = function(evt) {
        if ( evt.candidate ) {
            // 一部の ICE candidate を取得
            // Trickle ICE では ICE candidate を相手に通知する
            console.log(evt.candidate);
            document.getElementById('status').value = 'Collecting ICE candidates';
        } else {
            // 全ての ICE candidate の取得完了（空の ICE candidate イベント）
            // Vanilla ICE では，全てのICE candidate を含んだ SDP を相手に通知する
            // （SDP は pc.localDescription.sdp で取得できる）
            // 今回は手動でシグナリングするため textarea に SDP を表示する
            document.getElementById('localSDP').value = pc.localDescription.sdp;
            document.getElementById('status').value = 'Vanilla ICE ready';
        }
    };

    pc.onconnectionstatechange = function(evt) {
        switch(pc.connectionState) {
        case "connected":
            document.getElementById('status').value = 'connected';
            break;
        case "disconnected":
        case "failed":
            document.getElementById('status').value = 'disconnected';
            break;
        case "closed":
            document.getElementById('status').value = 'closed';
            break;
        }
    };

    pc.ondatachannel = function(evt) {
        console.log('Data channel created:', evt);
        setupDataChannel(evt.channel);
        dataChannel = evt.channel;
    };

    return pc;
}

// ピアの接続を開始する
function startPeerConnection() {
    // 新しい RTCPeerConnection を作成する
    peerConnection = createPeerConnection();

    // Data channel を生成
    dataChannel = peerConnection.createDataChannel('test-data-channel', dataChannelOptions);
    setupDataChannel(dataChannel);

    // Offer を生成する
    peerConnection.createOffer().then(function(sessionDescription) {
        console.log('createOffer() succeeded.');
        return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
        // setLocalDescription() が成功した場合
        // Trickle ICE ではここで SDP を相手に通知する
        // Vanilla ICE では ICE candidate が揃うのを待つ
        console.log('setLocalDescription() succeeded.');
    }).catch(function(err) {
        console.error('setLocalDescription() failed.', err);
    });

    document.getElementById('status').value = 'offer created';
}

// Data channel のイベントハンドラを定義する
function setupDataChannel(dc) {
    dc.onerror = function(error) {
        console.log('Data channel error:', error);
    };
    dc.onmessage = function(evt) {
        console.log('Data channel message:', evt.data);
        let msg = evt.data;
        document.getElementById('history').value = 'other> ' + msg + '\n' + document.getElementById('history').value;
    };
    dc.onopen = function(evt) {
        console.log('Data channel opened:', evt);
    };
    dc.onclose = function() {
        console.log('Data channel closed.');
    };
}

// 相手の SDP 通知を受ける
function setRemoteSdp() {
    let sdptext = document.getElementById('remoteSDP').value;

    if ( peerConnection ) {
        // Peer Connection が生成済みの場合，SDP を Answer と見なす
        let answer = new RTCSessionDescription({
            type: 'answer',
            sdp: sdptext,
        });
        peerConnection.setRemoteDescription(answer).then(function() {
            console.log('setRemoteDescription() succeeded.');
        }).catch(function(err) {
            console.error('setRemoteDescription() failed.', err);
        });
    } else {
        // Peer Connection が未生成の場合，SDP を Offer と見なす
        let offer = new RTCSessionDescription({
            type: 'offer',
            sdp: sdptext,
        });
        // Peer Connection を生成
        peerConnection = createPeerConnection();
        peerConnection.setRemoteDescription(offer).then(function() {
            console.log('setRemoteDescription() succeeded.');
        }).catch(function(err) {
            console.error('setRemoteDescription() failed.', err);
        });
        // Answer を生成
        peerConnection.createAnswer().then(function(sessionDescription) {
            console.log('createAnswer() succeeded.');
            return peerConnection.setLocalDescription(sessionDescription);
        }).then(function() {
            // setLocalDescription() が成功した場合
            // Trickle ICE ではここで SDP を相手に通知する
            // Vanilla ICE では ICE candidate が揃うのを待つ
            console.log('setLocalDescription() succeeded.');
        }).catch(function(err) {
            console.error('setLocalDescription() failed.', err);
        });
        document.getElementById('status').value = 'answer created';
    }
}

// チャットメッセージの送信
function sendMessage() {
    if ( !peerConnection || peerConnection.connectionState != 'connected' ) {
        alert('PeerConnection is not established.');
        return false;
    }
    let msg = document.getElementById('message').value;
    document.getElementById('message').value = '';

    document.getElementById('history').value = 'me> ' + msg + '\n' + document.getElementById('history').value;
    dataChannel.send(msg);

    return true;
}

```


下記がサンプルデモも有り分かりやすかったです。
[https://webrtc.github.io/samples/](https://webrtc.github.io/samples/)