WebRTC が何なのかよく分からないから
       調べて試してみた
2021.10.11
@tommy_tomtoru
Profile
@tommy_tomtoru
というホテルのチェックインシステムを作っている
福岡の会社でエンジニアやってます
Career
Python: 個人開発で書く。好き
PHP: 仕事で書く。
JS: アロー関数を見ると一瞬固まる。
  非同期で定期的にやらかす。
Skil
今回のモチベーション
では、WebRTCを用いて本人確認通話が可能なチェックインシステムを
福岡市などではテレビ通話などを用いて宿泊者の本人確認を行える環境があれば
「無人でのホテル運営」を行政より許可
をもらえることがある
https://www.city.fukuoka.lg.jp/hofuku/seikatsueisei/life/kurashinoeisei/ictwokatuyousitasyukuhkausis
etuniokeruhitaimennkeisikinotyekku_3.html
福岡市の例:
提供することで無人での宿泊施設運営を可能にしています
Introduction
今回のLTでは
「WebRTC APIを用いてWebブラウザ上で
    1対1のリアルタイム音声/映像通信を行うこと」
をゴールと設定します
Introduction
Web Real-Time Communication
WebサイトやWebアプリケーションにて
Real-Timeに音声/映像によってコミュニケーションを行う為の技術
WebRTCのアーキテクチャ,通信仕様 > IETF: rtcweb
https://datatracker.ietf.org/wg/rtcweb/documents/
APIとしてのWebRTC > W3C: webrtc-pc
https://w3c.github.io/webrtc-pc/
overview
全体像
overview
step1: ブラウザで音声と映像を取得
overview
step2: シグナリングサーバと接続
overview
step3: SDP Offer
overview
step4: SDP Answer
overview
step5: P2P通信
Step 1
音声や映像をブラウザで取得
constraints = {
audio: true,
video: true
// 以下の様な指定もできる
// video: { width: 1280, height: 720 }
// video: { facingMode: "user" }
}
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
//
})
.catch(function(err) {
//
});
実行するとブラウザ上で許可通知が表示される
許可を選択後、音声/映像のストリームを取得できる
Step 2
シグナリングサーバー
// websocketを用いた簡易なシグナリングサーバの例
const WebSocketServer = require('ws').Server;
const port = 3001;
const wsServer = new WebSocketServer({ port: port });
wsServer.on('connection', function(ws) {
ws.on('message', function(message) {
wsServer.clients.forEach(function each(client) {
if (ws==client) {
// skip
}
else {
client.send(message);
}
});
});
});
シグナリングサーバの役割
・接続するクライアントの管理
・SDPの交換仲介(step3,4)
P2Pの接続を行う前のやり取りを担う
WebRTCにおいてシグナリングの実装仕様は
特に定められていない
Step 3
SDP Offer
const pc = new RTCPeerConnection();
const stream = await
navigator.mediaDevices.getUserMedia({
audio: true, video: true
});
for (const track of stream.getTracks()) {
pc.addTrack(track, stream);
}
const offer = await pc.createOffer();
// offer.type >> offer
// offer.sdp >> sdpの内容
// setLocalDescription が実行されると発火
pc.onicecandidate = event => {
if (event.candidate) {
// skip
} else {
// 全ての候補を探索終了
// シグナリングサーバを介してSDPを送信する処理
sendSDP(pc.localDescription);
}
}
// SDP offerを自身に設定
await pc.setLocalDescription(offer);
Session Description Protocol
接続する双方でやり取りするメディアの仕様を把握する必
要がある
メディアの仕様を記述したものが SDP
通信を始める側から SDPのOfferを送り、
応答側がSDP のAnswerを送る
v=0
o=- 4305894064294705670 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=extmap-allow-mixed
a=msid-semantic: WMS OEleWTR9mctlixnN3JeYZyfVDRDbg6bpv101
m=video 51510 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127
120 125 107 108 109 35 36 124 119 123 118 114 115 116
c=IN IP4 217.178.96.190
a=rtcp:9 IN IP4 0.0.0.0
a=candidate:3290292206 1 udp 2113937151
fd42935f-ef3f-4fcc-90f5-ffafaa339117.local 51510 typ host
generation 0 network-cost 999
...
SDP例
Step 4
SDP Answer
// Answer側のクライアント
const pc = new RTCPeerConnection();
// offer側同様にstreamを取得してセット
const stream = await
navigator.mediaDevices.getUserMedia({
audio: true, video: true
});
for (const track of stream.getTracks()) {
pc.addTrack(track, stream);
}
// シグナリングサーバを介してOffer SDPを取得
const offer = getOfferSDP();
// offer側のSDPをセット
await pc.setRemoteDescription(offer);
pc.onicecandidate = event => {
if (event.candidate) {
// skip
} else {
sendSDP(pc.localDescription);
}
}
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
Session Description Protocol
接続する双方でやり取りするメディアの仕様を把握する必
要がある
メディアの仕様を記述したものが SDP
通信を始める側から SDPのOfferを送り、
応答側がSDP のAnswerを送る
v=0
o=- 4305894064294705670 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=extmap-allow-mixed
a=msid-semantic: WMS OEleWTR9mctlixnN3JeYZyfVDRDbg6bpv101
m=video 51510 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127
120 125 107 108 109 35 36 124 119 123 118 114 115 116
c=IN IP4 217.178.96.190
a=rtcp:9 IN IP4 0.0.0.0
a=candidate:3290292206 1 udp 2113937151
fd42935f-ef3f-4fcc-90f5-ffafaa339117.local 51510 typ host
generation 0 network-cost 999
...
SDP例
Step 5
P2P通信
const pc = new RTCPeerConnection();
// 通信相手がメディアを送った場合に発火
pc.ontrack = event => {
event.track; // MediaStreamTrack
event.streams; // MediaStream
}
// 通信相手の接続状態に変化が合った場合に発火
pc.oniceconnectionstatechange = () => {
pc.iceConnectionState;
// 'closed'
// 'failed'
// 'disconnected' etc..
}
// 切断
pc.close()
P2P以外を用いる方法もある
P2P:: ブラウザ同士で通信
good サーバが不要
bad 接続が増えると負荷が⤴
MCU:: サーバでメディアを合成
good ブラウザの負荷が低い
bad サーバ負荷が高い
SFU:: サーバでメディアを分岐
good ブラウザの負荷が低い (上り)
bad サーバが必要
Demo
実 践
Summary
・P2Pを用いる場合、用意されている APIで比較的難しいことを考えずに Web RTCを実装できる
・シグナリング時のNAT越えの為のTURN/STUNの理解が鬼門らしい(今回はスルーしました)
・動くものが出来るとうれしい。すごく
所感
Reference
■「webrtc-for-the-curious」: OSS
- すごく丁寧にまとめられている神資料。日本語版あり。
https://github.com/webrtc-for-the-curious/webrtc-for-the-curious
■「WebRTCを今から学ぶ人に向けて」 : @voluntas
- 何を抑えるべきかが分かりやすい神資料。
https://zenn.dev/voluntas/scraps/82b9e111f43ab3
■「WebRTCハンズオン」: @massie_g
- JS or Swiftで実際にWebRTCを実装する神ハンズオン。
https://qiita.com/massie_g/items/916694413353a3293f73
参考にさせていただいた神資料

WebRTCがよく分からないから調べて試してみた