WebRTCをざっくり解説
WebRTCをざっくり解説してみる
現在開発しているプロダクト
こちらで、ユーザー同士でカメラを共有するという機能を実装しました。
イメージ図
開発初期は「とりあえずZoomみたいなカメラを共有する機能でも実装するか」くらいの軽い気持ちで着手したのですが、調べれば調べるほど難易度の高さに絶望しておりました。
「どうやらWebRTCという技術を使えば実現できるらしい」
というところから、調べに調べまくって、なんとか実装にこぎつけられましたので、私のようなプログラミング初学者に対して、情報共有をさせて頂こうと思った次第です。
WebRTCとは?
WebRTCとは「Web Real-Time Communication」の略称です。
HTML5で新しく策定されたAPIの規格で、ウェブブラウザやモバイルアプリでリアルタイム通信(音声や動画など)を実現するための技術(オープンソース)になります。
詳細は公式ドキュメントから
WebRTC API
カンタンに言ってしまえば、「サーバーなどを介さずに、クライアント(ブラウザ)同士で、映像をリアルタイムでやり取りするための技術」ということになります。
以下関連する用語をざっくり解説します。
P2P通信
上記のような、サーバーを介さずにクライアント同士で直接通信することを、「P2P通信」と言います。(P2Pは「Peer to Peer」の略称)
RTCPeerConnection
RTCPeerConnection (Real-Time-Communication Peer Connection) は、ブラウザ上でPeer to Peer(P2P)通信を可能にする為のAPIです。
通信を確立させるために必要なデータは2つあります。
-
SDP(Session Description Protocol)
-
ICE Candidates(Interactive Connectivity Establishment Candidates)
SDP
Session Description Protocol
使用可能なメディアの情報、通信の情報などを交換するためのプロトコル。荷物の送り先と荷物の内容みたいなこと。
その中身は、以下のような形式で数百行続いています。
v=0
o=- 4340214655134371844 2 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=sendrecv
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 124 119 123 118 114 115 116
c=IN IP4 0.0.0.0
a=sendrecv
...
Offer/Answerモデルという方式で交換します。(後述します)
ICE
Interactive Connectivity Establishment
通信をするための経路情報を交換するためのプロトコル。荷物を届けるための道順みたいなこと。
「通信経路」ではなく、「通信経路候補」であるのは、通信してみないとその通信経路で本当に通信できるのかが分からないからです。WebRTCでは、自身が通信できそうな経路をたくさん使って疎通確認をして、実際に使えたものを使用するようになっています。
シグナリングサーバー
P2Pは、サーバーを使わずブラウザ同士で通信を行うと言いました。
しかし実際問題、P2P通信を行うためには、SDPとICEの交換(=シグナリング)が必要で、これらをインターネット上で交換するには、サーバーに中継してもらう必要があります。
交換が完了してしまえば、あとはブラウザ同士のP2P通信が確立するので、その後はサーバーが不要となります。
つまり、SDP、ICEを交換するためのサーバーだけ必要ということになります。このようなサーバーを「シグナリングサーバー」と言います。
Offer/Answerモデル
SDPの交換方式のことです。
offerというSDPを相手に送り、相手からはanswerというSDPが送られてきます。
webRTC関連のメソッド
以下で説明することは、応用的なことをすべて省いています。
詳しくはPeerConnection APIをご参照ください。
コンストラクター
const pc = new RTCPeerConnection();
RTCPeerConnection を返します。これに交換したSDPやICEをセットしていくことになります。(今回はpcという変数で取り扱うことにします。)
videoをセットする処理
pc.ontrack = (e) => {
// 通信相手の映像を映すvideoElementを用意して
videoElement.srcObject = e.streams[0];
};
コンストラクターを実行するときに、設定しておきます。通信が確立したときに、この中の処理が実行されます。映像はe.streams
として配列で受け取れます。
candidateを送信する処理
pc.onicecandidate = (e) => {
if (e.candidate !== null) {
// e.candidateを相手に送信する処理
}
}
candidateを送る準備ができたら実行されます。厳密に言えば、localとremoteのSDPがセットできたときに実行されます。
candidateをセット
const iceCandidate = new RTCIceCandidate(candidate);
pc.addIceCandidate(iceCandidate);
送られてきたcandidateをRTCCandidateにセットします。これをRTCPeerConnectionに追加します。
offer作成
const offer = pc.createOffer()
answer作成
const answer = pc.createAnswer()
2者間でどちらかがofferを作成し、もう一方はanswerを作成します。
自分のSDPをセット
pc.setLocalDescription(offer)
// または
pc.setLocalDescription(answer)
相手のSDPをセット
pc.setRemoteDescription(offer)
// または
pc.setRemoteDescription(answer)
offerを作成した方は、local
にoffer
、remote
にanswer
answerを作成した方は、local
にanswer
、remote
にoffer
をそれぞれセットするようになります。
細かい設定などもありますが、とりあえずこれだけでP2P通信をすることができます。
接続フロー
実際の流れを図にまとめてみました。
candidateについて
localとremoteにSDP(offer,answer)がセットされ、両方が揃った時点でoncandidateの中身が実行されます。
candidateは通信経路が確定するまでひたすら投げ続けるという仕組みになっています。
今回で言えば、Bさんの方が先にlocalとremoteのSDPをセットし終わるので、先にoncandidateが実行され、Aさんに対してcandidateを投げ始めます。
その時点ではAさんは、Bさんからのanswerを受け取っていないのでcandidateをセットできませんが、answerをremoteにセットしたらAさんもlocalとremoteが揃うのでcandidateをセットできます。
同時にAさんのoncandidateが実行されるので、Bさんにcandidateを投げるのですが、Aさんがcandidateをセットできればこれで通信は接続しますので、Bさんにcandidateを投げる必要はなくなります。
つまり、Aさん(offerを作成した方)はoncandidateを記述しなくても通信は成立するということです。ただし、それだけだと不十分なのでAさん側でもoncandidateを記述しておくと、接続はより安定します。
まとめ
WebRTCを使ってP2P通信を行う流れを解説しました。
今回は、シグナリングサーバーへのSDP、candidateの送受信する部分について触れなかったので、需要があれば記事にしようと思います。
Discussion