webrtc meetup tokyo 1
DESCRIPTION
WebRTC meetup 1のハンズオンの資料です。 シグナリングについてだけ、話します。 次のサイト記事をベースにしています。 http://html5experts.jp/mganeko/5181/ http://html5experts.jp/mganeko/5349/TRANSCRIPT
WebRTC Meetup #1ハンズオンインフォコム株式会社
がねこまさし
今日のテーマ• シグナリングの話だけ、します– Peer-to-Peer がつながる前の話です
• UserMedia の話はしません。ごめんなさい• STUN/TURN 、 DataChannel の話もありませ
ん
元ネタ• HTML5Experts.jp に書いた記事がベース– WebRTC に触ってみたいエンジニア必見!手動
で WebRTC 通信をつなげてみよう• http://html5experts.jp/mganeko/5181/
– WebRTC 初心者でも簡単にできる! Node.js で仲介(シグナリング)を作ってみよう• http://html5experts.jp/mganeko/5349/
• すでに試した方は同じ話です。ごめんなさい
WebRTC の通信はどうなっているの?
• 映像や音声などリアルタイムに取得されたデータを、ブラウザ間で送受信
• RTCPeerConnection が行う– Peer-to-Peer の通信
• ブラウザとブラウザの間で直接通信する
– UDP/IP を使用• TCP/IP のようにパケットの到着は保障しない• オーバーヘッドが少ない• 通信のリアルタイム性を重視• UDP のポート番号は動的に割り振られる( 49152 ~
65535 )
Peer-to-Peer 通信が確立するまで (1)
• Peer-to-Peer を行うには– 相手の IP アドレスを知る必要がある– 動的に割り振られる UDP のポート番号も知る必要が
ある
• Session Description Protocol (SDP) の交換– WebRTC 専用ではなく、 VoIP 等で利用されている– 各エンドポイント(ブラウザ)の情報を示す。例えば、
• メディアの種類(音声、映像)、メディアの形式(コーデック)
• データ転送プロトコル → WebRTC では Secure RTP• 通信で使用する帯域
Peer-to-Peer 通信が確立するまで (2)
• Interactive Connectivity Establishment (ICE) の交換– WebRTC 専用ではなく、 P2P で利用されている– 途中経路の情報を示す。複数ある場合も多い
• P2P による直接通信• STUN による、 NAT 通過のためのポートマッピング
– → 最終的には P2P になる• TURN による、リレーサーバーを介した中継通信
SDP SDPICE
手動編
ということで、やってみましょう• WebRTC に触ってみたいエンジニア必見!手動で WebRTC
通信をつなげてみよう– http://html5experts.jp/mganeko/5181/– 一番下の方の「手動シグナリングの改良版ソース (2014年 4月
21日追加)」のソース• 手動シグナリングは操作が面倒• 必要なテキストを自動で選択するように
DEMO
ということで、やってみましょう• WebRTC に触ってみたいエンジニア必見!手動で WebRTC
通信をつなげてみよう– http://html5experts.jp/mganeko/5181/– 一番下の方の「手動シグナリングの改良版ソース (2014 年 4 月
21 日追加)」のソース• みなさんも、一緒にやってみましょう
– ソースを自分のマシンにコピーして、 Chrome でアクセス– 自前の Web サーバーが必要です– ※file:// ~ ではなく、 http:// ~ の必要があります
• ※ 手動シグナリングは、なぜか動作が不安定– 端末によっては通信が確立しないケースあり
• 今のところ成功は 4 台 /7 台– 原因がさっぱりわからない … 情報求む!
• もし心当たりがあったら教えていただけると助かります
カメラ (UserMedia) を取得
SDP のやりとり
11
PeerConnectionApplication PeerConnection Application
new
new PeerConnection()
function prepareNewConnection() { var pc_config = {"iceServers":[]}; // info of STUN/TURN var peer = null; peer = new webkitRTCPeerConnection(pc_config); peer.onicecandidate = function (evt) { … } peer.addStream(localStream); peer.addEventListener("addstream", onRemoteStreamAdded, false); peer.addEventListener("removestream", onRemoteStreamRemoved, false); return peer;}
SDP のやりとり
13
PeerConnectionApplication PeerConnection Application
new
createOffer()
setLocalDescription(sdp)
offer sdp
sendOffer()
function sendOffer() { peerConnection = prepareNewConnection(); peerConnection.createOffer( function (sessionDescription) { // in case of success peerConnection.setLocalDescription(sessionDescription); sendSDP(sessionDescription); // show in textarea }, function () { // in case of error }, mediaConstraints);}
offer SDP を生成
SDP のやりとり
16
PeerConnectionApplication PeerConnection Application
new
createOffer()
setLocalDescription(sdp)
offer sdp
sdp Copy & Paste
setRemoteDescription(sdp)
new
setOffer()
function setOffer(evt) { peerConnection = prepareNewConnection(); peerConnection.setRemoteDescription( new RTCSessionDescription(evt) );}
SDP を手動でコピー( offer )
SDP のやりとり
19
PeerConnectionApplication PeerConnection Application
new
createOffer()
setLocalDescription(sdp)
offer sdp
sdp Copy & Paste
createAnswer()
answer sdpsetLocalDescription(sdp)
setRemoteDescription(sdp)
new
sendAnswer()
function sendAnswer(evt) { peerConnection.createAnswer( function (sessionDescription) { // in case of success peerConnection.setLocalDescription(sessionDescription); sendSDP(sessionDescription); // show in textarea }, function () { // in case of error }, mediaConstraints);}
answer SDP を生成
SDP のやりとり
22
PeerConnectionApplication PeerConnection Application
new
createOffer()
setLocalDescription(sdp)
offer sdp
sdp Copy & Paste
createAnswer()
answer sdpsetLocalDescription(sdp)
setRemoteDescription(sdp)
new
sdp Copy & Paste
setRemoteDescription(sdp)
setAnswer()
function setAnswer(evt) { peerConnection.setRemoteDescription( new RTCSessionDescription(evt) );}
SDP を手動でコピー( answer )
SDP のやりとり(全体)
25
PeerConnectionApplication PeerConnection Application
new
createOffer()
setLocalDescription(sdp)
offer sdp
sdp Copy & Paste
createAnswer()
answer sdpsetLocalDescription(sdp)
setRemoteDescription(sdp)
new
sdp Copy & Paste
setRemoteDescription(sdp)
ICE Candidate のやりとり
26
PeerConnectionApplication PeerConnection Application
onIceCandidate(ice)
peer.onIceCandidate()function prepareNewConnection() { var pc_config = {"iceServers":[]}; var peer = null; peer = new webkitRTCPeerConnection(pc_config); peer.onicecandidate = function (evt) { if (evt.candidate) { sendCandidate({ // show in text area type: "candidate", sdpMLineIndex: evt.candidate.sdpMLineIndex, sdpMid: evt.candidate.sdpMid, candidate: evt.candidate.candidate }); } else { // End of candidates. } } … return peer;}
ICE Candidate のやりとり
28
PeerConnectionApplication PeerConnection Application
addIceCandidate(ice)
onIceCandidate(ice)
ice Copy & Paste
addIceCandidate()
function onCandidate(evt) { var candidate = new RTCIceCandidate({ sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate }); peerConnection.addIceCandidate(candidate);}
ICE を手動でコピー
ICE を手動でコピー
ICE Candidate のやりとり
32
PeerConnectionApplication PeerConnection Application
onIceCandidate(ice)
addIceCandidate(ice)
addIceCandidate(ice)
onIceCandidate(ice)
ice Copy & Paste
ice Copy & Paste
peer.onIceCandidate() ※同じfunction prepareNewConnection() { var pc_config = {"iceServers":[]}; var peer = null; peer = new webkitRTCPeerConnection(pc_config); peer.onicecandidate = function (evt) { if (evt.candidate) { sendCandidate({ // show in text area type: "candidate", sdpMLineIndex: evt.candidate.sdpMLineIndex, sdpMid: evt.candidate.sdpMid, candidate: evt.candidate.candidate }); } else { // End of candidates. } } … return peer;}
addIceCandidate() ※同じfunction onCandidate(evt) { var candidate = new RTCIceCandidate({ sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate }); peerConnection.addIceCandidate(candidate);}
ICE を手動でコピー(帰り)
ICE Candidate のやりとり
36
PeerConnectionApplication PeerConnection Application
onIceCandidate(ice)
addIceCandidate(ice)
addIceCandidate(ice)
onIceCandidate(ice)
onIceCandidate() : end of candidate onIceCandidate() : end of candidate
P2P stream transfer
ice Copy & Paste
ice Copy & Paste
ICE を手動でコピー(帰り)
ICE Candidate のやりとり (全体)
38
PeerConnectionApplication PeerConnection Application
onIceCandidate(ice)
addIceCandidate(ice)
addIceCandidate(ice)
onIceCandidate(ice)
onIceCandidate() : end of candidate onIceCandidate() : end of candidate
P2P stream transfer
ice Copy & Paste
ice Copy & Paste
ICE Candidate のやりとり(今回)
39
PeerConnectionApplication PeerConnection Application
onIceCandidate(ice)
addIceCandidate(ice)
addIceCandidate(ice)
onIceCandidate(ice)
P2P stream transfer
ice, ice, ice, ice Copy & Paste
ice, ice, ice, ice Copy & Paste
シグナリングサーバー編
シグナリングサーバーはどうして必要なの?
• シグナリングの過程で、情報を受け渡したい– お互いの IP アドレス– お互いのポート番号
• この段階ではお互い IP アドレスを知らない– 直接やりとりでない
• 仲介役となるシグナリングサーバーが必要– どちらブラウザもサーバーの IP アドレスを知っ
ていることが前提
Peer-to-Peer 通信の開始前に、普通のサーバー / クライアント型の通信が行われる
準備• Node.js をインストール– http://nodejs.jp/nodejs.org_ja/docs/v0.10/
• socket.io もインストール$ sudo npm install socket.io
シグナリングサーバー• WebRTC…Node.js で仲介(シグナリング)を作ってみよう
– http://html5experts.jp/mganeko/5349/
• 例えば、 signaling.js に記述、起動$ node signaling.js
var port = 9001; // as you likevar io = require('socket.io').listen(port);console.log((new Date()) + " Server is listening on port " + port); io.sockets.on('connection', function(socket) { socket.on('message', function(message) { socket.broadcast.emit('message', message); }); socket.on('disconnect', function() { socket.broadcast.emit('user disconnected'); });});
動かしてみます
DEMO
みなさんも、やってみてください
• スタートは、先ほどの手動用 HTML–その HTML/JavaScript を修正
• やることは、 HTML5Experts.jp の記事の内容– WebRTC 初心者でも簡単にできる! Node.js で仲
介(シグナリング)を作ってみよう– http://html5experts.jp/mganeko/5349/
• まず、 socket.io.js を読み込みます<script src="http://localhost:9001/socket.io/socket.io.js"></script>
ソケットの確立
49
PeerConnectionsocket
Application SignalingServer
PeerConnectionsocket
Application
connect()connect connect()
connect
connect()
var socketReady = false;var port = 9001;var socket = io.connect('http://localhost:' + port + '/');
// socket: channel connectedsocket.on('connect', onOpened) .on('message', onMessage); function onOpened(evt) { console.log('socket opened.'); socketReady = true;}
メッセージ処理// socket: accept connection requestfunction onMessage(evt) { if (evt.type === 'offer') { onOffer(evt); } else if (evt.type === 'answer' && peerStarted) { onAnswer(evt); } else if (evt.type === 'candidate' && peerStarted) { onCandidate(evt); } else if (evt.type === 'user dissconnected' && peerStarted) { stop(); }}
SDP のやりとり
52
PeerConnectionsocket
Application SignalingServer
PeerConnectionsocket
Application
connect()connect connect()
connectcreateOffer()
offer sdp
setLocalDescription(sdp)
send(sdp)send sdp
send sdp onMessage(sdp)
setRemoteDescription(sdp)
sendSDP()function sendOffer() { peerConnection = prepareNewConnection(); peerConnection.createOffer( function (sessionDescription) { // in case of success peerConnection.setLocalDescription(sessionDescription); sendSDP(sessionDescription); }, function () { // in case of error }, mediaConstraints);}
function sendSDP(sdp) { // send via socket socket.json.send(sdp);}
onMessage()
function onMessage(evt) { if (evt.type === 'offer') { onOffer(evt); } else if (evt.type === 'answer' && peerStarted) { onAnswer(evt); } else if (evt.type === 'candidate' && peerStarted) { onCandidate(evt); } else if (evt.type === 'user dissconnected' && peerStarted) { stop(); }}
onOffer(), setOffer()
function onOffer(evt) { setOffer(evt); sendAnswer(evt);}
function setOffer(evt) { peerConnection = prepareNewConnection(); peerConnection.setRemoteDescription( new RTCSessionDescription(evt) );}
SDP のやりとり
56
PeerConnectionsocket
Application SignalingServer
PeerConnectionsocket
Application
connect()connect connect()
connectcreateOffer()
offer sdp
setLocalDescription(sdp)
send(sdp)send sdp
send sdp onMessage(sdp)
setRemoteDescription(sdp)
createAnswer()answer sdp
send sdpsend(sdp)
send sdponMessage(sdp)setRemoteDescription(sdp)
setLocalDescription(sdp)
sendSDP()
function sendAnswer(evt) { peerConnection.createAnswer( function (sessionDescription) { // in case of success peerConnection.setLocalDescription(sessionDescription); sendSDP(sessionDescription); }, function () { // in case of error }, mediaConstraints);}
function sendSDP(sdp) { // send via socket socket.json.send(sdp);}
onMessage()
function onMessage(evt) { if (evt.type === 'offer') { onOffer(evt); } else if (evt.type === 'answer' && peerStarted) { onAnswer(evt); } else if (evt.type === 'candidate' && peerStarted) { onCandidate(evt); } else if (evt.type === 'user dissconnected' && peerStarted) { stop(); }}
onAnswer(), setAnswer ()
function onAnswer(evt) { setAnswer(evt);}
function setAnswer(evt) { peerConnection.setRemoteDescription( new RTCSessionDescription(evt) );}
SDP のやりとり(全体)
60
PeerConnectionsocket
Application SignalingServer
PeerConnectionsocket
Application
connect()connect connect()
connectcreateOffer()
offer sdp
setLocalDescription(sdp)
send(sdp)send sdp
send sdp onMessage(sdp)
setRemoteDescription(sdp)
createAnswer()answer sdp
send sdpsend(sdp)
send sdponMessage(sdp)setRemoteDescription(sdp)
setLocalDescription(sdp)
ICE Candidate のやりとり
61
PeerConnectionsocket
Application SignalingServer
PeerConnectionsocket
Application
send(ice) send icesend ice
onMessage(ice)
addIceCandidate(ice)
onIceCandidate(ice)
peer.onIceCandidate()function prepareNewConnection() { var pc_config = {"iceServers":[]}; var peer = null; peer = new webkitRTCPeerConnection(pc_config); peer.onicecandidate = function (evt) { if (evt.candidate) { sendCandidate({ type: "candidate", sdpMLineIndex: evt.candidate.sdpMLineIndex, sdpMid: evt.candidate.sdpMid, candidate: evt.candidate.candidate }); } else { // End of candidates. } } … return peer;}
sendCandidate()
function sendCandidate(candidate) { // send via socket socket.json.send(candidate);}
onMessage()
function onMessage(evt) { if (evt.type === 'offer') { onOffer(evt); } else if (evt.type === 'answer' && peerStarted) { onAnswer(evt); } else if (evt.type === 'candidate' && peerStarted) { onCandidate(evt); } else if (evt.type === 'user dissconnected' && peerStarted) { stop(); }}
onCandidate(), addIceCandidate()
function onCandidate(evt) { var candidate = new RTCIceCandidate({ sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate }); peerConnection.addIceCandidate(candidate);}
ICE Candidate のやりとり
66
PeerConnectionsocket
Application SignalingServer
PeerConnectionsocket
Application
onIceCandidate(ice)
send(ice) send icesend ice
onMessage(ice)
addIceCandidate(ice)
send ice send(ice)send ice
onMessage(ice)
addIceCandidate(ice)
onIceCandidate(ice)
peer.onIceCandidate() ※同じfunction prepareNewConnection() { var pc_config = {"iceServers":[]}; var peer = null; peer = new webkitRTCPeerConnection(pc_config); peer.onicecandidate = function (evt) { if (evt.candidate) { sendCandidate({ type: "candidate", sdpMLineIndex: evt.candidate.sdpMLineIndex, sdpMid: evt.candidate.sdpMid, candidate: evt.candidate.candidate }); } else { // End of candidates. } } … return peer;}
onMessage() ※同じfunction onMessage(evt) { if (evt.type === 'offer') { onOffer(evt); } else if (evt.type === 'answer' && peerStarted) { onAnswer(evt); } else if (evt.type === 'candidate' && peerStarted) { onCandidate(evt); } else if (evt.type === 'user dissconnected' && peerStarted) { stop(); }}
onCandidate(), addIceCandidate() ※同じfunction onCandidate(evt) { var candidate = new RTCIceCandidate({ sdpMLineIndex:evt.sdpMLineIndex, sdpMid:evt.sdpMid, candidate:evt.candidate }); peerConnection.addIceCandidate(candidate);}
ICE Candidate のやりとり
70
PeerConnectionsocket
Application SignalingServer
PeerConnectionsocket
Application
onIceCandidate(ice)
send(ice) send icesend ice
onMessage(ice)
addIceCandidate(ice)
send ice send(ice)send ice
onMessage(ice)
addIceCandidate(ice)
onIceCandidate(ice)
onIceCandidate() : end of candidateonIceCandidate() : end of candidate
P2P stream transfer
ICE Candidate のやりとり(全体)
71
PeerConnectionsocket
Application SignalingServer
PeerConnectionsocket
Application
onIceCandidate(ice)
send(ice) send icesend ice
onMessage(ice)
addIceCandidate(ice)
send ice send(ice)send ice
onMessage(ice)
addIceCandidate(ice)
onIceCandidate(ice)
onIceCandidate() : end of candidateonIceCandidate() : end of candidate
P2P stream transfer
Peer-to-Peer 通信確立
なぜ WebRTC に注目するのか?通信手段の破壊的進化キャリア型通信
固定電話 携帯電話 (TV 放送)
手段の例
Over The Top
Skype, WebEx (YouTube, USTREAM)
Web ブラウザ型
WebRTC
世界中の人と会話 できる ユーザメリット 世界中の人と無料 /
安価で会話できる 専用アプリ無しで 会話できる
インフラを持つ キャリアが支配市場
キャリアに縛られない 独自の仕組みを提供 する少数のベンダー が参加可能
特別な仕組みは不要 誰でも参加可能
×事業者メリット 限定的な API 提供 連携可能
完全にプログラマブル 部品として利用可能
単独で利用利用方法 ユーザが組み合わ せて利用
製品 / サービスに 組み込んで利用
コールセンター、 EC サイト、情報共有システム、など
WebRTC をどんな風に使うかは、
みなさんのアイデア次第です!
74
Thank you!
75
END