ミルク色の記録

やったこと、やってみたこと

勉強会に初めてLT参加した話

ちょっと時間開いてしまったけれど、記録のために書いておく。


NDS meetup #8nds-meetup.connpass.com
という勉強会で初めてLTやってみた。


自分は35歳の平凡なプログラマ
勉強会とかデベロッパー向けイベントに参加しても毎回聴くだけの側。


この夏YAPCに行った体験とか、日頃の尊敬するエンジニアさんたちのアドバイスとかでいろいろ思うことがあり、ミリ単位でいいから踏み出してみたいかな…とか思ったので、テーマが自分の好きなJavaScriptという事もあり初挑戦。


発表内容はECMAScript6で書けるようになったシンタックスについて。
普段コード書いていて「あ、コレってもっと簡単に書けるようになったよな…」みたいなのに絞った(クラスとかブロックスコープみたいな忘れにくいのはすっ飛ばした)。

www.slideshare.net

終わったあとに気づいたんだけど、ES6じゃないじゃんってのも混じってた...ごめんなさい。


今回の経験で劇的に何か変わったみたいなのはないけれど、またミリ単位で進んで行きたいかなーみたいな感じ。

YAPC::Asia Tokyo 2015に行ってきた

初参加。
めちゃくちゃ楽しかった。


以下、ふり返り。

きっかけ

去年のuzullaさんの『半端なPHPDisでPHPerに陰で笑われないためのPerl Monger向け最新PHP事情』を動画で見て、「なんなんだろう?この空間。会場でリアルタイムに見たかったな...」と思ったのがきっかけ。

で、チケットの購入をトークのスケジュールが決まるまで…と待機していたら最初見事に買い逃した...軽く放心状態になっていたところをneko_gata_sさんに救っていただいた。

当日の過ごし方

ひたすらトークを聞いて回る、完全にトーク参加中心で過ごして大満足。素人万歳!

  • トーク・質疑応答が終わったら速やかに移動する
  • 水分摂取は生命維持可能な最低限に抑える

と、徹底して動きまわり、前夜祭と当日のすべてのお目当てトークを聴いて回った。
徹底しすぎて無限コーヒー全く飲めなかった...(弁当は頂いた。ごちそうさまでした)

会場とか懇親会でmiyagawaさんをはじめ、注目している方々をたくさん見かけたけど、コミュ力低い方なのでその辺はお察し...ファンボーイとして柱の影からそっと見守った。

所感

前夜祭、待望の「PHP帝国の逆襲」を生で見れて胸が熱くなった。
hayajoさんのおかげでuzullaさんご本人にちょっとだけ挨拶できてステッカーもらえた!

参加したトークすべて面白くて、学びがあって、上手くて、スピーカーの方々の背景にコミュニティの文化みたいなのが感じられて、「自分は趣味+仕事でプログラム書いているだけの人間だったんだなー。やっぱ自分へっぽこだわー。」と思った。

運営スタッフの方々、スピーカーの方々、きっかけとなったuzullaさん、チケット譲っていただいたneko_gata_sさん、仕事扱いにしてくれた会社のボスと快く送り出してくれた家族に感謝しつつ、「楽しかったってだけで終わらせないようにしたいけど、どうしたらいいのやら...」とか考えながら、自分の最初で最後のYAPCが終わった。

WebSocketとcreateObjectURLとvideoで遊んでみた

(´・ω・`)。oO(WebSocketのバイナリデータ送信って、クライアントの方も対応したのかなぁ…)

と、ふと思ったので試してみた。

環境こんな感じ。

  1. OS : ubuntu 11.04
  2. ブラウザ : Google Chrome 17.0.963.56
  3. 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);