WebRTC入門

WebRTC入門

本エントリーは「to-R JavaScript Advent Calendar 2015」7日目のエントリー、WebRTCを利用する方法を解説します。

そもそもWebRTCって?

WebRTCとは2台以上のPCで音声や動画、データなどを共有するための仕様です。すごくざっくりと説明するとSkypeのようなアプリケーションをWebブラウザのみで作成することができます。

これまでもビデオチャットのようなシステムはありましたが、そのほとんどがメディアサーバーと呼ばれるデータ仲介用のサーバーを介して行うのが一般的でした。そのため、この手のシステムではネットワーク帯域やサーバー機器に莫大な投資が必要でありビジネスとしての活用は難しかったです。

WebRTCの最大の特徴は仲介用サーバーを必要とせずにPC同士が直接通信を行う形式(P2P)を取っている点です。そのため、低コストでビデオチャットなどの仕組みが組み込めるとして注目を浴びています。

2台のPCの接続が確立

WebRTCでは2台のPCで幾つかの情報のやりとりを行い、それらの情報が確定したタイミングで通信が発生します。

そのひとつがSDP(Session Description Protocol)と呼ばれる情報です。

AとBという2台のPCで通信を行う場合、AはBのSDPを、BはAのSDPを持って初めてAとBの通信が可能になります。

もうひとつがICE(Interactive Connectivity Establishment)と呼ばれる情報でIPアドレスやポートの情報が含まれています。

AB間で一度通信が発生してしまえば仲介サーバーは不要ですが、通信が発生するまでに必要な情報をやり取りするサーバーをシグナリングサーバーとよびWebSocketなどで作成します。

今回は、前回作成したsocket.ioの簡易チャットをベースに解説を行っていきます。

作成したサンプルはGitHubにアップしておきます。

HTMLの作成

まずは以下の様な簡単なHTMLを記述します。左の#myVideoに自身のウェブカムの情報が表示され、#buttonが押下されたタイミングで通信を行い右の#yourVideoには通信先のウェブカムの情報が表示します。

<video id="myVideo" width="200"></video>
<button id="button">接続</button>
<video id="yourVideo" width="200"></video>

MediaStreamを取得

つぎに自身のウェブカムの情報をMediaStreamとして取得しましょう。MediaStreamの取得は「getUserMedia APIを利用してウェブカムの情報をvideo要素に表示する」で解説をしております。

var localStream;
//ベンダープリフィックスの有無を吸収
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
//MediaStreamの取得
navigator.getUserMedia({
	video: true,
	audio: true
},
function(stream) {
	//MediaStreamの設定
	var myVideo = document.getElementById("myVideo")
	myVideo.src = URL.createObjectURL(stream);
	myVideo.volume = 0;
	myVideo.play();
	localStream = stream;
},
function(error) {
	console.log("error",error);
});

MediaStreamをlocalStreamに格納しておき、ハウリングが起きないようにvideoのボリュームは0に設定しています。

peerオブジェクトの作成

WebRTC通信用のpeerオブジェクトの作成します。peerオブジェクトを作成するためのRTCPeerConnectionの書式がブラウザにより違いますので吸収しておきます。

