WebRTCについて学ぶ
手っ取り早くWebRTCを使ってみたいならPeerJSやSimpleWebRTCを使えばいいと思うが、今回は勉強ということで、ライブラリを使わずにライブチャットを作ってみた。
参考サイト
WebSocket関係
「Node.jsとWebSocket.IOでチャットアプリを作る」
http://mawatari.jp/archives/make-a-chat-application-in-node-js-and-websocket-io
webRTC関係
「WebRTCを仕組みから実装までやってみる」
http://blog.wnotes.net/blog/article/webrtc-beginning
「WebRTCことはじめ」
http://www.gcgate.jp/engineerblog/2014/01/07/194/
ソースコード
サーバー側
「WebRTCを仕組みから実装までやってみる」
http://blog.wnotes.net/blog/article/webrtc-beginning
のindex.jsをそのまま使った。受信したデータを全てのクライアントに送信する処理を行う。
クライアント側
クライアント側のコードは以下。
<!doctype html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>ライブチャット</title> <script src="http://code.jquery.com/jquery-latest.js"></script> <script type="text/javascript"> $(document).ready(function(){ // セレクター var myVideo = $('video#myVideo'); var remoteVideo = $('video#remoteVideo'); var textBox = $('input#textBox'); var sendOfferBtn = $('input#sendOffer'); var messageBox = $('div'); // websocket var websocket = new WebSocket('ws://localhost:8124/'); // peer var server = [{"url": "stun:stun.l.google.com:19302"}]; var peer = new webkitRTCPeerConnection({"iceServers": server}); // ユーザIDをランダム生成 var my_uuid = UUID(); function init(){ // 自分のカメラを利用する getUserMedia(); // オファーボタンのクリックイベント sendOfferBtn.click(function(){ var to_uuid = textBox.val(); messageBox.append('Send offer to: ' + to_uuid + '<br>'); createOffer(to_uuid); }); // peerの実装 peer.onicecandidate = function(event) { onCandidate(event); }; peer.onaddstream = function(stream) { onAddStream(stream); }; // websocketの実装 websocket.onopen = function() { onOpen(); }; websocket.onmessage = function(event) { onMessage(event); }; // ページを離れるときにwebsocketを切断しておく。 $(window).bind("beforeunload", function() { websocket.close(1000,"通常切断"); }); } function getUserMedia(){ navigator.webkitGetUserMedia( { audio: true, video: true }, function(stream) { // 自分のカメラ映像を表示 myVideo.attr({ src : window.webkitURL.createObjectURL(stream) }); // 自分のpeerにカメラストリームを接続させる peer.addStream(stream) }, function(err) { console.log(err.name + ': ' + err.message); } ); } // 自分のcandidateデータの処理 function onCandidate(event){ messageBox.append('onCandidate<br>'); if ( Object.keys(event.candidate).length > 0 ) { websocket.send(JSON.stringify({ type : 'candidate', candidate : event.candidate })); } } // 相手のカメラ映像が届いた時の処理 function onAddStream(stream){ messageBox.append('onAddstream<br>'); remoteVideo.attr({ src : window.webkitURL.createObjectURL(stream.stream) }); } // websocket接続完了時の処理 function onOpen(){ textBox.focus(); // 入室情報を送信 websocket.send(JSON.stringify({ type: 'join', uuid: my_uuid })); } // websocketメッセージ受信イベントを処理 function onMessage(event){ var data = JSON.parse(event.data); if (data.type === 'join') { messageBox.append('ID: ' + data.uuid + 'が入室しました<br>'); } else if (data.type === 'sdp') { if(data.to == my_uuid){ // 自分にSDPが送られてきた var sdp = new RTCSessionDescription(data.sdp); if(sdp.type == "offer"){ // offerをもらった messageBox.append('Offerd from ID: '+ data.from + '<br>'); // リモートに相手のsdpをセット peer.setRemoteDescription(sdp, function() { // 送り主にanswerを返す createAnswer(data.from); }); }else if(sdp.type == "answer"){ // answerが返ってきた messageBox.append('Answerd from ID: '+ data.from + '<br>'); // リモートに相手のsdpをセット peer.setRemoteDescription(sdp, function() { messageBox.append('Session connection completed!!<br>'); }); } } } else if (data.type == 'candidate') { var candidate = new RTCIceCandidate(data.candidate); peer.addIceCandidate(candidate); } } function createOffer(to_uuid){ peer.createOffer(function(sdp) { // ローカルに自分のsdpをセット peer.setLocalDescription(sdp, function() { // セット完了したら、相手に自分のSDPを送る websocket.send(JSON.stringify({ type : 'sdp', sdp : sdp, from : my_uuid, to : to_uuid })); }); }); } function createAnswer(to_uuid){ peer.createAnswer(function(sdp) { // この引数のSDPは自分用! peer.setLocalDescription(sdp, function() { // セット完了したら、相手に自分のAnswerSDPを送る websocket.send(JSON.stringify({ type : 'sdp', sdp : sdp, from : my_uuid, to : to_uuid })); }); }); } function UUID() { var uuid = [ (((1+Math.random())*0x10000)|0).toString(16).substring(1), (((1+Math.random())*0x10000)|0).toString(16).substring(1), (((1+Math.random())*0x10000)|0).toString(16).substring(1), (((1+Math.random())*0x10000)|0).toString(16).substring(1), (((1+Math.random())*0x10000)|0).toString(16).substring(1), (((1+Math.random())*0x10000)|0).toString(16).substring(1), (((1+Math.random())*0x10000)|0).toString(16).substring(1), (((1+Math.random())*0x10000)|0).toString(16).substring(1) ].join(""); return uuid; } init(); }); </script> </head> <body> <video id="myVideo" width="400" height="300" autoplay="1" muted></video> <video id="remoteVideo" width="400" height="300" autoplay></video><br> <input type="text" id="textBox"> <input type="button" id="sendOffer" value="Send offer"> <div></div> </body> </html>
動かし方
ローカルで動かす方法を書く。
① ターミナルでindex.jsのあるディレクトリに移動し、
node index.js
でWebSocketサーバを起動する。
② ChromeでクライアントのHTMLファイルを2つ開く。
カメラのアクセスを許可するか聞かれるので、2つとも許可しておく。クライアントにはランダムでIDが割り振られ、webSocket接続時に入室情報として送信する。先に開いた方のクライアントには、自分の入室情報と後に開いた相手の入室情報が表示されているはずなので、相手のIDをコピーして、テキストボックスに貼付ける。
③ 「Send offer」ボタンを押すと、接続が開始する。
リモートサーバーで動かす場合は、WebSocketのアドレスだけ変えれば動くはず。