WebRTCをさわってみる&手動シグナリングしてみる

0 件のコメント
今回は、WebRTCについて書きたいと思います。

WebRTCって何ぞ?

WebRTCって何ぞ?
ってとこから書きたいなと思ったのですが、他の方の記事見てるとよく書けてるな。。
改めて書く必要ないんじゃないかな。。
図ないと分かりづらいよな。。面倒だなこれ。。

って感じになりました。

ということで、WebRTCって何ぞ?とかは下記の記事を参考にしてください!

・WebRTCを仕組みから実装までやってみる
http://blog.wnotes.net/blog/article/webrtc-beginning

・WebRTCで変わるWebの未来
http://www.qcontokyo.com/data_2013/ToruYoshikawa_QConTokyo2013.pdf

んで、今回書くのは
P2Pで通信を開始するまでのシーケンスを1手順ずつ、自分でわかるように、自分のために書きます。

今回やろうとすること

端末A・端末BをPeer to Peerし、
Macに内蔵しているカメラで撮影している内容をリアルタイムに共有するところまでやる。

必要っぽいもの

シグナリングサーバー
ICEサーバー(STUN / TURN)

と思ったけど、シグナリングは手動でできそうだし、ICEサーバーだけでいいんじゃないか感。

ということで、今回は↓でやります。
・シグナリングは手動
・Googleが提供しているSTUNサーバー

接続手順

事前準備(共通)
No端末A端末B
1video要素を作っておくvideo要素を作っておく
2Peerを生成Peerを生成
3メディアに接続(カメラの起動を許可すること)
・Peerにストリームを接続し、自身の端末の状態をvideo要素で閲覧できる状態になる
メディアに接続(カメラの起動を許可すること)
・Peerにストリームを接続し、自身の端末の状態をvideo要素で閲覧できる状態になる

この作業は接続する端末共通です。
ここまで実施することで、ブラウザで自身の姿が見れるようになるはずです。

接続対象へSDPを送受信してセッションを確立
Simple Traversal of UDP through NATs (STUN): NAT越えの方法としてRFC3489で定められた標準的な仕組み。
外部のSTUNサーバに対してクライアントが一度接続し、グローバルIPとマッピングされたポート番号を記憶しておくことで、そのデータを使ってPeerは相手のマシンを特定することができる。
No端末A端末B
4端末Bへ送信するOfferを生成
5端末Aで生成したOfferを受信
6端末Bへ送信するAnswerを生成
7端末Bで生成したAnswerを受信

リモート側のストリームを共有する
接続経由を共有することで、端末Aと端末BをPeer to Peer接続する。
No端末A端末B
8端末Bへ送信する経路情報を出力
9端末Aで生成した経路情報を受信
10端末Aへ送信する経路情報を出力
11端末Bで生成した経路情報を受信

ここまでくると、接続した端末のカメラの動画が表示されるようになっているはずです。

まとめて書くと、こんな手順です。
No端末A端末B
1video要素を作っておくvideo要素を作っておく
2Peerを生成Peerを生成
3メディアに接続(カメラの起動を許可すること)
・Peerにストリームを接続し、自身の端末の状態をvideo要素で閲覧できる状態になる
メディアに接続(カメラの起動を許可すること)
・Peerにストリームを接続し、自身の端末の状態をvideo要素で閲覧できる状態になる
4端末Bへ送信するOfferを生成
5端末Aで生成したOfferを受信
6端末Aへ送信するAnswerを生成
7端末Bで生成したAnswerを受信
8端末Bへ送信する経路情報を出力
9端末Aで生成した経路情報を受信
10端末Aへ送信する経路情報を出力
11端末Bで生成した経路情報を受信

手動でやると面倒ですね。これ。

接続してみよう!

