WebSocketを使ってなんちゃってビデオチャットを作ってみた
はじめに
以下のページでjQueryでWebカメラを操作できると知ったので、WebSocketを使ってなんちゃってビデオチャットを書いてみました。
WebSocketサーバに接続した2名で1対1のビデオチャットが出来ます。送受信するデータはカメラの映像とテキストのみで、音声データの送受信には対応していません。
実行環境
- Chrome 10.0.612.3 dev
- Firefox 4 beta 7
- node.js 0.3.1
- node-websocket-server 1.4.01
- jQuery webcam plugin
WebScoketサーバ側のプログラム
今回はnode.jsを使ってサーバ側のプログラムを書きました。
- videoChatServer.js
var sys = require('sys') var http = require("http") var ws = require('./miksago-node-websocket-server/lib/ws/server'); var server = ws.createServer(); const NODE_MAX = 2; // 接続上限数 var nodeCount = 0; // 現在の接続数 // WebSocketコネクションが確立したときの処理 server.addListener("connection", function(conn){ ++nodeCount; if (nodeCount > NODE_MAX) { conn.close(); --nodeCount; } else { sys.log("connected: " + conn.id); // データを受信したときの処理 conn.addListener("message", function (msg) { sys.log(conn.id + ": length is " + msg.length); conn.broadcast(msg); }); } }); server.listen(8000);
Webサーバ側のプログラム
ポート番号が同じでなければ、WebサーバとWebSocketサーバは同一ホストでも大丈夫です。今回は80番ポートでWebサーバを、8000番ポートでWebSocketサーバを動かします。
以下のindex.html、videoChatClient.jsと同じ階層にjquery-1.4.4.min.jsとココからダウンロードしたjquery.webcam.jsとjscam_canvas_only.swfを置きます。
- index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <script type="text/javascript" src="jquery-1.4.4.min.js"></script> <script type="text/javascript" src="jquery.webcam.js"></script> <script type="text/javascript" src="videoChatClient.js"></script> </head> <body> <div id="camera"></div> <div style="float:left;"><canvas id="myCamera"></canvas></div> <div id="myCameraInfo">captureFPS</div> <hr style="clear:left;"> <div style="float:left;"><canvas id="targetCamera"></canvas></div> <div id="targetCameraInfo">drawFPS</div> <hr style="clear:left;"> <input id="inputText" type="text" size="40"> <input id="sendButton" type="submit" value="send"> <div id="log"></div> </body> </html>
- videoChatClient.js
const DISP_WIDTH = 320; const DISP_HEIGHT = 240; var myContext = null; var myCameraImage = null; var myCanvas = null; var targetContext = null; var targetCanvas = null; var captureFPS = null; var drawFPS = null; var pos = 0; var ws = null; function log (msg) { var logObj = $("#log"); logObj.html(msg + "<br>" + logObj.html()); } function save (data) { var col = data.split(";"); for (var i=0; i<320; ++i) { var tmp = parseInt(col[i]); myCameraImage.data[pos + 0] = (tmp >> 16) & 0xFF; myCameraImage.data[pos + 1] = (tmp >> 8) & 0xFF; myCameraImage.data[pos + 2] = tmp & 0xFF; myCameraImage.data[pos + 3] = 0xFF; pos += 4; // R, G, B, alpha } if (pos >= 4 * DISP_WIDTH * DISP_HEIGHT) { pos = 0; myContext.putImageData(myCameraImage, 0, 0); $("#myCameraInfo").html(Math.round(captureFPS()*100)/100); var sendData = { type: "img", data: myCanvas[0].toDataURL() }; if (ws.readyState == ws.OPEN) { setTimeout(function () { ws.send(JSON.stringify(sendData)); }, 0); } } } function parseData (rawData) { var recieveData = JSON.parse(rawData); var func = null; switch (recieveData["type"]) { case "msg": func = writeMessage; break; case "img": func = drawImage; break; } func(recieveData["data"]); } function createFPS () { var oldTime = new Date(); return function () { var newTime = new Date(); var ps = 1000 / (newTime - oldTime); oldTime = newTime; return ps; } } function drawImage (data) { var img = new Image(); img.src = data; img.onload = function () { targetContext.drawImage(img, 0, 0); $("#targetCameraInfo").html(Math.round(drawFPS()*100)/100); }; } function writeMessage (msg) { log(msg); } function sendMessage (msg) { var sendData = { type: "msg", data: msg }; if (ws.send(JSON.stringify(sendData))) { log("<You> " + msg); return true; } return false; } // init $(function () { $("#camera").webcam({ width: 0, height: 0, mode: "stream", swffile: "jscam_canvas_only.swf", onTick: function () {}, onSave: save, onCapture: function () {}, debug: function () {}, onLoad: function () { webcam.capture(); captureFPS = createFPS(); drawFPS = createFPS(); } }); myCanvas = $("#myCamera"); myCanvas.attr("width", DISP_WIDTH); myCanvas.attr("height", DISP_HEIGHT); myContext = myCanvas[0].getContext("2d"); myContext.clearRect(0, 0, DISP_WIDTH, DISP_HEIGHT); myCameraImage = myContext.getImageData(0, 0, DISP_WIDTH, DISP_HEIGHT); targetCanvas = $("#targetCamera"); targetCanvas.attr("width", DISP_WIDTH); targetCanvas.attr("height", DISP_HEIGHT); targetContext = targetCanvas[0].getContext("2d"); targetContext.clearRect(0, 0, DISP_WIDTH, DISP_HEIGHT); ws = new WebSocket("ws://" + location.hostname + ":8000"); ws.onopen = function () { log("connected."); }; ws.onmessage = function (e) { parseData(e.data); }; $("#sendButton").click(function () { var inputObj = $("#inputText"); if (sendMessage(inputObj.val())) { inputObj.val(""); } }); })
実行したときの様子
カメラの画像を取得するjQueryのプラグインはFlashを使っているので、一度カメラへのアクセスを許可する必要があります。
上記のindex.htmlに初めてアクセスするときはvideoChatClient.jsの一部を以下のように編集して下さい。
// init $(function () { $("#camera").webcam({ width: 0, // ここを320に変更 height: 0, // ここを240に変更 mode: "stream", swffile: "jscam_canvas_only.swf", onTick: function () {}, onSave: save,
そして、Flashの設定でカメラへのアクセスを許可するようにして下さい。このとき、『設定を保存』にチェックを入れるのを忘れないようにしてください。
設定ができたら、以下のようにWebSocketサーバを動かします。
% node videoChatServer.js
無事に動いたらindex.htmlに2つのPCでアクセスすると以下のようにビデオチャットができるはずです。
上の段の画像は自分のWebカメラの映像で、その隣にはキャプチャ速度(キャプチャ回数/秒)が表示されます。そして、その下の段には相手のWebカメラの映像とそのフレームレートが表示されます。一番下の段には、お互いが入力したテキストメッセージが積まれていきます。
上のスクリーンショットからもわかるように、キャプチャ速度が1秒間に1回以下という遅さのため、ビデオチャットとは呼べないほどカクカクしてます(;・∀・)
おわりに
次はカメラのキャプチャ速度を上げるためにjQueryのプラグインをいじってみようかと思ってます。
今回使ったjQueryのプラグインはFlashとJavaScriptを連携させていましたが、W3Cが策定している
HTML Media Captureを使えるようになれば、Flashを一切使わずにビデオチャットが作れるかもしれませんね(^−^)