//ベンダープリフィックスの有無を吸収
var RTCPeerConnection , RTCPeerConnection || webkitRTCPeerConnection || mozRTCPeerConnection;
//PEERオブジェクの作成
var peer = new RTCPeerConnection({ "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] });

この際に引数としてiceServersと呼ばれるものを設定します。ここに指定するのはstunサーバーというもので外部のサーバーから見て自身のPCのIPやポートなどの情報を返してくれます。

上記ではgoogleのフリーのstunサーバーを指定しています。

セキュリティの設定によっては外部から自身のPCのIPやポートなどがわからない事があり、そういった環境ではWebRTCは利用できません。(ただし、turnサーバーと呼ばれる仲介サーバーを用意すれば利用できます)

ICE交換

次にsocket.ioを利用してICE情報の交換をしましょう。

peerオブジェクトはICE情報候補が取得できたらonicecandidateイベントを発火させますのでそのタイミングで通信先に対してICE情報候補をemitし、通信情報候補先からICEを取得したらRTCIceCandidateでICE情報を作成してpeerオブジェクトに対してaddIceCandidateで設定を行います。

ICE情報候補というように複数の候補がstunサーバーより送られてきて通信可能なICE情報が採用されます。

//socket.ioの開始
var socket = io();
//ICEの送信
peer.onicecandidate = function (evt) {
	if ( evt.candidate ) {
		socket.emit('sendCdi',{
			"candidate": evt.candidate
		});
	}
}
//ICEの受信
socket.on('reciveCdi',function(data){
	var ice = new RTCIceCandidate(data.candidate);
	peer.addIceCandidate(ice);
});

node.jsでは受け取った情報をそのまま返す設定を指定ます。socket.broadcast.emit()とすることで送信者以外に対して送信します。

socket.on("sendCdi",function(cdi){
	socket.broadcast.emit('reciveCdi', cdi);
});

ビデオの再生設定

通信が開始したタイミングでpeerオブジェクトのaddstreamイベントが発火しますのでそのタイミングでvideo要素にMediaStreamを設定して再生を行います。

//ビデオ情報をセット
peer.addEventListener("addstream",function(e){
	var yourVideo = document.getElementById("yourVideo");
	yourVideo.src = URL.createObjectURL(e.stream);
	yourVideo.play();
});

SDP交換

SDP交換ができれば通信が開通しますが結構面倒くさい手続きが必要です。流れ的には以下の手続きが必要です。

1.AがBにオファーを送る
2.オファーを受け取ったBはAに対して返事を送る
3.返事を受け取ったAは通信を開始する

AがBにオファーを送る

まずはクライントサイドで通信先に対してオファーを送る設定をします。#buttonがクリックされたタイミングでlocalStreamが存在したらpeerオブジェクトにlocalStreamを設定してcreateOfferでSDPを作成します。自身のLocalDescriptionにSDPを設定したのちWebSocketでSDP情報を送ります。

//SDPを作成して送信
$("#button").click(function(){
	if(localStream){
		peer.addStream(localStream);
		peer.createOffer(function(sdp) {
			peer.setLocalDescription(sdp, function() {
				socket.emit('sendOffer',{
					"sdp":sdp
				});
			});
		});
	}
});

node.jsでは受け取ったSDPをそのまま送信する設定のみしておきます。

socket.on("sendOffer",function(sdf){
	console.log("offer");
	socket.broadcast.emit('reciveOffer', sdf);
});

オファーを受け取ったBはAに対して返事を送る

SDPを受けっとったらBのSDPを作成してAに返事を送信します。

//SDPを受け取ったらSDPを作成して返信
socket.on('reciveOffer', function(info) {
	if(localStream){
		peer.addStream(localStream);
		var sdp = new RTCSessionDescription(info.sdp);
		peer.setRemoteDescription(sdp, function() {
			peer.createAnswer(function(sdp) {
				peer.setLocalDescription(sdp, function() {
					socket.emit('sendAnswer',{
						"sdp": sdp
					});
				});
			});
		});
	}
});

node.jsでは受け取ったSDPをそのまま送信する設定のみしておきます。

socket.on("sendAnswer",function(sdf){
	console.log("answer");
	socket.broadcast.emit('reciveAnswer', sdf);
});

返事を受け取ったAは通信を開始する

SDPを受け取ったAはsetRemoteDescriptionでBのSDPを設定します。

//返信されたSDPをセット
socket.on('reciveAnswer', function(info) {
	console.log('reciveAnswer');
	//アンサーをセット
	var sdp = new RTCSessionDescription(info.sdp);
	peer.setRemoteDescription(sdp);
});

これで通信が発生し、addstreamイベントが発火します。

注意点

一見素晴らしいWebRTCですが以下の様な課題もあります。

・対応ブラウザがChrome/OperaとFirefoxのみ (Safari,IE,Edgeでは利用できない)
・ルーターやFirewallで通信できないことも
・CPU使用率が半端ない
・ドライバレベルの不具合を引き起こすことも
・不安定、とにかく不安定

安定したアプリケーションが作成できるのはまだ先かもしれませんが面白い技術ですので試してみてください。

関連エントリー

node.jsのExpressでローカルサーバーを構築
5分でわかるWebRTCの仕組み - html5minutes vol.01

スポンサードリンク

«node.jsとsocket.ioで簡易チャットを作成する | メイン | jQueryの$(function(){...});と$(window).on("load",function(){...})の違い»