Hero img
WebRTCの基本と実装ガイド

WebRTCの基本概を理解し、ローカルで接続できるように、P2P接続、SDP交換の解説。

FirefoxでのWebRTC通信の実装手順をコード例とともに紹介します。


目次

  • 目標
  • gitで配布
  • WebRTCとは何か?
  • WebRTCを実装するうえで
  • WebRTCでP2P接続する方法
  • WebRTC接続フロー
  • SDPの受け渡し方法
  • 自分の情報確認
  • NAT越え
  • コード例
  • より細かく
  • つまずいたところ
  • sdp生成
  • createは非同期処理
  • github
  • 使い方
  • 参考サイト
  • まとめ

目標

FireFox同士でWebRTCを使った映像送受信を実現します。

webrtc-about-result

gitで配布

このコードをgitで配布します。目次のgithubを参照ください。 ※nodjs必要です。

WebRTCとは何か?

WebRTC(Web Real-Time Communication)は、ブラウザやモバイルアプリでリアルタイムに音声やビデオ、データ通信を行うための技術です。プラグインや外部ソフトウェアを必要とせず、P2P(ピア・ツー・ピア)接続を利用することで、高速かつ低遅延な通信を実現します。 要するにp2pでデータの通信を行い高速で低遅延なデータをやり取りします。WebRTCは映像、音声だけでなくその他データの通信が可能なため、テキストデータも送る事ができます。

WebRTCを実装するうえで

WebRTCを活用するために、以下の要素は必須です

  • シグナリング
  • trunサーバー
  • strunサーバー
  • sdp
  • ピアコネクション

最低限これらを知る必要があります。

WebRTCでP2P接続する方法

P2P接続を行うには、自分(オファー側)と相手(アンサー側)の情報を理解する必要があります。

  • ipアドレスは?
  • 何を通信する?
  • 接続ポートは?

これらの情報をもとに、どの経路で通信するかを決定し、P2P接続を実現します。

WebRTC接続フロー

SDPには自分と相手の情報が含まれています。以下の手順でSDPを交換します

  1. 1.自分の情報を相手に伝える。
  2. 2.相手が回答し、自分に送る
  3. 3.自分は接続経路を確立させて通信をする。

これだけなので、とても単純なのですがたくさんの課題が残っています。

SDPの受け渡し方法

自分、相手のsdp情報を互いに受け渡しする必要がある。

解決方法

SDP情報の交換にはシグナリングサーバーを使用します。一般的にはWebSocketが利用されますが、メールでもSDPを交換することは可能です。
近年では、WHIPやWHEPという規格現れ色々なサービスで対応させている。今後メジャーになってくるのかまだ、わからない。

自分の情報確認

SDPには自分の情報が含まれていますが、これだけでは自分のIPアドレスを確認できません。STUNサーバーを使ってIPアドレスとUDPポートを取得します。 今回同じPCなので不要。

解決方法

sturnサーバーにip addressとudpポートを教えてもらいます。
実際に動作を確認したい場合はここから確認できます。

NAT越え

udpポート制限、ip制限等でどうしても接続できない場合turnサーバーを経由して接続します。 skywayさんが詳しく解説されています。
今回同じPCなので不要。

コード例

sdpのやり取りを大雑把にコードに直すと

//offer = Aさん
let Apeer = RTCPeerConnection();//RTCPeerConnectionを初期化する。
let offer = Apeer.createOffer();//sdp生成
Apeer.setLocalDescription(offer);//Aの情報を登録
//sdpをBさんに渡す

//answer = Bさん
let Bpeer = RTCPeerConnection();//RTCPeerConnectionを初期化する。
Bpeer.setRemoteDescription(A-sdp);//Aさんのsdpを登録する
let answer = Bpeer.createAnswer()//Aさんのsdpに対して回答する。
Apeer.setLocalDescription(answer);//回答したものを登録
//回答したsdpをAさんに送る

Apeer.setRemoteDescription(B-sdp)//Bさんのsdpを追加する。
//接続完了。

という流れになります。

より細かく

より細かくしていくと今回は「映像通信のみを想定」

1.Peerの作成

WebRTC接続のためにRTCPeerConnectionを作成します。必要に応じてiceServersを登録します。

Aさん
// TURN/STUNサーバーを指定し、RTCPeerConnectionを作成
//今回は不要。
let pc_config = { "iceServers": [] };
let peer = new RTCPeerConnection(pc_config);
peer.onicecandidate = function (evt) {
  if (!evt.candidate) {
    // SDP情報を送信する処理
    // ...
  }
};

2.通信するものを含める

