WebSocketを使ってなんちゃってビデオチャットを作ってみた

はじめに

以下のページでjQueryWebカメラを操作できると知ったので、WebSocketを使ってなんちゃってビデオチャットを書いてみました。
WebSocketサーバに接続した2名で1対1のビデオチャットが出来ます。送受信するデータはカメラの映像とテキストのみで、音声データの送受信には対応していません。

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("&lt;You&gt; " + 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プラグインFlashJavaScriptを連携させていましたが、W3Cが策定している
HTML Media Captureを使えるようになれば、Flashを一切使わずにビデオチャットが作れるかもしれませんね(^−^)