一年間、一回も更新してない!!!
つまり、、
趣味の時間がほぼ皆無ということの現れです。
業務と子育てに追われた一年でしたね。。。。。
危なく年をこすところだった。あぶない。なぶあい。
話を変えて。
今月初旬に 社内でちょっとしたプレゼンをする事になったんだけど
場所の都合上、プロジェクターが設置できないっぽいという情報を耳にしました。
なのでスマホをプロジェクタがわりにしたらええんでないの?かつ、
知らない人(私も含まれるけど)向けにwebsocketの紹介デモをするのにもってこいじゃないか。
と最近手を動かしてなかったので勉強兼ねて。
nodejsとwebsocket(socket.io)使ってスライドのデモを。
サンプル引っ張ってきてちょっとだけ弄って。
画面の制御だけをくっつけた登壇者タイミングで
制御するだけの簡単なデモを作ってみました。
環境と概要
SenchaUGで頂いたさくらのクラウドを使いました。ありがとうございます。さくらの方。
ありがとうございます。SenchaUG。
あのあとSenchaTouch使って社員旅行のパンフサイト作ったり
今回の様にnode突っ込んだりと遊ばせて頂いております。
ホントは社員みんなに触ってみてもらいたかったので
ユーザごとのフォルダに画像だけ放り込めば
メニューが生成されて複数人で使えるようにしたかったんだけどね。。
そんな時間はどこにもありませんでした。
ルーティングやらテンプレートなどの
設定などはもうなにをどうしていいか
わからなかったのでググって express + ejs という組み合わせにしました。
当時、会社のお昼休みしか遊ぶ時間がなかったので
- 機能はシンプル(と言うかそんなに作れません)
- ひとまず動けばイイ(・∀・)イイ!!
参考サイト
- http://gihyo.jp/dev/serial/01/nodejs
- http://snippets.takahironakamori.jp/2013/01/nodejs_install/
大きな流れ
- ざっくり機能をまとめる
- nodeのインストール
- expressのインストール
- foreverのインストール
- socket.ioのインストール
- クライアントサイドの実装
- サーバサイドの実装
1:ざっくり機能をまとめる
まず、最初に述べたとおり時間がない。(ほんとになかった)
一方的な通信で良い。
ざっくり同期とれりゃいい。
URL
聴取者はスマートフォンなのでURL入力をあまりさせたくないのでhttp://さくらのクラウドのIP:3000/
にした。登壇者は
http://さくらのクラウドのIP:3000/speaker
にした。スライド
最初、パワーポイントをhtmlとして吐き出してそれを操作させようと思ったんだけど、オブジェクトがうまく吐き出せない部分があった際に、
画像としてフォルダ単位で吐き出せば綺麗に吐き出せたから、ああ、それでいいじゃん。と。
登壇者
右矢印、左矢印で自身のイメージを変更。現在の表示インデックスをブロードキャスト。
聴衆者
送信されてきたインデックスからパスを生成してsrcを書き換え。登壇者、聴衆者共通
初期接続時、指定フォルダに格納されたファイルリストとURL生成用のパス情報を取得する必要がある。
ファイルリストは配列で。
最初、登壇者と聴衆者、全く同じ機構を使って登壇者が押したボタンを
ブロードキャストして聴衆者側のハンドラでボタンの押下を
シミュレートするようにしたんだけど
そもそもどっち押したかなんて必要ないわけだし。
今見えているインデックスだけ通知して
聴衆者が自主的に取得すればいいだけだからね。
だから今見てるインデックスだけ通知するように。
2:nodeのインストール
http://snippets.takahironakamori.jp/2013/01/nodejs_install/こちらを参考に入れました。
本当にありがとうございました。
3:expressのインストールと動作確認
今回のサンプルはejsを利用するため、-e オプションです。(jadeは見た目で拒絶しちゃいました)
npm install -g express
npm install -g express-generator
↑※こちら、Ver4.0.0以降?必要。expressコマンド自体はepxressと分離したみたい。。今回作成するのはslideappとしたので
作成するフォルダで
[hogehoge@fugafuga noderoot]$ express -e slideapp
create : slideapp
create : slideapp/package.json
create : slideapp/app.js
create : slideapp/public
create : slideapp/public/javascripts
create : slideapp/public/images
create : slideapp/public/stylesheets
create : slideapp/public/stylesheets/style.css
create : slideapp/routes
create : slideapp/routes/index.js
create : slideapp/routes/user.js
create : slideapp/views
create : slideapp/views/index.ejs
install dependencies:
$ cd slideapp && npm install
run the app:
$ node app
後半に書いてあるように必要なパッケージをインストールして
起動します。
[hogehoge@fugafuga noderoot]$ cd slideapp
[hogehoge@fugafuga slideapp]$ npm install
npm http GET https://registry.npmjs.org/express/3.4.5
npm http GET https://registry.npmjs.org/ejs
npm http 304 https://registry.npmjs.org/ejs
npm http 200 https://registry.npmjs.org/express/3.4.5
npm http GET https://registry.npmjs.org/express/-/express-3.4.5.tgz
npm http 200 https://registry.npmjs.org/express/-/express-3.4.5.tgz
npm http GET https://registry.npmjs.org/connect/2.11.1
npm http GET https://registry.npmjs.org/commander/1.3.2
npm http GET https://registry.npmjs.org/range-parser/0.0.4
npm http GET https://registry.npmjs.org/mkdirp/0.3.5
npm http GET https://registry.npmjs.org/cookie/0.1.0
npm http GET https://registry.npmjs.org/buffer-crc32/0.2.1
npm http GET https://registry.npmjs.org/fresh/0.2.0
npm http GET https://registry.npmjs.org/methods/0.1.0
npm http GET https://registry.npmjs.org/send/0.1.4
npm http GET https://registry.npmjs.org/cookie-signature/1.0.1
npm http GET https://registry.npmjs.org/debug
npm http 304 https://registry.npmjs.org/cookie/0.1.0
npm http 304 https://registry.npmjs.org/range-parser/0.0.4
npm http 304 https://registry.npmjs.org/buffer-crc32/0.2.1
npm http 304 https://registry.npmjs.org/fresh/0.2.0
npm http 304 https://registry.npmjs.org/mkdirp/0.3.5
npm http 304 https://registry.npmjs.org/methods/0.1.0
npm http 304 https://registry.npmjs.org/send/0.1.4
npm http 304 https://registry.npmjs.org/cookie-signature/1.0.1
npm http 200 https://registry.npmjs.org/debug
npm http 200 https://registry.npmjs.org/connect/2.11.1
npm http GET https://registry.npmjs.org/connect/-/connect-2.11.1.tgz
npm http 200 https://registry.npmjs.org/commander/1.3.2
npm http GET https://registry.npmjs.org/commander/-/commander-1.3.2.tgz
npm http 200 https://registry.npmjs.org/connect/-/connect-2.11.1.tgz
npm http 200 https://registry.npmjs.org/commander/-/commander-1.3.2.tgz
npm http GET https://registry.npmjs.org/mime
npm http GET https://registry.npmjs.org/keypress
npm http GET https://registry.npmjs.org/qs/0.6.5
npm http GET https://registry.npmjs.org/bytes/0.2.1
npm http GET https://registry.npmjs.org/pause/0.0.1
npm http GET https://registry.npmjs.org/uid2/0.0.3
npm http GET https://registry.npmjs.org/raw-body/1.1.1
npm http GET https://registry.npmjs.org/negotiator/0.3.0
npm http GET https://registry.npmjs.org/multiparty/2.2.0
npm http 304 https://registry.npmjs.org/mime
npm http 304 https://registry.npmjs.org/uid2/0.0.3
npm http 200 https://registry.npmjs.org/qs/0.6.5
npm http GET https://registry.npmjs.org/qs/-/qs-0.6.5.tgz
npm http 304 https://registry.npmjs.org/pause/0.0.1
npm http 304 https://registry.npmjs.org/negotiator/0.3.0
npm http 304 https://registry.npmjs.org/multiparty/2.2.0
npm http 200 https://registry.npmjs.org/raw-body/1.1.1
npm http GET https://registry.npmjs.org/raw-body/-/raw-body-1.1.1.tgz
npm http 304 https://registry.npmjs.org/keypress
npm http 200 https://registry.npmjs.org/qs/-/qs-0.6.5.tgz
npm http 304 https://registry.npmjs.org/bytes/0.2.1
npm http 200 https://registry.npmjs.org/raw-body/-/raw-body-1.1.1.tgz
npm http GET https://registry.npmjs.org/readable-stream
npm http GET https://registry.npmjs.org/stream-counter
npm http 304 https://registry.npmjs.org/readable-stream
npm http 304 https://registry.npmjs.org/stream-counter
npm http GET https://registry.npmjs.org/core-util-is
npm http GET https://registry.npmjs.org/debuglog/0.0.2
npm http 304 https://registry.npmjs.org/core-util-is
npm http 304 https://registry.npmjs.org/debuglog/0.0.2
ejs@0.8.5 node_modules/ejs
express@3.4.5 node_modules/express
├── methods@0.1.0
├── range-parser@0.0.4
├── cookie-signature@1.0.1
├── fresh@0.2.0
├── debug@0.7.4
├── buffer-crc32@0.2.1
├── cookie@0.1.0
├── mkdirp@0.3.5
├── send@0.1.4 (mime@1.2.11)
├── commander@1.3.2 (keypress@0.1.0)
└── connect@2.11.1 (uid2@0.0.3, pause@0.0.1, qs@0.6.5, bytes@0.2.1, raw-body@1.1.1, negotiator@0.3.0, multiparty@2.2.0)
こんな具合になるので、実行して確認してみます。[hogehoge@fugafuga slideapp]$ node app.js
Express server listening on port 3000
ブラウザで確認します。コンソールにも出力されています。
GET / 200 10ms - 206b
GET /stylesheets/style.css 200 7ms - 110b
これでひとまず、express+ejsの環境は出来ました。3.1:フォルダ構成
ちなみにexpressにおけるフォルダ構成はこんな具合でした。▾ slideapp/
▾ node_modules/ //<-----npmでインストールされた者達。
▸ .bin/
▸ ejs/
▸ express/
▾ public/ //<-----ちなみにここがwebルートになるらしいよ。
▸ images/
▸ javascripts/
▸ stylesheets/
▾ routes/
index.js //<-----このjs内でviewとのバインド実装をしたらいいみたい。
user.js
▾ views/
index.ejs //<-----ビューのテンプレート
app.js //<-----この中でメインロジック書いちゃいます。
//<-----URLと対応するroutesフォルダ配下の呼び出しルーティング設定はこちら。
(ホントはexportsとかで別ファイルで実装したほうが良さそうなんでしょうけど)
package.json
4:foreverのインストール
次に永続化npm install -g forever
としてインストール。
一番簡単な使い方forever の手っ取り早い使い方。
forever start xxxx.js (開始)
forever stop xxxx.js (終了)
forever list (現在実行中のリスト)
さっき起動していたnodeをCtrl+Cで止めて、ブラウザで動いていないことを確認。
今度はforeverで起動してみます。
[hogehoge@fugafuga slideapp]$ forever start app.js
warn: --minUptime not set. Defaulting to: 1000ms
warn: --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info: Forever processing file: app.js
[hogehoge@fugafuga slideapp]$
ブラウザで確認します。5:socket.ioのインストール
さて、これでようやくnodeによるコンテンツ出力までできたんですが、まだwebsocketの環境が整っていません。
socket.ioを使います。
[hogehoge@fugafuga slideapp]$ npm install socket.io
npm http GET https://registry.npmjs.org/socket.io
npm http 200 https://registry.npmjs.org/socket.io
npm http GET https://registry.npmjs.org/socket.io-client/0.9.16
npm http GET https://registry.npmjs.org/policyfile/0.0.4
npm http GET https://registry.npmjs.org/redis/0.7.3
npm http GET https://registry.npmjs.org/base64id/0.1.0
npm http 304 https://registry.npmjs.org/base64id/0.1.0
npm http 304 https://registry.npmjs.org/socket.io-client/0.9.16
npm http 304 https://registry.npmjs.org/policyfile/0.0.4
npm http 200 https://registry.npmjs.org/redis/0.7.3
npm http GET https://registry.npmjs.org/redis/-/redis-0.7.3.tgz
npm http 200 https://registry.npmjs.org/redis/-/redis-0.7.3.tgz
npm http GET https://registry.npmjs.org/uglify-js/1.2.5
npm http GET https://registry.npmjs.org/ws
npm http GET https://registry.npmjs.org/xmlhttprequest/1.4.2
npm http GET https://registry.npmjs.org/active-x-obfuscator/0.0.1
npm http 304 https://registry.npmjs.org/uglify-js/1.2.5
npm http 304 https://registry.npmjs.org/active-x-obfuscator/0.0.1
npm http 304 https://registry.npmjs.org/ws
npm http 304 https://registry.npmjs.org/xmlhttprequest/1.4.2
npm http GET https://registry.npmjs.org/zeparser/0.0.5
npm http GET https://registry.npmjs.org/commander
npm http GET https://registry.npmjs.org/nan
npm http GET https://registry.npmjs.org/tinycolor
npm http GET https://registry.npmjs.org/options
npm http 304 https://registry.npmjs.org/tinycolor
npm http 200 https://registry.npmjs.org/commander
npm http 304 https://registry.npmjs.org/zeparser/0.0.5
npm http 304 https://registry.npmjs.org/nan
npm http 304 https://registry.npmjs.org/options
> ws@0.4.31 install /var/www/html/noderoot/slideapp/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws
> (node-gyp rebuild 2> builderror.log) || (exit 0)
make: ディレクトリ `/var/www/html/noderoot/slideapp/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/build' に入ります
CXX(target) Release/obj.target/bufferutil/src/bufferutil.o
SOLINK_MODULE(target) Release/obj.target/bufferutil.node
SOLINK_MODULE(target) Release/obj.target/bufferutil.node: Finished
COPY Release/bufferutil.node
CXX(target) Release/obj.target/validation/src/validation.o
SOLINK_MODULE(target) Release/obj.target/validation.node
SOLINK_MODULE(target) Release/obj.target/validation.node: Finished
COPY Release/validation.node
make: ディレクトリ `/var/www/html/noderoot/slideapp/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/build' から出ます
socket.io@0.9.16 node_modules/socket.io
├── base64id@0.1.0
├── policyfile@0.0.4
├── redis@0.7.3
└── socket.io-client@0.9.16 (xmlhttprequest@1.4.2, uglify-js@1.2.5, active-x-obfuscator@0.0.1, ws@0.4.31)
6:クライアントサイドの実装
操作パネルつきのやつを作ってそれを登壇者操作パネルだけ引っこ抜いたものを聴衆者にしちゃいます。
あと、スライドの制御用とソケットの制御用って2つの
JSファイルに分けちゃいました。。
▾ slideapp/
▸ node_modules/
▾ public/
▸ images/
▾ javascripts/
ClientApp.js //<-----エントリポイントと本体
jquery-1.10.2.min.js //<-----ちょっと操作するので必要(ExtCore使えよっとか突っ込まれそうだけど)
SlideController.js //<-----スライド制御用
socket.io.js //<-----socket.ioのクライアント向けのフォルダから引っ張ってきた。
▸ slide/
▸ stylesheets/
▸ routes/
▾ views/
audience.ejs //<-----聴衆者側テンプレート
speaker.ejs //<-----登壇者側テンプレート
app.js
6.1:登壇者側のテンプレート(audience.ejs)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SpeakerView</title>
<script type="text/javascript" charset="utf-8" src="/javascripts/jquery-1.10.2.min.js"></script>
<script type="text/javascript" charset="utf-8" src="/javascripts/socket.io.js"></script>
<script type="text/javascript" charset="utf-8" src="/javascripts/SlideController.js"></script>
<script type="text/javascript" charset="utf-8" src="/javascripts/ClientApp.js"></script>
<link rel="stylesheet" type="text/css" href="/stylesheets/slide.css"/>
</head>
<body>
<div class="wrapper">
<div class="content">
<p class="slide"><img id="slide" src=""></img></p>
</div>
<div class="footer">
<div class="backbtn">
<img id="backbtnimg" src="/images/seek-back.png" ></img>
</div>
<div class="forwardtn">
<img id="forwardbtnimg" src="/images/seek-forward.png" ></img>
</div>
</div>
</div>
</body>
</html>
6.2:聴衆者側のテンプレート(speaker.ejs)
フッター部分ぶっこ抜いた状態のものです。いいんです。みてもらうだけですから。<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SpeakerView</title>
<script type="text/javascript" charset="utf-8" src="/javascripts/jquery-1.10.2.min.js"></script>
<script type="text/javascript" charset="utf-8" src="/javascripts/socket.io.js"></script>
<script type="text/javascript" charset="utf-8" src="/javascripts/SlideController.js"></script>
<script type="text/javascript" charset="utf-8" src="/javascripts/ClientApp.js"></script>
<link rel="stylesheet" type="text/css" href="/stylesheets/slide.css"/>
</head>
<body>
<div class="wrapper">
<div class="content">
<p class="slide"><img id="slide" src=""></img></p>
</div>
</div>
</body>
</html>
6.3:スライドの制御(SlideController.js)
- 初期化
- パスの生成
- ボタン制御
- 画像差し替え
インデックスだけもらってパスを生成して自分で取得すりゃいいでしょ。
ということにしたんでファイルパス生成情報だけは初期化時に渡す必要があります。
var SlideController = function (){};
SlideController.prototype = {};
(function () {
var me = this;
me._pageindex = 0;
/**
* me._slidepath スライドパス生成
*/
me._slidepath = function (){
var me = this,
path = '';
if ($.isArray(me._slide.slide)
&& me._pageindex >= 0
&& me._pageindex < me._slide.slide.length) {
path = me._slide.path + me._slide.slide[me._pageindex];
}
return path;
}
/**
* me.init 初期化
* @param viewid スライド用DOMエレメントID
* @param slideinfo
* @return this
*/
me.init = function (viewid,slideinfo) {
var me = this;
me._slide = slideinfo;
me._viewid = viewid;
return me;
}
/**
* me.reset リセット
* @return this
*/
me.reset = function () {
var me = this;
me._pageindex = 0;
return me;
}
/**
* me.forward 進む
* @return this
*/
me.forward = function () {
var me = this;
((me._pageindex + 1) <= (me._slide.slide.length-1)) ? me._pageindex++ : 0;
return me;
}
/**
* me.back 戻る
* @return this
*/
me.back = function () {
var me = this;
me._pageindex = (me._pageindex-1 < 0) ? 0 : me._pageindex-1;
return me;
}
/**
* me.getPageIndex 現在のページインデックス取得
* @return _pageindex
*/
me.getPageIndex = function() {
var me = this;
return me._pageindex;
}
/**
* me.disp スライド表示
* @return this
*/
me.disp = function(page) {
var me = this;
if ($.isNumeric(page)) {
me._pageindex = page;
}
$('#' + me._viewid).attr('src',me._slidepath());
return me;
}
}).apply(SlideController.prototype);
6.4:エントリポイントと本体(ClientApp.js)
- 初期化メッセージハンドラ
- 表示メッセージハンドラ
- ボタン押下時の制御
最後にサーバと結合させて確認。
コネクト時にスライド情報わたしゃいいんだけど
ホントは他の人向けにスライド情報を切り替えたかったので
初期化メッセージとかにして別の口にした。
(コネクト時に引数を渡す方法を調べる時間とやる気すらなかったのは内緒。)
// エントリポイント
$(document).ready(function () {
// スライドコントローラ
var slide = new SlideController();
// サーバに接続する
var socket = io.connect('http://133.242.235.218:3000')
// サーバに接続時
socket.on('connect', function() {
// ローカル確認用。
/*slide.init('slide',{
path: './slide/hoge/',
slide: [
'スライド1.jpg',
'スライド2.jpg',
'スライド3.jpg'
]
});*/
console.log('connect!!');
});
/**
* スライド情報初期化
*/
socket.on('init', function(slideinfo) {
console.log('スライド情報初期化');
slide.init('slide',slideinfo);
slide.reset().disp();
});
/**
* スライド表示指示
*/
socket.on('show', function(page) {
//console.log('ここでスライドのページを変更');
//console.log(msg);
slide.disp(page);
});
// メッセージを送る
/**
* varsendmessage
* @param varmsg
*/
var sendmessage = function (msg) {
socket.send(msg);
}
/**
* 戻るボタン
*/
$('#backbtnimg').bind('click',function(){
console.log('back押下');
slide.back().disp();
sendmessage(slide.getPageIndex());
});
/**
* 進むボタン
*/
$('#forwardbtnimg').bind('click',function(){
console.log('forward押下');
slide.forward().disp();
sendmessage(slide.getPageIndex());
});
});
7:サーバサイドの実装
expressと合わせる際の引っかかりどころはcreateServerして出来たサーバをsocket.ioに設定する部分。
これやってないと404が返却されてきてハマりました。
今ググるとサクッと一発で出てきました。
当時どうやってググっていたんでしょうか。
▾ slideapp/
▸ node_modules/
▸ public/
▾ routes/
audience.js <-------このファイルです。
speaker.js <-------このファイルです。
▸ views/
app.js <-------このファイルです。
7.1サーバサイド本体(app.js)
- クライアントからの接続準備
- 接続時初期処理
- メッセージ処理
の返却とメッセージの受け渡しのみです。
/**
* Module dependencies.
*/
var express = require('express');
var fs = require("fs");
var io = require('socket.io');
//※ココ ルータ実装読込
var speaker = require('./routes/speaker');
var audience = require('./routes/audience');
var http = require('http');
var path = require('path');
var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
//※ココ routing URLとViewのヒモづけ。
app.get('/speaker', speaker.slide);
app.get('/', audience.slide);
// サーバ生成(メソッドチェーンせずに、一旦変数に確保) ★接続準備
var server = http.createServer(app);
// リスン開始
server.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
// ソケット生成
var socketio = io.listen(server);
// コールバックbind
socketio.sockets.on('connection', function (socket) {
//★メッセージ処理 showメッセージ メッセージをそのままshowとしてブロードキャスト
socket.on('message',function(data) {
// 表示イベント発行
socket.broadcast.emit('show', data);
});
//★初期処理処理 接続してきた相手に初期化イベント発行(パスとファイルリスト送信)
//http://nodejs.org/docs/v0.3.1/api/fs.html#fs.readdir
fs.readdir('./public/slide/hogehoge/',function(err,files) {
// 初期イベント発行
socket.emit('init',{
path: './slide/hogehoge/',
slide: files
});
})
});
7.2 View(audience.js)
ejsをレンダリングします。exports.slide = function(req, res){
res.render('audience', { title: 'Audience' });
};
7.3 View(speaker.js)
ejsをレンダリングします。exports.slide = function(req, res){
res.render('speaker', { title: 'Speaker' });
};
感想
みんなが使えるようにフォルダ名リストでメニュー画面生成させて
フォルダ配下に画像突っ込めばそれっぽく使えるように。
と思ったのですが平日むちゃくちゃ忙しくて
実装する暇が全くなく。、間に合わず。
当日ぶっつけリハーサル無しで使ってみたんですが、
0.5〜1秒ほど遅延することがありました。(しょぼショボ)
セカンダリサーフェスとして裏に読み込み用にimgタグつくって
事前読込とかしておくといいかもしません。
ですが、まぁみんなのスマホに私のスライドが
一斉に出力されたときのちょっと「ぉぉ」的な驚きの反応を
見れただけでも、まぁ、うれしかったです。。
ちょっとした時間で構築してちょっと驚かせる
(websocket知らない人限定だけど)
ことができるのもプログラムのおもしろさだな。
と再確認。