今回は映像を送信するため、addTrackでビデオストリームを追加します。 addstream、addTrack、addTransceiverを使い映像音声を送る事ができますが、addstreamは非推奨となっているため、 aaddTrackを利用してください。

Aさん
//peerに通信するものを入れる。
//映像の通信をしたいのでaddTrackで追加する。
var localstream = await navigator.mediaDevices.getUserMedia({audio: false, video: true})
peer.addTrack(localStream);
//データのやり取りもする場合
//peer.createDataChannel("channel");
//addTransceiverは送るもの受け取るものを自由に指定できます。

3.offerを作成する

自分のSDP情報を作成し、RTCPeerConnectionに設定します。

Aさん
//Offerで自分の情報作成し、peerに入れる。
peer.createOffer().then(offersdp =>{
  peer.setLocalDescription(offersdp)
})

4.SDPの送信

setLocalDescriptionをするとpeer.onicecandidateイベントが発生しまするのでこれを利用し、通信経路がそろってから相手にSDP情報を渡します。「Vanilla ICE」

SDPを渡す方法

送信方法は自由です。手動での入力でも機能します。

Aさん
//1.Peerを作成する。のコードです。
    peer.onicecandidate = function (evt) {
      //このonicecandidateイベントは複数回発火する。
      if (evt.candidate) {
      } else {
        //これを送る(peer.localDescription)
        //SDPを送る処理
        //....
      }
    };

5.相手のPeerを作成

相手側でもRTCPeerConnectionを作成し、必要なメディアを追加します。 「1.Peerを作成」と「2.通信する」ものと同じです。

Bさん
let peer2 = new RTCPeerConnection(pc_config);
//省略....

6.SDPの受信と設定

ここからoffer時の処理と異なります。
受信したSDPをRemoteDescriptionに設定します。

Bさん
peer2.setRemoteDescription(offersdp);

7.answerを作成する

SDP情報を作成し、LocalDescriptionに設定します。

Bさん
//createanswerで自分の情報作成し、LocalDescriptionに入れる。
peer.createAnswer().then(answersdp =>{
  peer.setLocalDescription(answersdp)
})

8.アンサーの送信

作成したSDPをオファー側に送り返します。

Bさん
//1.Peerを作成する。のコードです。
    peer.onicecandidate = function (evt) {
      //このonicecandidateイベントは複数回発火する。
      if (evt.candidate) {
      } else {
        //これを送る(peer2.localDescription)
        //SDPを送る処理
        //....
      }
    };

9.アンサーの受信

オファー側は、相手からのSDPを受信し、RemoteDescriptionに設定します。

Aさん
peer.setRemoteDescription(answersdp);

これでwebrtc通信ができるようになります。

つまずいたところ

webrtcを実装してつまづいたところは

sdp生成

sdpの生成はcreateOffer(),createAnswer()の後にsetLocalDescriptionでsdpを生成できますが、この時、非同期で通信経路を複数収集します。そのため、処理をちゃんとしてあげないと基本つながりません。「Trickle ICE」 その代わりに全て収集した後に送る方法「Vanilla ICE」でsdpを送ります。

一番簡単な方法はonicecandidateで通信経路収集が止まった時にsdpを送る方法です。

peer.onicecandidate = (evt) => {
  if (!evt.candidate) {
    //sdpを送る処理
  }
}

createは非同期処理

createOffer(),createAnswer()は非同期処理のため、待機しないといけません。 awaitかthenを使って待機してください。

    peerConnection.createOffer()
    .then(function (sessionDescription) {
      return peerConnection.setLocalDescription(sessionDescription);
    })

github

githubにコードを載せておきます。ほとんどhttps://html5experts.jp/mganeko/20013/
から改変していませんが...問題なく動くと思います。

使い方

readmeに記入するべきですが...

npm i
npm run start
index.htmlを2つブラウザーで表示してください。
2つstart Videoをクリックします。
その後Connectをクリックしてください。

参考サイト

webrtc概要

signalingサーバー
signalingの接続フロー

まとめ

webrtcでの映像通信のサンプルコードが豊富なのでwebrtcを使うだけなら簡単に実装できます。
webrtcの問題点はつながらない時の原因の切り分けが難しい。いろんなos,ブラウザー,バージョン etcがあって検証や、対処が難しい...

関連記事

コメント

コメントを書く

質問や、間違いがありましたら、お気軽にどうぞ

※お名前とコメントの内容を入力してください。
※全てこのブログ上に公表されます。
※許可なく管理者によって修正・削除する場合がございます。 詳しくはプライバシーポリシーを参照ください