1. 程式人生 > >實時彈幕系統的設計與實現

實時彈幕系統的設計與實現

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。

structure.png

新增 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頁面。index_0.png

在後面還會對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);

不過這其實根本沒用上伺服器,也就是靜態網頁一樣的效果。

index_1.png

2. 動態(服務端)

動態是實現一個真正的“螢幕客戶端”,監聽等待“顯示彈幕”的事件,並實時顯示。

實時彈幕也需要後端伺服器的支援。實時彈幕可以採取Polling(定時讀取)或者 Push Notify(監聽等待)兩個主動和被動模式實現。

WebSocketHTML5開始提供的一種在單個 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"事件執行的提示。index_2.png

四、發射客戶端

發射客戶端傳送“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. */