SlideShare a Scribd company logo
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 sdp
setLocalDescription(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 sdp
setLocalDescription(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 sdp
setLocalDescription(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 like
var 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
PeerConnection
socket
Application
Signaling
Server
PeerConnection
socket
Application
connect()
connect connect()
connect
connect()
var socketReady = false;
var port = 9001;
var socket = io.connect('http://localhost:' + port + '/');
// socket: channel connected
socket.on('connect', onOpened)
.on('message', onMessage);
function onOpened(evt) {
console.log('socket opened.');
socketReady = true;
}
メッセージ処理
// socket: accept connection request
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();
}
}
SDPのやりとり
52
PeerConnection
socket
Application
Signaling
Server
PeerConnection
socket
Application
connect()
connect connect()
connect
createOffer()
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
PeerConnection
socket
Application
Signaling
Server
PeerConnection
socket
Application
connect()
connect connect()
connect
createOffer()
offer sdp
setLocalDescription(sdp)
send(sdp)
send sdp
send sdp onMessage(sdp)
setRemoteDescription(sdp)
createAnswer()
answer sdp
send sdp
send(sdp)
send sdp
onMessage(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
PeerConnection
socket
Application
Signaling
Server
PeerConnection
socket
Application
connect()
connect connect()
connect
createOffer()
offer sdp
setLocalDescription(sdp)
send(sdp)
send sdp
send sdp onMessage(sdp)
setRemoteDescription(sdp)
createAnswer()
answer sdp
send sdp
send(sdp)
send sdp
onMessage(sdp)
setRemoteDescription(sdp)
setLocalDescription(sdp)
ICE Candidateのやりとり
61
PeerConnection
socket
Application
Signaling
Server
PeerConnection
socket
Application
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;
}
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
PeerConnection
socket
Application
Signaling
Server
PeerConnection
socket
Application
onIceCandidate(ice)
send(ice) send ice
send 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
PeerConnection
socket
Application
Signaling
Server
PeerConnection
socket
Application
onIceCandidate(ice)
send(ice) send ice
send ice
onMessage(ice)
addIceCandidate(ice)
send ice send(ice)
send ice
onMessage(ice)
addIceCandidate(ice)
onIceCandidate(ice)
onIceCandidate() : end of candidate
onIceCandidate() : end of candidate
P2P stream transfer
ICE Candidateのやりとり(全体)
71
PeerConnection
socket
Application
Signaling
Server
PeerConnection
socket
Application
onIceCandidate(ice)
send(ice) send ice
send ice
onMessage(ice)
addIceCandidate(ice)
send ice send(ice)
send ice
onMessage(ice)
addIceCandidate(ice)
onIceCandidate(ice)
onIceCandidate() : end of candidate
onIceCandidate() : 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

More Related Content

WebRTC meetup Tokyo 1