コードはこんな感じ(1〜11の上記手順通り番号をつけています。
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<style type="text/css">
video {
width: 480px;
height: 360px;
margin: 5px;
padding: 0;
border: 10px solid #000;
}
textarea {
width: 480px;
height: 200px;
margin: 5px;
padding: 0;
border: 10px solid #0f0;
}
</style>
<script type="text/javascript">
var webrtc = {};
(function(webrtc) {
// 1. video要素が必要
var body = document.getElementsByTagName('body')[0];
var localVideo = document.createElement('video');
localVideo.autoplay = true;
var remoteVideo = document.createElement('video');
remoteVideo.autoplay = true;
var receiveSdpInput = document.createElement('textarea');
receiveSdpInput.value = "ここにはSDPを貼り付けてね";
var candidatesInput = document.createElement('textarea');
candidatesInput.value = "ここにはCandidateを貼り付けてね";
body.appendChild(localVideo);
body.appendChild(remoteVideo);
body.appendChild(receiveSdpInput);
body.appendChild(candidatesInput);
// 2. Peerを生成
// Googleが提供しているSTUNサーバを使う
// Simple Traversal of UDP through NATs (STUN): NAT越えの方法としてRFC3489で定められた標準的な仕組み
// 外部のSTUNサーバに対してクライアントが一度接続し、グローバルIPとマッピングされたポート番号を記憶しておくことで、そのデータを使ってPeerは相手のマシンを特定することができる
var peer = new webkitRTCPeerConnection({
"iceServers": [{"url": "stun:stun.l.google.com:19302"}]
});
// 3. メディアに接続(カメラ
// メディアに接続しvideo要素に内容を表示する
navigator.webkitGetUserMedia({video: true, audio: false},
function(stream) {
// blob URLを指定するとカメラの画像video要素で閲覧できる
// 自分の顔を見たくなかったらコメントアウトしてね
localVideo.src = window.URL.createObjectURL(stream);
// Peerにストリームを接続
peer.addStream(stream);
},
function(err) {
console.log(err);
}
);
// --- 接続対象へSDPを送受信し、セッションを確立させる
// Session Description Protocol (SDP): 各ブラウザの情報(セッションが含むメディアの種類、IPアドレス、ポート番号などなど)を示し、文字列で表現される
// 本来ならばシグナリングサーバーを用意して接続対象を選びSDPを送信するという手順になるが、用意が面倒なので手動でやる
// 4. Offer生成: 端末A
// 接続対象へSDPを送信する(今回はconsole.logに出力するだけ
var createOffer = function() {
peer.createOffer(function(sdp) {
peer.setLocalDescription(sdp,
function() {
// 本来ならばこのタイミングでシグナリングサーバーへ自分のSDPを送信する
console.log(sdp);
},
function(err) {
console.log(err);
}
);
// コピーしやすいように文字列化
console.log(JSON.stringify(sdp));
});
};
webrtc.createOffer = createOffer;
// 5. Offerを受信: 端末B
// SDPを受信する(手順4. で生成したSDPをコピーしテキストエリアに入力する. その後、Developer Toolsのコンソールから下記関数を実行する
var receiveSdp = function() {
var sdp = receiveSdpInput.value;// コンソールから値を渡すようにしたかったが、自動文字変換が走ってうまくいかないので左側のテキストエリアに記載して値を取る
sdp = JSON.parse(sdp);
var remoteSdp = new RTCSessionDescription(sdp);
peer.setRemoteDescription(remoteSdp,
function() {
if (remoteSdp.type === "offer") {
console.log("receive offer");
}
if (remoteSdp.type === "answer") {
console.log("receive answer");
}
},
function(err) {
console.log(err);
}
);
};
webrtc.receiveSdp = receiveSdp;
// 6. Answer生成: 端末B
// 接続対象へSDPを送信する(今回はconsole.logに出力するだけ
var createAnswer = function() {
peer.createAnswer(function(sdp) {
peer.setLocalDescription(sdp,
function() {
console.log(sdp);
},
function(err) {
console.log(err);
}
);
// コピーしやすいように文字列化
console.log(JSON.stringify(sdp));
});
};
webrtc.createAnswer = createAnswer;
// 7. Answerを受信: 端末A
// SDPを受信する(手順6. で生成したSDPをコピーしテキストエリアに入力する. その後、Developer Toolsのコンソールから5で定義した下記関数を実行する
// webrtc.receiveSdp()
// --- ストリームの共有
peer.onaddstream = function(event) {
remoteVideo.src = window.webkitURL.createObjectURL(event.stream);
};
// 通常は1経路ずつやりとりするが、手動では辛いので配列に入れていっぺんに共有する
var candidates = [];
peer.onicecandidate = function(event) {
candidates.push(event.candidate);
};
// 8. candidateの出力: 端末A
var displayCandidates = function() {
// コピーしやすいように文字列化
console.log(JSON.stringify(candidates));
};
webrtc.displayCandidates = displayCandidates;
// 9. candidateの受信: 端末B
var receiveCandidates = function() {
var candidates = candidatesInput.value;// コンソールから値を渡すようにしたかったが、自動文字変換が走ってうまくいかないので右側のテキストエリアに記載して値を取る
candidates = JSON.parse(candidates);
for (var i = 0, l = candidates.length; i < l; i++) {
if (candidates[i]) {
var candidate = new RTCIceCandidate(candidates[i]);
peer.addIceCandidate(candidate);
} else {
console.log("no candidate");
}
}
};
webrtc.receiveCandidates = receiveCandidates;
// 10. candidateの出力: 端末B
// 8.と同様の手順
// 11. candidateの受信: 端末A
// 9.と同様の手順
})(webrtc);
</script>
</body>
</html>
view raw index.html hosted with ❤ by GitHub
このコードをブラウザで表示すると、こうなります。
図. webrtcテストサンプル
このコードでは、1〜3までは開いた時に勝手にやってくれます。
(カメラを許可すると、左上に端末A(自分の顔)が表示されます。

では、ブラウザを2つ立ち上げ(別のPCでも可)、端末Aと端末Bとして作業してみましょう。
4〜11は下記のような作業が必要なので実行してみてください。
図. 手順説明用

No端末A端末B
4端末Bへ送信するOfferを生成

・Developer ToolsのConsoleで下記を実行し、表示される文字列をコピーする(文字列に変換して表示されてるやつ
webrtc.createOffer()
5端末Aで生成したOfferを受信

・4でコピーした文字列をブラウザに表示されてるテキストエリア左側(図の①)に貼り付け、Consoleから下記を実行する
webrtc.receiveSdp()
6端末Aへ送信するAnswerを生成

・Consoleで下記を実行し、表示される文字列をコピーする(文字列に変換して表示されてるやつ
webrtc.createAnswer()
7端末Bで生成したAnswerを受信

・6でコピーした文字列をブラウザに表示されてるテキストエリア左側(図の①)に貼り付け、Consoleから下記を実行する
webrtc.receiveSdp()
8端末Bへ送信する経路情報を出力

・Consoleで下記を実行し、表示される文字列をコピーする(文字列に変換して表示されてるやつ
webrtc.displayCandidates()
9端末Aで生成した経路情報を受信

・8でコピーした文字列をブラウザに表示されてるテキストエリア右側(図の②)に貼り付け、Consoleから下記を実行する
webrtc.receiveCandidates()
10端末Aへ送信する経路情報を出力

・Consoleで下記を実行し、表示される文字列をコピーする(文字列に変換して表示されてるやつ
webrtc.displayCandidates()
11端末Bで生成した経路情報を受信

・10でコピーした文字列をブラウザに表示されてるテキストエリア右側(図の②)に貼り付け、Consoleから下記を実行する
webrtc.receiveCandidates()

11を実行することで、右上に端末B(相手の顔)が表示されます。

おわりに

手動でやるとことで、流れがだいぶ理解できました。
今度は自動でシグナリングするようにして、DataChannelでもいじってみたいと思います。

以上!

0 件のコメント :

コメントを投稿