SlideShare a Scribd company logo
WebRTCの技術解説
WebRTC 勉強会 @ 第1回 NTT-WEST学生向けアプリ開発コンテスト
https://www.facebook.com/nttw.w.con
2014/08/05
NTT西日本
http://ntt-west.co.jp/
公開版
資料公開にあたり
当資料は学生向けWebRTCコンテストの
関連イベントであるWebRTC勉強会で
使用されたものです。
コンテストの詳細については、
下記URLをご参照下さい。
第1回 NTT-WEST学生向けアプリ開発コンテスト
https://www.facebook.com/nttw.w.con
きんじょう ゆう
金城 雄
Twitter @youkinjoh
GitHub @youkinjoh
SlideShare @You_Kinjoh
講師紹介
gihyo.jp
Jettyで始めるWebSocket超入門
http://gihyo.jp/dev/feature/01/websocket/0001
デモ参加のお願い
IN
FO
R
M
A
T
IO
N
IN
FO
R
M
A
T
IO
N
参加型デモ
カメラ付きの端末でお願いします。
できるだけ
新しい
Chrome
Android
パソコン
IN
FO
R
M
A
T
IO
N
IN
FO
R
M
A
T
IO
N
WebRTCの概要
WebRTCの2つの仕様
PeerJS
SkyWay
ユーザメディアを操作する
その他雑多な内容
今日お話しすること
質疑応答について
WebRTC
Web Real-Time Communication
リアルタイムコミュニケーションのAPI
ボイスチャット・ビデオチャットが
プラグインなしにブラウザでできる
テキストデータ・バイナリデータも送信可
P2P
WebRTC
これまでの
リアルタイム
コミュニケーション
との違い
http://www.slideshare.net/mganeko/2013-web-rtctechcross/6 より引用
キャリア型通信
手段の例
市場
ユーザ
メリット
事業者
メリット
利用方法
固定電話 携帯電話 (TV放送)
インフラを持つキャリアが支配
世界中の人と会話できる
×
単独で利用
http://www.slideshare.net/mganeko/2013-web-rtctechcross/6 より改変して引用
Over The Top
手段の例
市場
ユーザ
メリット
事業者
メリット
利用方法
Skype, WebEx
(YouTube, Ustream)
キャリアに縛られない独自の仕組みを
提供する少数のベンダーが参加可能
世界中の人と無料/安価で会話できる
限定的なAPI提供
一部連携可能
ユーザが組み合わせて利用
http://www.slideshare.net/mganeko/2013-web-rtctechcross/6 より改変して引用
Webブラウザ型
手段の例
市場
ユーザ
メリット
事業者
メリット
利用方法
WebRTC
特別な仕組みは不要
誰でも参加可能
専用アプリ無しで会話できる
完全にプログラマブル部品として
利用可能
製品/サービスに組み込んで利用
http://www.slideshare.net/mganeko/2013-web-rtctechcross/6 より改変して引用
キャリア型通信 Over The Top Webブラウザ型
手段の例
市場
ユーザ
メリット
事業者
メリット
利用方法
固定電話
携帯電話
(TV放送)
Skype, WebEx
(Youtube,
Ustream)
WebRTC
インフラを持つ
キャリアが支配
キャリアに縛られない
独自の仕組みを提供
する少数のベンダー
が参加可能
特別な仕組みは不要
誰でも参加可能
世界中の人と
会話できる
世界中の人と無料/
安価で会話できる
専用アプリ無しで
会話できる
×
限定的なAPI提供
一部連携可能
完全にプログラマブル
部品として利用可能
単独で利用
ユーザが組み合わせて
利用
製品/サービスに
組み込んで利用
http://www.slideshare.net/mganeko/2013-web-rtctechcross/6 より改変して引用
対応ブラウザ
※ 但し、iOSのブラウザは全て未対応
(2014/07現在)
今後もまだ仕様が変更になる可能性がある
ベンダープレフィックスが必要
先行したバージョンの使用がお勧め
Google Chrome Canary
Firefox Bata
Firefox Aurora
まだ策定中
ベンダープレフィックス
先行実装であることを示す慣習
ブラウザベンダーによって違う
メソッドの挙動が同じであれば、
代入してメソッド名の違いを吸収できる
navigator.getUserMedia =
navigator.getUserMedia || //Specification
navigator.webkitGetUserMedia || //for Chrome
navigator.mozGetUserMedia ; //for Firefox
ベンダープレフィックスの現状は以下を参照のこと。
https://plus.google.com/app/basic/stream/z121hnjxtqq2svgni23mznm4cxnctznc5
主な2つの仕様
Media Capture and Streams
ブラウザからカメラやマイクの
メディアストリームを取得するための仕様
WebRTC 1.0: Real-time
Communication Between
Browsers
ブラウザとブラウザをP2Pで接続し
通信を行なうための仕様
Media Capture and Streams
(getUserMedia)
ブラウザからマイクやカメラにアクセス
利用範囲はWebRTC以外とも
音声処理(with Web Audio API)
ボイスチェンジャー etc.
画像処理(with Canvas)
顔検出 etc.
顔認識ができるようになるのも時間の問題。
DEMO
SAMPLE
音声と映像を取得
Media Capture and Streams
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="script.js"></script>
<title>getUserMedia Sample</title>
</head>
<body>
<video id="video"></video>
</body>
</html>
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="script.js"></script>
<title>getUserMedia Sample</title>
</head>
<body>
<video id="video"></video>
</body>
</html>
HTML
ベンダープレフィックスの処理
ユーザメディアの取得
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="script.js"></script>
<title>getUserMedia Sample</title>
</head>
<body>
<video id="video"></video>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="script.js"></script>
<title>getUserMedia Sample</title>
</head>
<body>
<video id="video"></video>
</body>
</html>
Scriptの読み込みと
ビデオ要素の表示。
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
ベンダープレフィックス
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
ページ読み込み時の処理を指定。
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
ユーザメディアの取得開始。
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
取得するユーザメディアは
カメラとマイク。
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
ユーザメディア取得時の動作を指定。
streamはMediaStreamオブジェクト。
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
ビデオ要素の取得。
ユーザメディアの指定。
MediaStreamをURLにし、src属性に指定。
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
再生開始。
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
ユーザメディア取得失敗時の処理を指定。
以前はエラー用のcallbackなしでも動きましたが、
最近のブラウザでは指定が必須のようです。
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
ユーザメディア取得失敗時の処理。
ここではエラーログを出力しているだけ。
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
window.addEventListener('load', function() {
navigator.getUserMedia(
{video: true, audio: true},
function(stream) {
var video = document.getElementById('video');
video.src = window.URL.createObjectURL(stream);
video.play();
},
function(error) {
console.error(error);
}
);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="script.js"></script>
<title>getUserMedia Sample</title>
</head>
<body>
<video id="video"></video>
</body>
</html>
WebRTC 1.0: Real-time
Communication Between
Browsers
ブラウザとブラウザをP2Pで接続
通信は全て暗号化される
P2Pの前に要シグナリング
シグナリングサーバが必要
WebSocketが良く使われている
Node.jsならSocket.IO
WebRTC 1.0: Real-time
Communication Between
Browsers
APIが複雑でわかりにくい
抽象化した仕様の多いHTML5の
他のAPIと比べると非常に複雑
それでも、従来のリアルタイム通信の
処理よりは断然楽
ジッタやパケットロス等の対策は、
ブラウザが全て対応してくれる
DEMO
WebRTC 1.0: Real-time
Communication Between
Browsers
ICE (STUN + TURN + α)
NAT通過・ネゴシエーション
STUN
P2P・UDPホールパンチング
TURN
P2Pが不可能ならサーバ経由で通信
WebRTC 1.0: Real-time
Communication Between
Browsers
SDP
セッションプロトコルを記述
セッション開始に必要な情報
DTLS (UDP等のデータグラム向けのTLS)
データ通信は全て暗号化される
WebRTC 1.0: Real-time
Communication Between
Browsers
MediaStream (SRTP・SRTCP)
音声データ・映像データ
SRTP (RTPのセキュア版)
リアルタイムデータ配信の仕様
SRTCP (RTCPのセキュア版)
配信用制御プロトコル
WebRTC 1.0: Real-time
Communication Between
Browsers
DataChannel (SCTP)
テキストデータ・バイナリデータ
SCTP
TCPとUDPの良いところ取りをしたプロトコル
標準ではTCPに似た動作をする
設定で信頼性と引き換えにUDPに似た動作にでき、
リアルタイム性の向上が可能
どの処理をブラウザが
勝手にやってくれるのか、
どの処理を自分で
実装する必要があるのかを
把握しないと
実装時に大変混乱する。
どうやって
ブラウザで
P2Pを
実現しているのか
?????????? ??????????
画面上部にメソッド名等の
キーワードが表示されます。
実装時にご活用下さい。
Browser Browser
NAT NAT
NATが邪魔して直接通信ができない。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
HTML+JS+CSS
Global IP/Port
signaling
HTML+JS+CSS
Global IP/Port
signaling
data
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
ブラウザでWebRTCを使った
ページにアクセス。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
HTML+JS+CSS
HTTP Request
HTTP Response
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Signaling Serverとして利用する
WebSocket Serverに接続。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
signaling
WebSocket open
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
signaling
対向のブラウザもWebRTCを使った
ページにアクセス。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
HTML+JS+CSS
HTTP Request
HTTP Response
signaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
signaling
対向のブラウザも
WebSocket Serverに接続。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
signaling signaling
WebSocket open
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
signaling signaling
以後、WebSocketは接続を継続する。
この先、説明の都合上、接続中でもグレーアウトします。
データが流れる時だけ色がつきます。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
接続を開始する側でoffer開始。
SDPを生成・自身に登録後送信。
対向のブラウザは受け取ったSDPを登録。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Offer SDP
createOffer
SDPは以下の
情報を含む。
メディアタイプ
コーデック
帯域幅 etc.
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Offer SDP
setLocalDescription
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
WebSocket send
Offer SDP
signaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Offer SDP
signaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
setRemoteDescription
Offer SDP
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
受け取ったofferに対してanswer開始。
SDPを生成・自身に登録後送信。
offerした側でも対向のSDPを登録。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
createAnswer
SDPは以下の
情報を含む。
メディアタイプ
コーデック
帯域幅 etc.
Answer SDP
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
setLocalDescription
Answer SDP
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
WebSocket send
Answer SDP
signaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Answer SDP
signaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
setRemoteDescription
Answer SDP
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
P2Pの為に相手にIPを伝える必要がある。
その為にSTUNを使い自身のIPを調べる。
ブラウザが裏で処理してくれる。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
ここの
Global IP/Port
を知りたい
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Global IP/Port
Global
IP/Port
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Global IP/Port
Global
IP/Port
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
ここの
Global IP/Port
を知りたい
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Global IP/Port
Global
IP/Port
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
Global IP/Port
Global
IP/Port
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
自身のIP/Portは把握。
自身に接続できそうな経路の候補を
相手側に伝える必要がある。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
ICE
candidate
ブラウザは
これまでの
情報を元に
裏で経路候補
(candidate)を
生成。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
ICE
candidate
icecandidate Event
イベントに
登録した
ハンドラ経由で
経路候補を取得。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
WebSocket send
ICE
candidatesignaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
ICE
candidate
signaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
addIceCandidate
ICE
candidate
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
ブラウザは
STUNからの
情報を元に
裏で経路候補
(candidate)を
生成。
ICE
candidate
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
icecandidate Event
ICE
candidate
イベントに
登録した
ハンドラ経由で
経路候補を取得。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
WebSocket send
ICE
candidate signaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
ICE
candidate
signaling
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
addIceCandidate
ICE
candidate
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
P2Pで通信する為の情報が
(やっと)整った。
通信開始。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
data
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
それでもP2Pが無理だった場合、
TURNサーバが中継を行なう。
TURN経由でもデータの暗号化は解かれずセキュア。
Web Server
WebSocket
Server
ICE Server
(STUN + TURN)
Browser Browser
NAT NAT
data data
RTCPeerConnection
ストリーミングを扱うための
WebRTCの中心となるオブジェクト
RTCSessionDescription
SDPを扱うオブジェクト
RTCIceCandidate
経路情報を扱うオブジェクト
RTCDataChannel
テキスト・バイナリ用のデータチャネル
SAMPLE
このサンプルでは細かい制御は行なっていないので注意してください。
ビデオチャット
(二者間通信)
WebRTC 1.0: Real-time
Communication Between
Browsers
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
window.RTCPeerConnection =
window.RTCPeerConnection ||
window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection ;
window.RTCSessionDescription =
window.RTCSessionDescription ||
window.webkitRTCSessionDescription ||
window.mozRTCSessionDescription ;
window.RTCIceCandidate =
window.RTCIceCandidate ||
window.webkitRTCIceCandidate ||
window.mozRTCIceCandidate ;
var ws = null;
var peer = null;
function initialize() {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
peer = new RTCPeerConnection({
iceServers: [
{url: 'stun:stun.l.google.com:19302'},
{url: 'stun:23.21.150.121'}
]
});
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
peer.addStream(stream);
},
function(error) {
console.error(error);
}
);
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
peer.addEventListener('icecandidate', function(evt) {
if (!evt.candidate) {return;}
var candidate = evt.candidate;
ws.send(JSON.stringify({candidate: candidate}));
});
peer.addEventListener('addstream', function(evt) {
var video = document.getElementById('remote');
video.src = URL.createObjectURL(evt.stream);
video.play();
});
var offerbtn = document.getElementById('offer_button');
offerbtn.addEventListener('click', offer);
}
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="scripts/script.js"></script>
<title>WebRTC Sample</title>
</head>
<body>
<input type="button" value="offer" id="offer_button" />
<video id="local" autoplay="autoplay"></video>
<video id="remote" autoplay="autoplay"></video>
</body>
</html>
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
window.RTCPeerConnection =
window.RTCPeerConnection ||
window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection ;
window.RTCSessionDescription =
window.RTCSessionDescription ||
window.webkitRTCSessionDescription ||
window.mozRTCSessionDescription ;
window.RTCIceCandidate =
window.RTCIceCandidate ||
window.webkitRTCIceCandidate ||
window.mozRTCIceCandidate ;
var ws = null;
var peer = null;
function initialize() {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
peer = new RTCPeerConnection({
iceServers: [
{url: 'stun:stun.l.google.com:19302'},
{url: 'stun:23.21.150.121'}
]
});
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
peer.addStream(stream);
},
function(error) {
console.error(error);
}
);
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
peer.addEventListener('icecandidate', function(evt) {
if (!evt.candidate) {return;}
var candidate = evt.candidate;
ws.send(JSON.stringify({candidate: candidate}));
});
peer.addEventListener('addstream', function(evt) {
var video = document.getElementById('remote');
video.src = URL.createObjectURL(evt.stream);
video.play();
});
var offerbtn = document.getElementById('offer_button');
offerbtn.addEventListener('click', offer);
}
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="scripts/script.js"></script>
<title>WebRTC Sample</title>
</head>
<body>
<input type="button" value="offer" id="offer_button" />
<video id="local" autoplay="autoplay"></video>
<video id="remote" autoplay="autoplay"></video>
</body>
</html>
HTML
ベンダープレフィックスの
処理
WebSocketと
PeerConnectionの
初期化
ユーザメディアの取得
WebSocketの
イベント登録
PeerConnectionの
イベント登録
変数宣言
ボタンのイベント登録
offer処理
answer処理
onload
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="scripts/script.js"></script>
<title>WebRTC Sample</title>
</head>
<body>
<input type="button" value="offer" id="offer_button" />
<video id="local" autoplay="autoplay"></video>
<video id="remote" autoplay="autoplay"></video>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="scripts/script.js"></script>
<title>WebRTC Sample</title>
</head>
<body>
<input type="button" value="offer" id="offer_button" />
<video id="local" autoplay="autoplay"></video>
<video id="remote" autoplay="autoplay"></video>
</body>
</html>
Scriptの読み込みと
offer開始用のボタンと
ビデオ要素の表示。
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
ベンダープレフィックス
window.RTCPeerConnection =
window.RTCPeerConnection ||
window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection ;
window.RTCSessionDescription =
window.RTCSessionDescription ||
window.webkitRTCSessionDescription ||
window.mozRTCSessionDescription ;
window.RTCIceCandidate =
window.RTCIceCandidate ||
window.webkitRTCIceCandidate ||
window.mozRTCIceCandidate ;
ベンダープレフィックス
var ws = null;
var peer = null;
function initialize() {
// other slides
}
window.addEventListener('load', initialize);
WebSocketオブジェクトと
RTCPeerConnectionオブジェクトの
変数宣言と、初期化関数の定義。
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
peer = new RTCPeerConnection({
iceServers: [
{url: 'stun:stun.l.google.com:19302'},
{url: 'stun:23.21.150.121'}
]
});
シグナリングに使う
WebSocketの接続開始。
initialize内
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
peer = new RTCPeerConnection({
iceServers: [
{url: 'stun:stun.l.google.com:19302'},
{url: 'stun:23.21.150.121'}
]
});
RTCPeerConnection初期化。
STUNサーバ/TURNサーバを
引数に指定(複数可)。
initialize内
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
peer.addStream(stream);
},
function(error) {
console.error(error);
}
);
ユーザメディアを取得し表示。
メディアストリームを
RTCPeerConnectionに登録。
initialize内
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
peer.addStream(stream);
},
function(error) {
console.error(error);
}
);
ユーザメディアの取得・表示。
メディアストリームを
RTCPeerConnectionに登録。
initialize内
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
WebSocketに
イベントハンドラを指定。
initialize内
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
WSのメッセージ受信時の処理を指定。
(SDP用)
initialize内
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
SDPが含まれているか判定。
RTCSessionDescriptionを生成。
initialize内
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
RTCSessionDescriptionを
PeerConnectionに登録。
合わせてcallbackを指定。
initialize内
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
受け取ったSDPがofferだった場合、
自動的にanswer処理を実行。
answerの内容は後述。
initialize内
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
initialize内
WSのメッセージ受信時の処理を指定。
(経路情報用)
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
initialize内
経路情報が含まれているか判定。
RTCIceCandidateを生成。
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
initialize内
RTCIceCandidateを
PeerConnectionに登録。
peer.addEventListener('icecandidate', function(evt) {
if (!evt.candidate) {return;}
var candidate = evt.candidate;
ws.send(JSON.stringify({candidate: candidate}));
});
peer.addEventListener('addstream', function(evt) {
var video = document.getElementById('remote');
video.src = URL.createObjectURL(evt.stream);
video.play();
});
initialize内
RTCPeerConnectionに
イベントハンドラを指定。
peer.addEventListener('icecandidate', function(evt) {
if (!evt.candidate) {return;}
var candidate = evt.candidate;
ws.send(JSON.stringify({candidate: candidate}));
});
peer.addEventListener('addstream', function(evt) {
var video = document.getElementById('remote');
video.src = URL.createObjectURL(evt.stream);
video.play();
});
RTCPeerConnectionの
経路候補取得時の処理を指定。
initialize内
peer.addEventListener('icecandidate', function(evt) {
if (!evt.candidate) {return;}
var candidate = evt.candidate;
ws.send(JSON.stringify({candidate: candidate}));
});
peer.addEventListener('addstream', function(evt) {
var video = document.getElementById('remote');
video.src = URL.createObjectURL(evt.stream);
video.play();
});
経路候補が含まれているか判定。
経路候補を対向に送信。
initialize内
peer.addEventListener('icecandidate', function(evt) {
if (!evt.candidate) {return;}
var candidate = evt.candidate;
ws.send(JSON.stringify({candidate: candidate}));
});
peer.addEventListener('addstream', function(evt) {
var video = document.getElementById('remote');
video.src = URL.createObjectURL(evt.stream);
video.play();
});
RTCPeerConnectionの
Stream(対向)取得時の処理を指定。
initialize内
peer.addEventListener('icecandidate', function(evt) {
if (!evt.candidate) {return;}
var candidate = evt.candidate;
ws.send(JSON.stringify({candidate: candidate}));
});
peer.addEventListener('addstream', function(evt) {
var video = document.getElementById('remote');
video.src = URL.createObjectURL(evt.stream);
video.play();
});
対向のMediaStreamを表示。
initialize内
var offerbtn = document.getElementById('offer_button');
offerbtn.addEventListener('click', offer);
offer開始用のボタンのクリック時に
offer処理を実行。
offerの内容は後述。
initialize内
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
offer処理。
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
offer処理宣言。
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
offer生成。
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
offer生成成功時の動作を指定。
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
取得したofferを
PeerConnectionに登録。
合わせてcallbackを指定。
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
offer登録後、
offerを対向に送信。
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
offer取得失敗時の処理を指定。
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
offer取得失敗時の処理。
ここではエラーログを出力しているだけ。
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
answer処理。
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
answer処理宣言。
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
answer生成。
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
answer生成成功時の動作を指定。
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
取得したanswerを
PeerConnectionに登録。
合わせてcallbackを指定。
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
answer登録後、
answerを対向に送信。
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
answer取得失敗時の処理を指定。
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
answer取得失敗時の処理。
ここではエラーログを出力しているだけ。
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
window.RTCPeerConnection =
window.RTCPeerConnection ||
window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection ;
window.RTCSessionDescription =
window.RTCSessionDescription ||
window.webkitRTCSessionDescription ||
window.mozRTCSessionDescription ;
window.RTCIceCandidate =
window.RTCIceCandidate ||
window.webkitRTCIceCandidate ||
window.mozRTCIceCandidate ;
var ws = null;
var peer = null;
function initialize() {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
peer = new RTCPeerConnection({
iceServers: [
{url: 'stun:stun.l.google.com:19302'},
{url: 'stun:23.21.150.121'}
]
});
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
peer.addStream(stream);
},
function(error) {
console.error(error);
}
);
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.sdp) {return;}
var sdp = data.sdp;
var description = new RTCSessionDescription(sdp);
peer.setRemoteDescription(description, function() {
if (description.type === 'offer') {
answer();
}
});
});
ws.addEventListener('message', function(evt) {
var data = JSON.parse(evt.data);
if (!data.candidate) {return;}
var candidate = new RTCIceCandidate(data.candidate);
peer.addIceCandidate(candidate);
});
peer.addEventListener('icecandidate', function(evt) {
if (!evt.candidate) {return;}
var candidate = evt.candidate;
ws.send(JSON.stringify({candidate: candidate}));
});
peer.addEventListener('addstream', function(evt) {
var video = document.getElementById('remote');
video.src = URL.createObjectURL(evt.stream);
video.play();
});
var offerbtn = document.getElementById('offer_button');
offerbtn.addEventListener('click', offer);
}
function offer() {
peer.createOffer(
function(offer) {
peer.setLocalDescription(offer, function() {
ws.send(JSON.stringify({sdp: offer}));
});
},
function(error) {
console.error(error);
}
);
}
function answer() {
peer.createAnswer(
function(answer) {
peer.setLocalDescription(answer, function() {
ws.send(JSON.stringify({sdp: answer}));
});
},
function(error) {
console.error(error);
}
);
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="scripts/script.js"></script>
<title>WebRTC Sample</title>
</head>
<body>
<input type="button" value="offer" id="offer_button" />
<video id="local" autoplay="autoplay"></video>
<video id="remote" autoplay="autoplay"></video>
</body>
</html>
Library / PaaS
APIのWrapperや、
Signaling Server機能を含むもの、
room機能の有無など様々。
ICE Serverを提供しているものも。
クライアント側の実装に
集中したい場合は、導入の価値あり。
サーバ側を細かく制御したい場合は
使わないという選択肢も。
Library / PaaS
simpleRTC
room機能あり
PeerJS
日本のドキュメントあり(SkyWay内)
SkyWay
PeerJSを使ったPaaS
他にもたくさんあります。
コンテストで提供する実行環境には、PeerServerがインストールされています。
PeerJS
Offer/Answerは
PeerJSが裏でやってくれる。
接続先を制御するために、
新たにID(PeerID)の概念が増える。
(電話番号の概念に近い。)
通信開始には相手のIDが必要。
IDを意識させずに自動接続するには、
WebSocket等でID交換の必要がある。
Peer
PeerJSの本体。
MediaConnection
MediaStreamのWrapper。
DataConnection
DataChannelのWrapper。
PeerJS
SAMPLE
ビデオチャット
(多者間通信)
PeerJS版
with PeerServer Cloud
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var PEERJS_API_KEY = '[PEERJS_API_KEY]';
var ws = null;
var peer = null;
var selfid = null;
var localStream = null;
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
initializeWebSocket(function() {
ws.send(selfid);
});
});
});
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="scripts/script.js"></script>
<title>PeerJS Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var PEERJS_API_KEY = '[PEERJS_API_KEY]';
var ws = null;
var peer = null;
var selfid = null;
var localStream = null;
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
initializeWebSocket(function() {
ws.send(selfid);
});
});
});
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="scripts/script.js"></script>
<title>PeerJS Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
HTML
ベンダープレフィックスの
処理
API Keyと変数宣言
PeerJSの
初期化とイベント登録
ユーザメディアの取得
WebSocketの
初期化とイベント登録
MediaConnectionの
設定
(主にイベント登録)
onloadと
初期化処理の呼び出しと
シグナリング開始
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="scripts/script.js"></script>
<title>PeerJS Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="scripts/script.js"></script>
<title>PeerJS Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
LibraryとScriptの読み込みと
ビデオ要素の表示と
対向の表示領域の準備。
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var PEERJS_API_KEY = '[PEERJS_API_KEY]';
var ws = null;
var peer = null;
var selfid = null;
var localStream = null;
ベンダープレフィックスの処理。
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var PEERJS_API_KEY = '[PEERJS_API_KEY]';
var ws = null;
var peer = null;
var selfid = null;
var localStream = null;
API Key、WebSocketとPeerJS、
自分のidとStream用の変数宣言。
PeerServer Cloud service の API Key 取得は以下のURLから。
http://peerjs.com/peerserver
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}PeerJSの各種設定。
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
PeerJSの初期化。
要API Key。
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
PeerServerと接続時に
自身のPeerIDを取得する。
ローカル変数にPeerIDを保存。
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
初期化処理のためのcallback。
callbackの詳細は後述。
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
相手から通話依頼に、自動応答させる。
MediaConnectionの設定。
MediaConnectionの設定の詳細は後述。
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
PeerServerとの通信が切断した時、
Peerオブジェクトを破棄。
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
エラー処理。
ここではエラーログを出力しているだけ。
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
UserMediaの各種設定。
同じ説明の繰り返しになるので、ここは簡単に解説。
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
ユーザメディアを取得し表示。
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
初期化処理のためのcallback。
callbackの詳細は後述。
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
WebSocketの各種設定。
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
PeerIDを自動で交換して接続するための
WebSocketの接続開始。
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
WebSocket接続時の処理を指定。
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
初期化処理のためのcallback。
callbackの詳細は後述。
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
WSのメッセージ受信時の処理を指定。
(PeerID用)
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
受け取ったPeerIDに、自動発信させる。
MediaConnectionの設定。
MediaConnectionの設定の詳細は後述。
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
発信/応答時に得た
MediaConnectionに設定。
(主にイベント登録。)
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
発信/応答先のPeerIDを取得。
StreamとVideo要素の保存領域。
closure使用。
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
Stream(対向)取得時の処理を指定。
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
対向のMediaStreamを表示。
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
MediaConnectionが切断した時、
URLオブジェクトの削除と
Video要素を削除。
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
エラー処理。
ここではエラーログを出力しているだけ。
function initialize() {
initializePeer(function() {
initializeMedia(function() {
initializeWebSocket(function() {
ws.send(selfid);
});
});
});
}
window.addEventListener('load', initialize);
初期化処理。
callback地獄。
function initialize() {
initializePeer(function() {
initializeMedia(function() {
initializeWebSocket(function() {
ws.send(selfid);
});
});
});
}
window.addEventListener('load', initialize);
Peerとメディアの初期化が終わらないと
PeerIDの送信が行なえないため。
DefferedやPromiseを使えばもっと綺麗に書ける。
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var PEERJS_API_KEY = '[PEERJS_API_KEY]';
var ws = null;
var peer = null;
var selfid = null;
var localStream = null;
function initializePeer(callback) {
peer = new Peer({key: PEERJS_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
initializeWebSocket(function() {
ws.send(selfid);
});
});
});
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="scripts/script.js"></script>
<title>PeerJS Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
SkyWay
PeerJSを使ったPaaS。
PeerServer Cloudで
できることに加え、
接続しているRemoteの一覧を
RestAPIで取得可能。
IDを意識させずに自動接続する場合でも、
RestAPIでIDを取得できるため、
静的ファイルのみで構築可能。
RestAPI
接続しているPeerIDの一覧を取得
接続しているPeerの数を取得
SkyWay
PeerServerでも、起動時に allowDiscovery オプションを指定すると、
同様のことが可能。
SAMPLE
ビデオチャット
(多者間通信)
SkyWay版
(静的ファイルのみで動作可能)
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var SKYWAY_API_KEY = '[SKYWAY_API_KEY]';
var REST_API_LIST = 'https://skyway.io/v2/active/list/';
var peer = null;
var selfid = null;
var localStream = null;
function initializePeer(callback) {
peer = new Peer({key: SKYWAY_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
callRemoteAll();
});
});
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="https://skyway.io/dist/v2/0.3/peer.js"></
script>
<script src="scripts/script.js"></script>
<title>SkyWay Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var SKYWAY_API_KEY = '[SKYWAY_API_KEY]';
var REST_API_LIST = 'https://skyway.io/v2/active/list/';
var peer = null;
var selfid = null;
var localStream = null;
function initializePeer(callback) {
peer = new Peer({key: SKYWAY_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
callRemoteAll();
});
});
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="https://skyway.io/dist/v2/0.3/peer.js"></
script>
<script src="scripts/script.js"></script>
<title>SkyWay Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
HTML PeerJSの
初期化とイベント登録
ユーザメディアの取得
MediaConnectionの
設定
(主にイベント登録)
onloadと
初期化処理の呼び出しと
発信処理開始
接続先の一覧所得と
発信処理
ベンダープレフィックスの
処理
API Keyと変数宣言
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var SKYWAY_API_KEY = '[SKYWAY_API_KEY]';
var REST_API_LIST = 'https://skyway.io/v2/active/list/';
var peer = null;
var selfid = null;
var localStream = null;
function initializePeer(callback) {
peer = new Peer({key: SKYWAY_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
callRemoteAll();
});
});
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="https://skyway.io/dist/v2/0.3/peer.js"></
script>
<script src="scripts/script.js"></script>
<title>SkyWay Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
主に
PeerJS版との
差分のみを解説
...
<link rel="stylesheet" href="styles/style.css" />
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="scripts/script.js"></script>
...
...
<link rel="stylesheet" href="styles/style.css" />
<script src="https://skyway.io/dist/v2/0.3/peer.js"></script>
<script src="scripts/script.js"></script>
...
読み込むLibraryの変更。
var PEERJS_API_KEY = '[PEERJS_API_KEY]';
var SKYWAY_API_KEY = '[SKYWAY_API_KEY]';
var REST_API_LIST = 'https://skyway.io/v2/active/list/';
API KeyをSkyWay用に変更。
接続しているPeerIDの
一覧を取得するRestAPIのURL。
SkyWay の API Key 取得は以下のURLから。
http://nttcom.github.io/skyway/registration.html
...
var ws = null;
...
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
...
...
var ws = null;
...
function initializeWebSocket(callback) {
var secure = location.protocol === 'https:';
var protocol = secure ? 'wss' : 'ws';
var url = protocol + '://' + location.host + '/';
ws = new WebSocket(url);
ws.addEventListener('open', function() {
callback();
});
ws.addEventListener('message', function(evt) {
var remoteid = evt.data;
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
});
}
...
WebSocketの処理は削除。
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}
RestAPIからPeerIDの一覧を取得し、
全てに発信。
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}RestAPIのURL + API Key。
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}
Ajax(XHR)お決まりの記述。
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}接続しているPeerIDの一覧を取得。
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}
RestAPIからPeerIDの一覧を取得し、
全てに自動発信。
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}
MediaConnectionの設定。
function initialize() {
initializePeer(function() {
initializeMedia(function() {
initializeWebSocket(function() {
ws.send(selfid);
});
});
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
callRemoteAll();
});
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
initializeWebSocket(function() {
ws.send(selfid);
});
});
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
callRemoteAll();
});
});
}
WebSocketの初期化削除。
シグナリング開始処理削除。
発信処理追加。
navigator.getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ;
window.URL =
window.URL ||
window.webkitURL ;
var SKYWAY_API_KEY = '[SKYWAY_API_KEY]';
var REST_API_LIST = 'https://skyway.io/v2/active/list/';
var peer = null;
var selfid = null;
var localStream = null;
function initializePeer(callback) {
peer = new Peer({key: SKYWAY_API_KEY});
peer.on('open', function(id) {
selfid = id;
callback();
});
peer.on('call', function(mediaConnection) {
mediaConnection.answer(localStream);
settingMediaConnection(mediaConnection);
});
peer.on('close', function() {
peer.destroy();
});
peer.on('error', function(err) {
console.error(err);
});
}
function initializeMedia(callback) {
navigator.getUserMedia(
{audio: true, video: true},
function(stream) {
localStream = stream;
var video = document.getElementById('local');
video.src = URL.createObjectURL(stream);
video.play();
callback();
},
function(error) {
console.error(error);
}
);
}
function callRemoteAll() {
var url = REST_API_LIST + SKYWAY_API_KEY;
var xhr = new XMLHttpRequest();
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState != 4) {return;}
if (xhr.status != 200) {return;}
var remoteids = JSON.parse(xhr.responseText);
for (var i = 0; i < remoteids.length; i++) {
var remoteid = remoteids[i];
var mediaConnection =
peer.call(remoteid, localStream);
settingMediaConnection(mediaConnection);
}
});
xhr.open('GET', url);
xhr.send();
}
function settingMediaConnection(mediaConnection) {
var remoteid = mediaConnection.peer;
var remoteStream = null;
var video = null;
mediaConnection.on('stream', function(stream) {
video = document.createElement('video');
video.src = URL.createObjectURL(stream);
video.play();
var parent = document.getElementById('remotes');
parent.appendChild(video);
});
mediaConnection.on('close', function() {
URL.revokeObjectURL(video.src);
video.parentNode.removeChild(video);
});
mediaConnection.on('error', function() {
console.error(err);
});
}
function initialize() {
initializePeer(function() {
initializeMedia(function() {
callRemoteAll();
});
});
}
window.addEventListener('load', initialize);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/style.css" />
<script src="https://skyway.io/dist/v2/0.3/peer.js"></
script>
<script src="scripts/script.js"></script>
<title>SkyWay Sample</title>
</head>
<body>
<video id="local"></video>
<div id="remotes"></div>
</body>
</html>
ユーザメディアを
操作する
Canvas
Web Audio
API
Media
Stream
Media
Stream
音声処理
画像処理
現在の実装状況
MediaStream Processing APIの中にCanvas Recordingという
Canvas要素からstreamを取得する仕様案があったが進展なし?
Canvas
Web Audio
API
Media
Stream
Media
Stream
音声処理
画像処理
処理した音声は送信可能
処理した映像は送信不可
受信側ではどちらも処理可能
画像をバイナリデータとして
DataChannelで送信・連続して表示することは可能ですが、
ジッタ処理等のWebRTCのメディア処理の恩恵を受けることができません。
必要であれば、ジッタ処理等を自分で実装することになります。
Canvas
Web Audio
API 再生
Media
Stream
音声処理
画像処理
表示
MediaStream
Web Audio API
SAMPLE
簡易
ボイスチェンジャー
MediaStream <=> Web Audio API
当ボイスチェンジャーで変換した音声は、
音声解析によりほぼ元の音声を復元可能であり、
匿名性を担保するものではありません。
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
ユーザメディアを
Web Audio APIで編集し、
RTCPeerConnectionに渡す。
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
getUserMediaで音声を取得。
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
Web Audio APIの
AudioContextをインスタンス化。
ベンダープレフィックス(例:webkitAudioContext)
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
MediaStreamをWeb Audio APIの
Source(入力側)にする。
WebRTCの世界からWeb Audio APIの世界へ。
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
音声処理をJavaScriptで行なうための
ScriptProcessorをインスタンス化。
旧名:JavaScriptNode。古い資料を見るときは注意。
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
Web Audio APIの
Destination(出力側)をインスタンス化。
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
音声処理に使うfunction(後述)を
ScriptProcessorのイベントに登録。
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
source(入力側)
scriptProcessor
destination(出力側)の順に接続。
navigator.getUserMedia({audio: true},
function(inputStream) {
var audioContext = new AudioContext(); // 要ベンダープレフィックス
var mediastreamsource = audioContext.createMediaStreamSource(inputStream);
var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
var mediastreamdestination = audioContext.createMediaStreamDestination();
scriptProcessor.addEventListener('audioprocess', onAudioProcess);
mediastreamsource.connect(scriptProcessor);
scriptProcessor.connect(mediastreamdestination);
var outputStream = mediastreamdestination.stream;
peer.addStream(outputStream);
},
function(error) {}
);
function onAudioProcess(evt) {
var input = evt.inputBuffer.getChannelData(0);
var output = evt.outputBuffer.getChannelData(0);
var bufferData = new Float32Array(bufferSize);
for (var i = 0; i < bufferSize; i++) {
bufferData[i] = (
(input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 +
input[Math.floor(i / 2) % bufferSize]
) / 2;
}
output.set(bufferData);
}
Web Audio APIのSource(入力側)を
MediaStreamにする。
Web Audio APIの世界からWebRTCの世界へ。
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版
WebRTCの技術解説 公開版

More Related Content

WebRTCの技術解説 公開版

  • 1. WebRTCの技術解説 WebRTC 勉強会 @ 第1回 NTT-WEST学生向けアプリ開発コンテスト https://www.facebook.com/nttw.w.con 2014/08/05 NTT西日本 http://ntt-west.co.jp/ 公開版
  • 3. きんじょう ゆう 金城 雄 Twitter @youkinjoh GitHub @youkinjoh SlideShare @You_Kinjoh 講師紹介 gihyo.jp Jettyで始めるWebSocket超入門 http://gihyo.jp/dev/feature/01/websocket/0001
  • 13. Over The Top 手段の例 市場 ユーザ メリット 事業者 メリット 利用方法 Skype, WebEx (YouTube, Ustream) キャリアに縛られない独自の仕組みを 提供する少数のベンダーが参加可能 世界中の人と無料/安価で会話できる 限定的なAPI提供 一部連携可能 ユーザが組み合わせて利用 http://www.slideshare.net/mganeko/2013-web-rtctechcross/6 より改変して引用
  • 15. キャリア型通信 Over The Top Webブラウザ型 手段の例 市場 ユーザ メリット 事業者 メリット 利用方法 固定電話 携帯電話 (TV放送) Skype, WebEx (Youtube, Ustream) WebRTC インフラを持つ キャリアが支配 キャリアに縛られない 独自の仕組みを提供 する少数のベンダー が参加可能 特別な仕組みは不要 誰でも参加可能 世界中の人と 会話できる 世界中の人と無料/ 安価で会話できる 専用アプリ無しで 会話できる × 限定的なAPI提供 一部連携可能 完全にプログラマブル 部品として利用可能 単独で利用 ユーザが組み合わせて 利用 製品/サービスに 組み込んで利用 http://www.slideshare.net/mganeko/2013-web-rtctechcross/6 より改変して引用
  • 18. ベンダープレフィックス 先行実装であることを示す慣習 ブラウザベンダーによって違う メソッドの挙動が同じであれば、 代入してメソッド名の違いを吸収できる navigator.getUserMedia = navigator.getUserMedia || //Specification navigator.webkitGetUserMedia || //for Chrome navigator.mozGetUserMedia ; //for Firefox ベンダープレフィックスの現状は以下を参照のこと。 https://plus.google.com/app/basic/stream/z121hnjxtqq2svgni23mznm4cxnctznc5
  • 19. 主な2つの仕様 Media Capture and Streams ブラウザからカメラやマイクの メディアストリームを取得するための仕様 WebRTC 1.0: Real-time Communication Between Browsers ブラウザとブラウザをP2Pで接続し 通信を行なうための仕様
  • 20. Media Capture and Streams (getUserMedia) ブラウザからマイクやカメラにアクセス 利用範囲はWebRTC以外とも 音声処理(with Web Audio API) ボイスチェンジャー etc. 画像処理(with Canvas) 顔検出 etc. 顔認識ができるようになるのも時間の問題。
  • 21. DEMO
  • 23. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="script.js"></script> <title>getUserMedia Sample</title> </head> <body> <video id="video"></video> </body> </html>
  • 24. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="script.js"></script> <title>getUserMedia Sample</title> </head> <body> <video id="video"></video> </body> </html> HTML ベンダープレフィックスの処理 ユーザメディアの取得
  • 25. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="script.js"></script> <title>getUserMedia Sample</title> </head> <body> <video id="video"></video> </body> </html>
  • 26. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="script.js"></script> <title>getUserMedia Sample</title> </head> <body> <video id="video"></video> </body> </html> Scriptの読み込みと ビデオ要素の表示。
  • 27. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; ベンダープレフィックス
  • 28. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); });
  • 29. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); ページ読み込み時の処理を指定。
  • 30. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); ユーザメディアの取得開始。
  • 31. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); 取得するユーザメディアは カメラとマイク。
  • 32. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); ユーザメディア取得時の動作を指定。 streamはMediaStreamオブジェクト。
  • 33. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); ビデオ要素の取得。 ユーザメディアの指定。 MediaStreamをURLにし、src属性に指定。
  • 34. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); 再生開始。
  • 35. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); ユーザメディア取得失敗時の処理を指定。 以前はエラー用のcallbackなしでも動きましたが、 最近のブラウザでは指定が必須のようです。
  • 36. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); ユーザメディア取得失敗時の処理。 ここではエラーログを出力しているだけ。
  • 37. window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); });
  • 38. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; window.addEventListener('load', function() { navigator.getUserMedia( {video: true, audio: true}, function(stream) { var video = document.getElementById('video'); video.src = window.URL.createObjectURL(stream); video.play(); }, function(error) { console.error(error); } ); }); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="script.js"></script> <title>getUserMedia Sample</title> </head> <body> <video id="video"></video> </body> </html>
  • 39. WebRTC 1.0: Real-time Communication Between Browsers ブラウザとブラウザをP2Pで接続 通信は全て暗号化される P2Pの前に要シグナリング シグナリングサーバが必要 WebSocketが良く使われている Node.jsならSocket.IO
  • 40. WebRTC 1.0: Real-time Communication Between Browsers APIが複雑でわかりにくい 抽象化した仕様の多いHTML5の 他のAPIと比べると非常に複雑 それでも、従来のリアルタイム通信の 処理よりは断然楽 ジッタやパケットロス等の対策は、 ブラウザが全て対応してくれる
  • 41. DEMO
  • 42. WebRTC 1.0: Real-time Communication Between Browsers ICE (STUN + TURN + α) NAT通過・ネゴシエーション STUN P2P・UDPホールパンチング TURN P2Pが不可能ならサーバ経由で通信
  • 43. WebRTC 1.0: Real-time Communication Between Browsers SDP セッションプロトコルを記述 セッション開始に必要な情報 DTLS (UDP等のデータグラム向けのTLS) データ通信は全て暗号化される
  • 44. WebRTC 1.0: Real-time Communication Between Browsers MediaStream (SRTP・SRTCP) 音声データ・映像データ SRTP (RTPのセキュア版) リアルタイムデータ配信の仕様 SRTCP (RTCPのセキュア版) 配信用制御プロトコル
  • 45. WebRTC 1.0: Real-time Communication Between Browsers DataChannel (SCTP) テキストデータ・バイナリデータ SCTP TCPとUDPの良いところ取りをしたプロトコル 標準ではTCPに似た動作をする 設定で信頼性と引き換えにUDPに似た動作にでき、 リアルタイム性の向上が可能
  • 50. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT HTML+JS+CSS Global IP/Port signaling HTML+JS+CSS Global IP/Port signaling data
  • 51. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT ブラウザでWebRTCを使った ページにアクセス。
  • 52. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT HTML+JS+CSS HTTP Request HTTP Response
  • 53. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Signaling Serverとして利用する WebSocket Serverに接続。
  • 54. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT signaling WebSocket open
  • 55. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT signaling 対向のブラウザもWebRTCを使った ページにアクセス。
  • 56. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT HTML+JS+CSS HTTP Request HTTP Response signaling
  • 57. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT signaling 対向のブラウザも WebSocket Serverに接続。
  • 58. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT signaling signaling WebSocket open
  • 59. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT signaling signaling 以後、WebSocketは接続を継続する。 この先、説明の都合上、接続中でもグレーアウトします。 データが流れる時だけ色がつきます。
  • 60. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT 接続を開始する側でoffer開始。 SDPを生成・自身に登録後送信。 対向のブラウザは受け取ったSDPを登録。
  • 61. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Offer SDP createOffer SDPは以下の 情報を含む。 メディアタイプ コーデック 帯域幅 etc.
  • 62. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Offer SDP setLocalDescription
  • 63. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT WebSocket send Offer SDP signaling
  • 64. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Offer SDP signaling
  • 65. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT setRemoteDescription Offer SDP
  • 66. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT 受け取ったofferに対してanswer開始。 SDPを生成・自身に登録後送信。 offerした側でも対向のSDPを登録。
  • 67. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT createAnswer SDPは以下の 情報を含む。 メディアタイプ コーデック 帯域幅 etc. Answer SDP
  • 68. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT setLocalDescription Answer SDP
  • 69. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT WebSocket send Answer SDP signaling
  • 70. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Answer SDP signaling
  • 71. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT setRemoteDescription Answer SDP
  • 72. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT P2Pの為に相手にIPを伝える必要がある。 その為にSTUNを使い自身のIPを調べる。 ブラウザが裏で処理してくれる。
  • 73. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT ここの Global IP/Port を知りたい
  • 74. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Global IP/Port Global IP/Port
  • 75. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Global IP/Port Global IP/Port
  • 76. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT ここの Global IP/Port を知りたい
  • 77. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Global IP/Port Global IP/Port
  • 78. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT Global IP/Port Global IP/Port
  • 79. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT 自身のIP/Portは把握。 自身に接続できそうな経路の候補を 相手側に伝える必要がある。
  • 80. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT ICE candidate ブラウザは これまでの 情報を元に 裏で経路候補 (candidate)を 生成。
  • 81. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT ICE candidate icecandidate Event イベントに 登録した ハンドラ経由で 経路候補を取得。
  • 82. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT WebSocket send ICE candidatesignaling
  • 83. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT ICE candidate signaling
  • 84. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT addIceCandidate ICE candidate
  • 85. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT ブラウザは STUNからの 情報を元に 裏で経路候補 (candidate)を 生成。 ICE candidate
  • 86. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT icecandidate Event ICE candidate イベントに 登録した ハンドラ経由で 経路候補を取得。
  • 87. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT WebSocket send ICE candidate signaling
  • 88. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT ICE candidate signaling
  • 89. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT addIceCandidate ICE candidate
  • 90. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT P2Pで通信する為の情報が (やっと)整った。 通信開始。
  • 91. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT data
  • 92. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT それでもP2Pが無理だった場合、 TURNサーバが中継を行なう。 TURN経由でもデータの暗号化は解かれずセキュア。
  • 93. Web Server WebSocket Server ICE Server (STUN + TURN) Browser Browser NAT NAT data data
  • 96. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection ; window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription ; window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate ; var ws = null; var peer = null; function initialize() { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); peer = new RTCPeerConnection({ iceServers: [ {url: 'stun:stun.l.google.com:19302'}, {url: 'stun:23.21.150.121'} ] }); navigator.getUserMedia( {audio: true, video: true}, function(stream) { var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); peer.addStream(stream); }, function(error) { console.error(error); } ); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); peer.addEventListener('icecandidate', function(evt) { if (!evt.candidate) {return;} var candidate = evt.candidate; ws.send(JSON.stringify({candidate: candidate})); }); peer.addEventListener('addstream', function(evt) { var video = document.getElementById('remote'); video.src = URL.createObjectURL(evt.stream); video.play(); }); var offerbtn = document.getElementById('offer_button'); offerbtn.addEventListener('click', offer); } function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="scripts/script.js"></script> <title>WebRTC Sample</title> </head> <body> <input type="button" value="offer" id="offer_button" /> <video id="local" autoplay="autoplay"></video> <video id="remote" autoplay="autoplay"></video> </body> </html>
  • 97. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection ; window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription ; window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate ; var ws = null; var peer = null; function initialize() { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); peer = new RTCPeerConnection({ iceServers: [ {url: 'stun:stun.l.google.com:19302'}, {url: 'stun:23.21.150.121'} ] }); navigator.getUserMedia( {audio: true, video: true}, function(stream) { var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); peer.addStream(stream); }, function(error) { console.error(error); } ); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); peer.addEventListener('icecandidate', function(evt) { if (!evt.candidate) {return;} var candidate = evt.candidate; ws.send(JSON.stringify({candidate: candidate})); }); peer.addEventListener('addstream', function(evt) { var video = document.getElementById('remote'); video.src = URL.createObjectURL(evt.stream); video.play(); }); var offerbtn = document.getElementById('offer_button'); offerbtn.addEventListener('click', offer); } function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="scripts/script.js"></script> <title>WebRTC Sample</title> </head> <body> <input type="button" value="offer" id="offer_button" /> <video id="local" autoplay="autoplay"></video> <video id="remote" autoplay="autoplay"></video> </body> </html> HTML ベンダープレフィックスの 処理 WebSocketと PeerConnectionの 初期化 ユーザメディアの取得 WebSocketの イベント登録 PeerConnectionの イベント登録 変数宣言 ボタンのイベント登録 offer処理 answer処理 onload
  • 98. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="scripts/script.js"></script> <title>WebRTC Sample</title> </head> <body> <input type="button" value="offer" id="offer_button" /> <video id="local" autoplay="autoplay"></video> <video id="remote" autoplay="autoplay"></video> </body> </html>
  • 99. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="scripts/script.js"></script> <title>WebRTC Sample</title> </head> <body> <input type="button" value="offer" id="offer_button" /> <video id="local" autoplay="autoplay"></video> <video id="remote" autoplay="autoplay"></video> </body> </html> Scriptの読み込みと offer開始用のボタンと ビデオ要素の表示。
  • 100. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; ベンダープレフィックス
  • 101. window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection ; window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription ; window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate ; ベンダープレフィックス
  • 102. var ws = null; var peer = null; function initialize() { // other slides } window.addEventListener('load', initialize); WebSocketオブジェクトと RTCPeerConnectionオブジェクトの 変数宣言と、初期化関数の定義。
  • 103. var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); peer = new RTCPeerConnection({ iceServers: [ {url: 'stun:stun.l.google.com:19302'}, {url: 'stun:23.21.150.121'} ] }); シグナリングに使う WebSocketの接続開始。 initialize内
  • 104. var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); peer = new RTCPeerConnection({ iceServers: [ {url: 'stun:stun.l.google.com:19302'}, {url: 'stun:23.21.150.121'} ] }); RTCPeerConnection初期化。 STUNサーバ/TURNサーバを 引数に指定(複数可)。 initialize内
  • 105. navigator.getUserMedia( {audio: true, video: true}, function(stream) { var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); peer.addStream(stream); }, function(error) { console.error(error); } ); ユーザメディアを取得し表示。 メディアストリームを RTCPeerConnectionに登録。 initialize内
  • 106. navigator.getUserMedia( {audio: true, video: true}, function(stream) { var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); peer.addStream(stream); }, function(error) { console.error(error); } ); ユーザメディアの取得・表示。 メディアストリームを RTCPeerConnectionに登録。 initialize内
  • 107. ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); WebSocketに イベントハンドラを指定。 initialize内
  • 108. ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); WSのメッセージ受信時の処理を指定。 (SDP用) initialize内
  • 109. ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); SDPが含まれているか判定。 RTCSessionDescriptionを生成。 initialize内
  • 110. ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); RTCSessionDescriptionを PeerConnectionに登録。 合わせてcallbackを指定。 initialize内
  • 111. ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); 受け取ったSDPがofferだった場合、 自動的にanswer処理を実行。 answerの内容は後述。 initialize内
  • 112. ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); initialize内 WSのメッセージ受信時の処理を指定。 (経路情報用)
  • 113. ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); initialize内 経路情報が含まれているか判定。 RTCIceCandidateを生成。
  • 114. ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); initialize内 RTCIceCandidateを PeerConnectionに登録。
  • 115. peer.addEventListener('icecandidate', function(evt) { if (!evt.candidate) {return;} var candidate = evt.candidate; ws.send(JSON.stringify({candidate: candidate})); }); peer.addEventListener('addstream', function(evt) { var video = document.getElementById('remote'); video.src = URL.createObjectURL(evt.stream); video.play(); }); initialize内 RTCPeerConnectionに イベントハンドラを指定。
  • 116. peer.addEventListener('icecandidate', function(evt) { if (!evt.candidate) {return;} var candidate = evt.candidate; ws.send(JSON.stringify({candidate: candidate})); }); peer.addEventListener('addstream', function(evt) { var video = document.getElementById('remote'); video.src = URL.createObjectURL(evt.stream); video.play(); }); RTCPeerConnectionの 経路候補取得時の処理を指定。 initialize内
  • 117. peer.addEventListener('icecandidate', function(evt) { if (!evt.candidate) {return;} var candidate = evt.candidate; ws.send(JSON.stringify({candidate: candidate})); }); peer.addEventListener('addstream', function(evt) { var video = document.getElementById('remote'); video.src = URL.createObjectURL(evt.stream); video.play(); }); 経路候補が含まれているか判定。 経路候補を対向に送信。 initialize内
  • 118. peer.addEventListener('icecandidate', function(evt) { if (!evt.candidate) {return;} var candidate = evt.candidate; ws.send(JSON.stringify({candidate: candidate})); }); peer.addEventListener('addstream', function(evt) { var video = document.getElementById('remote'); video.src = URL.createObjectURL(evt.stream); video.play(); }); RTCPeerConnectionの Stream(対向)取得時の処理を指定。 initialize内
  • 119. peer.addEventListener('icecandidate', function(evt) { if (!evt.candidate) {return;} var candidate = evt.candidate; ws.send(JSON.stringify({candidate: candidate})); }); peer.addEventListener('addstream', function(evt) { var video = document.getElementById('remote'); video.src = URL.createObjectURL(evt.stream); video.play(); }); 対向のMediaStreamを表示。 initialize内
  • 120. var offerbtn = document.getElementById('offer_button'); offerbtn.addEventListener('click', offer); offer開始用のボタンのクリック時に offer処理を実行。 offerの内容は後述。 initialize内
  • 121. function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } offer処理。
  • 122. function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } offer処理宣言。
  • 123. function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } offer生成。
  • 124. function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } offer生成成功時の動作を指定。
  • 125. function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } 取得したofferを PeerConnectionに登録。 合わせてcallbackを指定。
  • 126. function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } offer登録後、 offerを対向に送信。
  • 127. function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } offer取得失敗時の処理を指定。
  • 128. function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } offer取得失敗時の処理。 ここではエラーログを出力しているだけ。
  • 129. function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } answer処理。
  • 130. function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } answer処理宣言。
  • 131. function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } answer生成。
  • 132. function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } answer生成成功時の動作を指定。
  • 133. function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } 取得したanswerを PeerConnectionに登録。 合わせてcallbackを指定。
  • 134. function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } answer登録後、 answerを対向に送信。
  • 135. function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } answer取得失敗時の処理を指定。
  • 136. function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } answer取得失敗時の処理。 ここではエラーログを出力しているだけ。
  • 137. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection ; window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription ; window.RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate ; var ws = null; var peer = null; function initialize() { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); peer = new RTCPeerConnection({ iceServers: [ {url: 'stun:stun.l.google.com:19302'}, {url: 'stun:23.21.150.121'} ] }); navigator.getUserMedia( {audio: true, video: true}, function(stream) { var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); peer.addStream(stream); }, function(error) { console.error(error); } ); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.sdp) {return;} var sdp = data.sdp; var description = new RTCSessionDescription(sdp); peer.setRemoteDescription(description, function() { if (description.type === 'offer') { answer(); } }); }); ws.addEventListener('message', function(evt) { var data = JSON.parse(evt.data); if (!data.candidate) {return;} var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); }); peer.addEventListener('icecandidate', function(evt) { if (!evt.candidate) {return;} var candidate = evt.candidate; ws.send(JSON.stringify({candidate: candidate})); }); peer.addEventListener('addstream', function(evt) { var video = document.getElementById('remote'); video.src = URL.createObjectURL(evt.stream); video.play(); }); var offerbtn = document.getElementById('offer_button'); offerbtn.addEventListener('click', offer); } function offer() { peer.createOffer( function(offer) { peer.setLocalDescription(offer, function() { ws.send(JSON.stringify({sdp: offer})); }); }, function(error) { console.error(error); } ); } function answer() { peer.createAnswer( function(answer) { peer.setLocalDescription(answer, function() { ws.send(JSON.stringify({sdp: answer})); }); }, function(error) { console.error(error); } ); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="scripts/script.js"></script> <title>WebRTC Sample</title> </head> <body> <input type="button" value="offer" id="offer_button" /> <video id="local" autoplay="autoplay"></video> <video id="remote" autoplay="autoplay"></video> </body> </html>
  • 138. Library / PaaS APIのWrapperや、 Signaling Server機能を含むもの、 room機能の有無など様々。 ICE Serverを提供しているものも。 クライアント側の実装に 集中したい場合は、導入の価値あり。 サーバ側を細かく制御したい場合は 使わないという選択肢も。
  • 143. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var PEERJS_API_KEY = '[PEERJS_API_KEY]'; var ws = null; var peer = null; var selfid = null; var localStream = null; function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } function initialize() { initializePeer(function() { initializeMedia(function() { initializeWebSocket(function() { ws.send(selfid); }); }); }); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="http://cdn.peerjs.com/0.3/peer.js"></script> <script src="scripts/script.js"></script> <title>PeerJS Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html>
  • 144. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var PEERJS_API_KEY = '[PEERJS_API_KEY]'; var ws = null; var peer = null; var selfid = null; var localStream = null; function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } function initialize() { initializePeer(function() { initializeMedia(function() { initializeWebSocket(function() { ws.send(selfid); }); }); }); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="http://cdn.peerjs.com/0.3/peer.js"></script> <script src="scripts/script.js"></script> <title>PeerJS Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html> HTML ベンダープレフィックスの 処理 API Keyと変数宣言 PeerJSの 初期化とイベント登録 ユーザメディアの取得 WebSocketの 初期化とイベント登録 MediaConnectionの 設定 (主にイベント登録) onloadと 初期化処理の呼び出しと シグナリング開始
  • 145. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="http://cdn.peerjs.com/0.3/peer.js"></script> <script src="scripts/script.js"></script> <title>PeerJS Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html>
  • 146. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="http://cdn.peerjs.com/0.3/peer.js"></script> <script src="scripts/script.js"></script> <title>PeerJS Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html> LibraryとScriptの読み込みと ビデオ要素の表示と 対向の表示領域の準備。
  • 147. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var PEERJS_API_KEY = '[PEERJS_API_KEY]'; var ws = null; var peer = null; var selfid = null; var localStream = null; ベンダープレフィックスの処理。
  • 148. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var PEERJS_API_KEY = '[PEERJS_API_KEY]'; var ws = null; var peer = null; var selfid = null; var localStream = null; API Key、WebSocketとPeerJS、 自分のidとStream用の変数宣言。 PeerServer Cloud service の API Key 取得は以下のURLから。 http://peerjs.com/peerserver
  • 149. function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); }PeerJSの各種設定。
  • 150. function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } PeerJSの初期化。 要API Key。
  • 151. function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } PeerServerと接続時に 自身のPeerIDを取得する。 ローカル変数にPeerIDを保存。
  • 152. function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } 初期化処理のためのcallback。 callbackの詳細は後述。
  • 153. function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } 相手から通話依頼に、自動応答させる。 MediaConnectionの設定。 MediaConnectionの設定の詳細は後述。
  • 154. function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } PeerServerとの通信が切断した時、 Peerオブジェクトを破棄。
  • 155. function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } エラー処理。 ここではエラーログを出力しているだけ。
  • 156. function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } UserMediaの各種設定。 同じ説明の繰り返しになるので、ここは簡単に解説。
  • 157. function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } ユーザメディアを取得し表示。
  • 158. function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } 初期化処理のためのcallback。 callbackの詳細は後述。
  • 159. function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } WebSocketの各種設定。
  • 160. function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } PeerIDを自動で交換して接続するための WebSocketの接続開始。
  • 161. function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } WebSocket接続時の処理を指定。
  • 162. function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } 初期化処理のためのcallback。 callbackの詳細は後述。
  • 163. function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } WSのメッセージ受信時の処理を指定。 (PeerID用)
  • 164. function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } 受け取ったPeerIDに、自動発信させる。 MediaConnectionの設定。 MediaConnectionの設定の詳細は後述。
  • 165. function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } 発信/応答時に得た MediaConnectionに設定。 (主にイベント登録。)
  • 166. function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } 発信/応答先のPeerIDを取得。 StreamとVideo要素の保存領域。 closure使用。
  • 167. function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } Stream(対向)取得時の処理を指定。
  • 168. function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } 対向のMediaStreamを表示。
  • 169. function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } MediaConnectionが切断した時、 URLオブジェクトの削除と Video要素を削除。
  • 170. function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } エラー処理。 ここではエラーログを出力しているだけ。
  • 171. function initialize() { initializePeer(function() { initializeMedia(function() { initializeWebSocket(function() { ws.send(selfid); }); }); }); } window.addEventListener('load', initialize); 初期化処理。 callback地獄。
  • 172. function initialize() { initializePeer(function() { initializeMedia(function() { initializeWebSocket(function() { ws.send(selfid); }); }); }); } window.addEventListener('load', initialize); Peerとメディアの初期化が終わらないと PeerIDの送信が行なえないため。 DefferedやPromiseを使えばもっと綺麗に書ける。
  • 173. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var PEERJS_API_KEY = '[PEERJS_API_KEY]'; var ws = null; var peer = null; var selfid = null; var localStream = null; function initializePeer(callback) { peer = new Peer({key: PEERJS_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } function initialize() { initializePeer(function() { initializeMedia(function() { initializeWebSocket(function() { ws.send(selfid); }); }); }); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="http://cdn.peerjs.com/0.3/peer.js"></script> <script src="scripts/script.js"></script> <title>PeerJS Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html>
  • 177. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var SKYWAY_API_KEY = '[SKYWAY_API_KEY]'; var REST_API_LIST = 'https://skyway.io/v2/active/list/'; var peer = null; var selfid = null; var localStream = null; function initializePeer(callback) { peer = new Peer({key: SKYWAY_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); } function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } function initialize() { initializePeer(function() { initializeMedia(function() { callRemoteAll(); }); }); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="https://skyway.io/dist/v2/0.3/peer.js"></ script> <script src="scripts/script.js"></script> <title>SkyWay Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html>
  • 178. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var SKYWAY_API_KEY = '[SKYWAY_API_KEY]'; var REST_API_LIST = 'https://skyway.io/v2/active/list/'; var peer = null; var selfid = null; var localStream = null; function initializePeer(callback) { peer = new Peer({key: SKYWAY_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); } function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } function initialize() { initializePeer(function() { initializeMedia(function() { callRemoteAll(); }); }); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="https://skyway.io/dist/v2/0.3/peer.js"></ script> <script src="scripts/script.js"></script> <title>SkyWay Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html> HTML PeerJSの 初期化とイベント登録 ユーザメディアの取得 MediaConnectionの 設定 (主にイベント登録) onloadと 初期化処理の呼び出しと 発信処理開始 接続先の一覧所得と 発信処理 ベンダープレフィックスの 処理 API Keyと変数宣言
  • 179. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var SKYWAY_API_KEY = '[SKYWAY_API_KEY]'; var REST_API_LIST = 'https://skyway.io/v2/active/list/'; var peer = null; var selfid = null; var localStream = null; function initializePeer(callback) { peer = new Peer({key: SKYWAY_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); } function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } function initialize() { initializePeer(function() { initializeMedia(function() { callRemoteAll(); }); }); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="https://skyway.io/dist/v2/0.3/peer.js"></ script> <script src="scripts/script.js"></script> <title>SkyWay Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html> 主に PeerJS版との 差分のみを解説
  • 180. ... <link rel="stylesheet" href="styles/style.css" /> <script src="http://cdn.peerjs.com/0.3/peer.js"></script> <script src="scripts/script.js"></script> ... ... <link rel="stylesheet" href="styles/style.css" /> <script src="https://skyway.io/dist/v2/0.3/peer.js"></script> <script src="scripts/script.js"></script> ... 読み込むLibraryの変更。
  • 181. var PEERJS_API_KEY = '[PEERJS_API_KEY]'; var SKYWAY_API_KEY = '[SKYWAY_API_KEY]'; var REST_API_LIST = 'https://skyway.io/v2/active/list/'; API KeyをSkyWay用に変更。 接続しているPeerIDの 一覧を取得するRestAPIのURL。 SkyWay の API Key 取得は以下のURLから。 http://nttcom.github.io/skyway/registration.html
  • 182. ... var ws = null; ... function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } ...
  • 183. ... var ws = null; ... function initializeWebSocket(callback) { var secure = location.protocol === 'https:'; var protocol = secure ? 'wss' : 'ws'; var url = protocol + '://' + location.host + '/'; ws = new WebSocket(url); ws.addEventListener('open', function() { callback(); }); ws.addEventListener('message', function(evt) { var remoteid = evt.data; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); }); } ... WebSocketの処理は削除。
  • 184. function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); } RestAPIからPeerIDの一覧を取得し、 全てに発信。
  • 185. function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); }RestAPIのURL + API Key。
  • 186. function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); } Ajax(XHR)お決まりの記述。
  • 187. function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); }接続しているPeerIDの一覧を取得。
  • 188. function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); } RestAPIからPeerIDの一覧を取得し、 全てに自動発信。
  • 189. function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); } MediaConnectionの設定。
  • 190. function initialize() { initializePeer(function() { initializeMedia(function() { initializeWebSocket(function() { ws.send(selfid); }); }); }); } function initialize() { initializePeer(function() { initializeMedia(function() { callRemoteAll(); }); }); }
  • 191. function initialize() { initializePeer(function() { initializeMedia(function() { initializeWebSocket(function() { ws.send(selfid); }); }); }); } function initialize() { initializePeer(function() { initializeMedia(function() { callRemoteAll(); }); }); } WebSocketの初期化削除。 シグナリング開始処理削除。 発信処理追加。
  • 192. navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ; window.URL = window.URL || window.webkitURL ; var SKYWAY_API_KEY = '[SKYWAY_API_KEY]'; var REST_API_LIST = 'https://skyway.io/v2/active/list/'; var peer = null; var selfid = null; var localStream = null; function initializePeer(callback) { peer = new Peer({key: SKYWAY_API_KEY}); peer.on('open', function(id) { selfid = id; callback(); }); peer.on('call', function(mediaConnection) { mediaConnection.answer(localStream); settingMediaConnection(mediaConnection); }); peer.on('close', function() { peer.destroy(); }); peer.on('error', function(err) { console.error(err); }); } function initializeMedia(callback) { navigator.getUserMedia( {audio: true, video: true}, function(stream) { localStream = stream; var video = document.getElementById('local'); video.src = URL.createObjectURL(stream); video.play(); callback(); }, function(error) { console.error(error); } ); } function callRemoteAll() { var url = REST_API_LIST + SKYWAY_API_KEY; var xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', function() { if (xhr.readyState != 4) {return;} if (xhr.status != 200) {return;} var remoteids = JSON.parse(xhr.responseText); for (var i = 0; i < remoteids.length; i++) { var remoteid = remoteids[i]; var mediaConnection = peer.call(remoteid, localStream); settingMediaConnection(mediaConnection); } }); xhr.open('GET', url); xhr.send(); } function settingMediaConnection(mediaConnection) { var remoteid = mediaConnection.peer; var remoteStream = null; var video = null; mediaConnection.on('stream', function(stream) { video = document.createElement('video'); video.src = URL.createObjectURL(stream); video.play(); var parent = document.getElementById('remotes'); parent.appendChild(video); }); mediaConnection.on('close', function() { URL.revokeObjectURL(video.src); video.parentNode.removeChild(video); }); mediaConnection.on('error', function() { console.error(err); }); } function initialize() { initializePeer(function() { initializeMedia(function() { callRemoteAll(); }); }); } window.addEventListener('load', initialize); <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="styles/style.css" /> <script src="https://skyway.io/dist/v2/0.3/peer.js"></ script> <script src="scripts/script.js"></script> <title>SkyWay Sample</title> </head> <body> <video id="local"></video> <div id="remotes"></div> </body> </html>
  • 194. Canvas Web Audio API Media Stream Media Stream 音声処理 画像処理 現在の実装状況 MediaStream Processing APIの中にCanvas Recordingという Canvas要素からstreamを取得する仕様案があったが進展なし?
  • 197. SAMPLE 簡易 ボイスチェンジャー MediaStream <=> Web Audio API 当ボイスチェンジャーで変換した音声は、 音声解析によりほぼ元の音声を復元可能であり、 匿名性を担保するものではありません。
  • 198. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); }
  • 199. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } ユーザメディアを Web Audio APIで編集し、 RTCPeerConnectionに渡す。
  • 200. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } getUserMediaで音声を取得。
  • 201. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } Web Audio APIの AudioContextをインスタンス化。 ベンダープレフィックス(例:webkitAudioContext)
  • 202. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } MediaStreamをWeb Audio APIの Source(入力側)にする。 WebRTCの世界からWeb Audio APIの世界へ。
  • 203. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } 音声処理をJavaScriptで行なうための ScriptProcessorをインスタンス化。 旧名:JavaScriptNode。古い資料を見るときは注意。
  • 204. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } Web Audio APIの Destination(出力側)をインスタンス化。
  • 205. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } 音声処理に使うfunction(後述)を ScriptProcessorのイベントに登録。
  • 206. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } source(入力側) scriptProcessor destination(出力側)の順に接続。
  • 207. navigator.getUserMedia({audio: true}, function(inputStream) { var audioContext = new AudioContext(); // 要ベンダープレフィックス var mediastreamsource = audioContext.createMediaStreamSource(inputStream); var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1); var mediastreamdestination = audioContext.createMediaStreamDestination(); scriptProcessor.addEventListener('audioprocess', onAudioProcess); mediastreamsource.connect(scriptProcessor); scriptProcessor.connect(mediastreamdestination); var outputStream = mediastreamdestination.stream; peer.addStream(outputStream); }, function(error) {} ); function onAudioProcess(evt) { var input = evt.inputBuffer.getChannelData(0); var output = evt.outputBuffer.getChannelData(0); var bufferData = new Float32Array(bufferSize); for (var i = 0; i < bufferSize; i++) { bufferData[i] = ( (input[(i * 2) % bufferSize] + input[(i * 2 + 1) % bufferSize]) / 2 + input[Math.floor(i / 2) % bufferSize] ) / 2; } output.set(bufferData); } Web Audio APIのSource(入力側)を MediaStreamにする。 Web Audio APIの世界からWebRTCの世界へ。