實時彈幕系統的設計與實現
http://cnodejs.org/topic/54fd8d4a1e9291e16a7b3598
看新年晚會的時候,發現最大的樂趣就是微信上牆了,但是量大了要等好久才能看見自己發的,為什麼不能是彈幕的形式呢?發現在GitHub上開源了一個JS彈幕模組核心CommentCoreLibrary,慢慢開始學習Node.js的一套。原來是比較做後臺開發的,也是第一次做這樣的分享,請大家多多指教啦……
一、Express
Express是Node.js最流行的一款web框架,小而靈活。Node.js和npm的安裝配置可以參考這裡。
可以通過npm安裝Express(參考),也可以使用Express application generator快速產生一個Express樣例
對於Express初學者,用Express application generator生成樣例更有利於快速上手。因此就以此為例:
# install Express application generator
$ npm install express-generator -g
# create an Express app named danmaku
$ express danmaku
# install dependencies
$ cd danmaku
$ npm install
# run the app on Windows
$ set DEBUG=danmaku & node .\bin\www
# or
$ npm start
關於set DEBUG=danmaku
可以見此文。可以用npm
start
啟動伺服器是因為在packege.json中有了這麼一段:
"scripts":{"start":"node ./bin/www"}
Express的4比之3,把伺服器配置和伺服器啟動做了分離,原來都在app.js裡,現在將啟動程式碼放到了www中。現在,瀏覽一遍這個Express樣例,對這框架就可以知道個大概了。
- bin:存放啟動專案的指令碼檔案
- node_modules:存放所有的專案依賴庫
- public:靜態檔案(css、js、img等)
- routes:路由檔案(MVC中的C,controller)
- views:頁面檔案(jade或ejs模板)
- package.json:專案依賴配置及開發者資訊
- app.js:應用核心配置檔案
更多參考:
二、路由
將實時彈幕系統實際上是分為三個角色:
- 服務端:監聽客戶端連線、彈幕事件等並響應。
- 發射客戶端:由使用者發射彈幕。以emitCtrl.js作為emit頁面的controller。
- 螢幕客戶端:接收彈幕並顯示。以indexCtrl.js作為index頁面的controller。
新增 routes/indexCtrl.js
var express =require('express');var router = express.Router();/* GET home page. */
router.get('/',function(req, res,next){
res.render('index',{title:"danmaku"});});module.exports = router;
新增 routes/emitCtrl.js
var express =require('express');var router = express.Router();/* GET emit page. */
router.get('/',function(req, res,next){
res.render('emit');});module.exports = router;
修改 app.js
var indexCtrl =require('./routes/indexCtrl');var emitCtrl =require('./routes/emitCtrl');...
app.use('/', indexCtrl);
app.use('/emit', emitCtrl);
啟動後可檢視到index頁面。
在後面還會對emitCtrl.js增加彈幕配置的檔案config.json的讀取。
三、螢幕客戶端
1. 靜態
CommentCoreLibrary是GitHub上開源的JS彈幕模組核心,提供從基本骨架到高階彈幕的支援。
考慮到實際,感覺並不應該引入外部庫。如果作為外部庫用,需要
$ npm install comment-core-library --save
使用時(去除public)
<linkrel="stylesheet"href="/node_modules/comment-core-library/build/style.css"/><scriptsrc="/node_modules/comment-core-library/build/CommentCoreLibrary.js"></script>
所以換種方式,將CommentCoreLibrary.js放入public/javascripts,style.css放入public/stylesheets中。
新增views/index.jade
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
link(rel='stylesheet', href='/stylesheets/index.css')
script(src='/javascripts/CommentCoreLibrary.js')
body
#my-player.abp(style='width:100%; height:600px; background:#000;')#my-comment-stage.container
ul#messages
script(src='http://cdn.bootcss.com/jquery/2.1.3/jquery.js')
script(src='/javascripts/index.js')
新增public/stylesheets/index.css
*{ margin:0; padding:0; box-sizing: border-box;}#messages { list-style-type: none; margin: 0; padding: 0; }#messages li { padding: 5px 10px; }#messages li:nth-child(odd) { background: #eee; }
body {
margin:0px;
padding:0px;
font-family:"Segoe UI","Microsoft Yahei", sans-serif;}
新增public/javascripts/index.js
window.addEventListener('load',function(){// 在窗體載入完畢後再繫結var CM =newCommentManager($('#my-comment-stage'));
CM.init();// 先啟用彈幕播放(之後可以停止)
CM.start();// 開放 CM 物件到全域性這樣就可以在 console 終端裡操控
window.CM = CM;});
然後在Console裡怒射一彈:
var danmaku ={"mode":1,"text":"hello world","stime":0,"size":25,"color":0xff00ff,"dur":10000};
CM.send(danmaku);
不過這其實根本沒用上伺服器,也就是靜態網頁一樣的效果。
2. 動態(服務端)
動態是實現一個真正的“螢幕客戶端”,監聽等待“顯示彈幕”的事件,並實時顯示。
實時彈幕也需要後端伺服器的支援。實時彈幕可以採取Polling(定時讀取)或者 Push Notify(監聽等待)兩個主動和被動模式實現。
WebSocket
是HTML5開始提供的一種在單個
TCP 連線上進行全雙工通訊的協議。既然是雙向通訊,就意味著伺服器端和客戶端可以同時傳送並響應請求,而不再像HTTP的請求和響應。WebSocket通訊協議於2011年被IETF定為標準RFC 6455,WebSocketAPI被W3C定為標準。知乎上關於WebSocket的科普。
Socket.IO
是一個開源的WebSocket庫,它通過Node.js實現WebSocket服務端,同時也提供客戶端JS庫。Socket.IO支援以事件為基礎的實時雙向通訊,它可以工作在任何平臺、瀏覽器或移動裝置。Socket.IO支援4種協議:WebSocket、htmlfile、xhr-polling、jsonp-polling,它會自動根據瀏覽器選擇適合的通訊方式,從而讓開發者可以聚焦到功能的實現而不是平臺的相容性,同時具有不錯的穩定性和效能。p.s.
實際過程中踩到了phpwebsocket的坑。
npm install socket.io --save
修改www(Express4從app.js裡把啟動分出來了)
// Create socket.iovar io =require('socket.io')(server);...// Wait for socket event
io.on('connection',function(socket){
console.log('a user connected');
socket.on('disconnect',function(){
console.log('user disconnected');});
socket.on('danmaku send',function(msg){
console.log('message: '+ msg);
io.emit('danmaku show', msg);});});
修改index.jade
script(src='http://cdn.bootcss.com/socket.io/1.3.2/socket.io.js')
script(src='http://cdn.bootcss.com/jquery/2.1.3/jquery.js')
script(src='/javascripts/index.js')
修改index.js
window.addEventListener('load',function(){// 在窗體載入完畢後再繫結var CM =newCommentManager($('#my-comment-stage'));
CM.init();// 先啟用彈幕播放(之後可以停止)
CM.start();// 開放 CM 物件到全域性這樣就可以在 console 終端裡操控
window.CM = CM;var socket = io();
socket.on('danmaku show',function(msg){
console.log(msg);
$('#messages').append($('<li>').text(msg));var danmaku = JSON.parse(msg);
CM.send(danmaku);});});
這樣就由服務端監聽了“connection”、“disconnect”和“danmaku send”三個事件,特別是在收到“danmaku send”時會發送“danmaku show”事件。而螢幕客戶端監聽“danmaku show”事件,並把傳遞來的彈幕顯示出來。
啟動後開啟index,確實能看到"connection"事件執行的提示。
四、發射客戶端
發射客戶端傳送“danmaku send”事件及彈幕給服務端。
除此之外,在CommentCoreLibrary裡可以對彈幕屬性進行設定,比如文字大小、模式、顏色,將它們的可選值寫成配置檔案,並設定預設值。
新增public/jsons/config.json
{"sizes":[{"size":12,"title":"非常小"},{"size":16,"title":"較小"},{"size":18,"title":"小"},{"size":25,"title":"中"},{"size":36,"title":"大"},{"size":45,"title":"較大"},{"size":64,"title":"非常大"}],"modes":[{"mode":1,"title":"頂端滾動"},{"mode":2,"title":"底端滾動"},{"mode":5,"title":"頂端漸隱"},{"mode":4,"title":"底端漸隱"},{"mode":6,"title":"逆向滾動"}],"colors":[{"color":"000000","title":"黑色"},{"color":"C0C0C0","title":"灰色"},{"color":"ffffff","title":"白色"},{"color":"ff0000","title":"紅色"},{"color":"00ff00","title":"綠色"},{"color":"0000ff","title":"藍色"},{"color":"ffff00","title":"黃色"},{"color":"00ffff","title":"墨綠"},{"color":"ff00ff","title":"洋紅"}],"inits":{"size":3,"mode":0,"color":4}}
修改emitCtrl.js,讀取配置
var fs =require('fs');.../* GET emit page. */