WebSocketとcreateObjectURLとvideoで遊んでみた
(´・ω・`)。oO(WebSocketのバイナリデータ送信って、クライアントの方も対応したのかなぁ…)
と、ふと思ったので試してみた。
環境こんな感じ。
- OS : ubuntu 11.04
- ブラウザ : Google Chrome 17.0.963.56
- node.js v0.6.11, connect, websocket
nodeのwebsocketモジュールで
client.sendBytes(data);
みたいなメソッドがあったのを覚えていたので、画像ファイルを送ってブラウザ側で貰えているか、
messageイベントのデータを見てみると…届いてるっぽい。
せっかくなので、前々からちょっと試してみたかったことを試してみた。
なんちゃってストリーミング配信的な
適当に用意した動画ファイルをOpenShot Video Editorを使って、
webmでエンコードして10秒毎に分割。連番のファイル名にしてひとつのディレクトリに設置。
それをクライアントに次々送りつけて、createObjectURLで変換してvideoタグのsrcにセットして再生していく。
最初videoタグ一つでやってたら、継ぎ目がプチプチしてたので、
videoタグを2つ用意してダブルバッファリング的にやってみたら、
割といい感じになったっぽい。
カメラからとかで動的にwebmファイル作れるともっと面白いんだけど。。
書いたソースは下のとおり。
サーバ側
var connect = require('connect'); var WebSocketServer = require('websocket').server; var util = require('util'); var fs = require('fs'); var EventEmitter = require('events').EventEmitter; /** * ファイルリストを順番に処理するための簡易キュー */ function Queue(queue) { EventEmitter.call(this); this.queue_ = queue; this.next(); } util.inherits(Queue, EventEmitter); Queue.prototype.next = function() { var that = this; process.nextTick(function() { var q = that.queue_.shift(); if (q != null) { that.emit('data', q); } else { that.emit('end'); } }); }; /** * 簡易HTTPサーバ */ var httpServer = connect.createServer( connect.static(__dirname + '/public_html', { maxAge : 86400000 }) , connect.cookieParser() , connect.bodyParser() , connect.session({ secret : 'hogefugapiyo' }) , connect.router(function(route) { }) ).listen(3000); /** * WebSocketサーバ */ var wsServer = new WebSocketServer({ httpServer : httpServer, autoAcceptConnections : true }); wsServer.on('connect', function(client) { util.log('client connect'); client.on('message', function(msg) { var param = msg.utf8Data; switch(param) { case 'play': // クライアントからplayメッセージを受け取ったらデータ送信 sendData(client); break; } }); client.on('close', function() { util.log('client disconnect'); }); }); console.log('http://localhost:3000/'); /** * データをクライアントへ送信 */ function sendData(client) { var path = __dirname + '/public_html/video'; // ディレクトリ一覧読み込み fs.readdir(path, function(err, files) { if (err) { util.log(err); return; } // ファイル一覧を名前順にソート files.sort(); var queue = new Queue(files); queue.on('data', function(file) { // ファイル読み込み fs.readFile(path + '/' + file, function(err, data) { if (!err) { client.sendBytes(data); } // データを送信完了したら次のキューへ queue.next(); }); }); queue.on('end', function() { // 全部送ったら終わり console.log('end'); }); }); }
クライアント側
HTMLこんな感じ。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="css/index.css" /> <title>Video Viewer</title> </head> <body> <div id="viewer"> <div id="screen_cont"> <video id="screen1" style="display:none;"></video> <video id="screen2" style="display:none;"></video> </div> <div id="controller"> <input type="button" id="play" value="play" /> </div> </div> <script type="text/javascript" src="js/jquery-1.6.4.min.js"></script> <script type="text/javascript" src="js/index.js"></script> </body> </html>
クライアント側のソースはこんな感じ。
!function(global) { /** * サーバから受信したデータキュー */ var datas_ = []; /** * 再生待ちのvideoタグキュー */ var readyScreens_ = []; /** * データ設定待ちのvideoタグキュー */ var waitScreens_ = []; /** * 最初のデータ受信判定フラグ */ var firstData_ = true; // スクリーン1 waitScreens_.push(document.getElementById('screen1')); waitScreens_[0].addEventListener('ended', function() { // 再生終了イベント // スクリーンを切り替え changeScreen(this); updateQueue(); }, false); // スクリーン2 waitScreens_.push(document.getElementById('screen2')); waitScreens_[1].addEventListener('ended', function() { changeScreen(this); updateQueue(); }, false); /** * スクリーンを切り替える * @param {Element} oldScreen videoエレメント */ function changeScreen(oldScreen) { // 再生済みのスクリーンを非表示にして設定待ちスクリーンキューに追加 oldScreen.style.display = 'none'; waitScreens_.push(oldScreen); // 再生待ちスクリーンを取得 var screen = readyScreens_.shift(); if (screen) { // 存在したら表示して再生 screen.style.display = ''; screen.play(); } } /** * キューの更新 */ function updateQueue() { if (waitScreens_.length > 0 && datas_.length > 0) { // 受信データとデータ設定待ちのスクリーンが存在する場合それぞれ取り出し var screen = waitScreens_.shift(); var blob = datas_.shift(); // src属性に設定 screen.src = blob; if (firstData_) { // 最初の受信データだった場合 firstData_ = false; // データを再生 screen.style.display = ''; screen.play(); } else { // 再生待ちのスクリーンキューに追加 readyScreens_.push(screen); } } } // WebSocketの接続を開く var socket = new WebSocket('ws://' + location.host + '/'); // messageイベント socket.addEventListener('message', function(evt) { // メッセージを受け取ったらcreateObjectURLで変換してデータキューに入れる datas_.push(window.webkitURL.createObjectURL(evt.data)); // キュー更新 updateQueue(); }, false); $('#play').click(function() { // 再生ボタンクリックしたら再生メッセージをサーバへ通知 socket.send('play'); }); }(this);