看新年晚会的时候,发现最大的乐趣就是微信上墙了,但是量大了要等好久才能看见自己发的,为什么不能是弹幕的形式呢? 发现在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弹幕模块核心,提供从基本骨架到高级弹幕的支持。
<s>考虑到实际,感觉并不应该引入外部库。如果作为外部库用,需要
$ npm install comment-core-library --save
使用时(去除public)
<link rel="stylesheet" href="/node_modules/comment-core-library/build/style.css" />
<script src="/node_modules/comment-core-library/build/CommentCoreLibrary.js"></script>
另外CommentCoreLibrary模块也有点笨重。</s>
所以换种方式,将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 = new CommentManager($('#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. 动态(服务端)
动态是实现一个真正的“屏幕客户端”,监听等待“显示弹幕”的事件,并实时显示。
在CommentCoreLibrary的Doc中有一段:
实时弹幕也需要后端服务器的支持。实时弹幕可以采取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,它会自动根据浏览器选择适合的通讯方式,从而让开发者可以聚焦到功能的实现而不是平台的兼容性,同时具有不错的稳定性和性能。
<s>p.s. 实际过程中踩到了phpwebsocket的坑。</s>
npm install socket.io --save
修改www(Express4从app.js里把启动分出来了)
// Create socket.io
var 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 = new CommentManager($('#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. */
router.get('/', function (req, res, next) {
var config = JSON.parse(fs.readFileSync(__dirname + './../public/jsons/config.json'));
res.render('emit', { title: 'Emitter', sizes: config.sizes, modes: config.modes, colors: config.colors, inits: config.inits});
});
添加views/emit.jade
doctype html
html
head
title= title
meta(name='viewport', content='width=device-width, initial-scale=1,maximum-scale=1')
link(rel='stylesheet',href='http://cdn.bootcss.com/jquery-mobile/1.4.3/jquery.mobile.css')
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.min.js')
script(src='http://cdn.bootcss.com/jquery-mobile/1.4.3/jquery.mobile.js')
body
div(data-role='page')
div(data-role='content')
div.ui-grid-b
a#size.ui-btn.ui-btn-inline.ui-block-a(href='#popupMenu_font', data-rel='popup', data-transition='pop',data-position-to="window",danmaku-size= sizes[inits.size].size )= sizes[inits.size].title
#popupMenu_font(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;')
ul(data-role='listview')
each val, index in sizes
li
a(data-rel='back',danmaku-size=val.size)= val.title
a#mode.ui-btn.ui-btn-inline.ui-block-b(href='#popupMenu_mode', data-rel='popup', data-transition='pop',data-position-to="window",danmaku-mode= modes[inits.mode].mode )= modes[inits.mode].title
#popupMenu_mode(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;')
ul(data-role='listview')
each val, index in modes
li
a(data-rel='back',danmaku-mode=val.mode)= val.title
a#color.ui-btn.ui-btn-inline.ui-block-c(href='#popupMenu_color', data-rel='popup', data-transition='pop',data-position-to="window",danmaku-color= colors[inits.color].color )= colors[inits.color].title
#popupMenu_color(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;')
.ui-grid-b
- var i=0;
each val, index in colors
case i++%3
when 0: a.ui-block-a(data-rel="back", style='background-color:#'+val.color+';min-height:60px;line-height:60px;text-align:center',danmaku-color=val.color)= val.title
when 1: a.ui-block-b(data-rel="back", style='background-color:#'+val.color+';min-height:60px;line-height:60px;text-align:center',danmaku-color=val.color)= val.title
when 2: a.ui-block-c(data-rel="back", style='background-color:#'+val.color+';min-height:60px;line-height:60px;text-align:center',danmaku-color=val.color)= val.title
textarea#msg(placeholder='来一发弹幕~')
button#btnSend 发射
script(src='/javascripts/emit.js')
添加public/javascripts/emit.js
var socket = io();
$('#popupMenu_font a').click(function(e){
$('#size').text($(e.target).text()).attr("danmaku-size",$(e.target).attr("danmaku-size"));
});
$('#popupMenu_mode a').click(function(e){
$('#mode').text($(e.target).text()).attr("danmaku-mode",$(e.target).attr("danmaku-mode"));
});
$('#popupMenu_color a').click(function(e){
$('#color').text($(e.target).text()).attr("danmaku-color",$(e.target).attr("danmaku-color"));
});
$('#btnSend').click(function(e){
e.preventDefault();
var danmaku = {
"mode": Number($("#mode").attr("danmaku-mode")),
"text": $('#msg').val(),
"stime":0,
"size": Number($("#size").attr("danmaku-size")),
"color":parseInt($("#color").attr("danmaku-color"),16),
"dur":10000
};
var msg=JSON.stringify(danmaku);
console.log(msg);
socket.emit('danmaku send',msg);
$('#msg').val("");
});
最后整个效果就是这样啦~ 源码在此:https://github.com/cstackess/danmaku
收藏
我也踩到了phpwebsocket的坑,才转的node.
@hezedu 是的,感觉Socket.IO是一众WebSocket实现中相较出色的
好叼的样子
很不错~~,mark
谢谢支持…… 一般真的需要用才是我做东西的动力……
Gruntfile.coffee怎么不用js文件?
就是为了收藏而注册的,让我看看怎么收藏这篇文章!
mark先
mark
不错,学习了~~
Nice
mark
mark
我就想知道要怎么收藏
mark
mark
有点意思,mark
mark
mark=-=
@zhengyue770 装个evernote剪藏,搞定 :)
mark
来自酷炫的 CNodeMD
mark 自豪地采用 CNodeJS ionic
nodejs 有没有视频直播的方案
@Cococ lihai
楼上挖坟,不过挖的好 先mark
这个处理并发的能力怎么样?
感谢分享
简陋但效果有了 自豪地采用 CNodeJS ionic
mark
自豪地采用 CNodeJS ionic
mark
弹幕的内容,是保存为json格式的文件?
mark
感谢分享!
厉害!!
MARK
能在 windows 平台开发,也是蛮赞的。
mark
mark下
MARK
这个可以收藏了
使用最新的CCL会有个错误 window.addEventListener(‘load’, function () { // 在窗体载入完毕后再绑定 var CM = new CommentManager($(’#my-comment-stage’)); CM.init(); // 先启用弹幕播放(之后可以停止) CM.start(); // 开放 CM 对象到全局这样就可以在 console 终端里操控 window.CM = CM; }); 用JQ选择之后需要再加上.get(0),否则会报错
现在弹幕辣么火。。。