├── README.md ├── app.js ├── bin └── www ├── package.json ├── public ├── javascripts │ ├── CommentCoreLibrary.js │ ├── emit.js │ └── index.js ├── jsons │ └── config.json └── stylesheets │ ├── index.css │ └── style.css ├── routes ├── emitCtrl.js └── indexCtrl.js ├── views ├── emit.jade ├── error.jade ├── index.jade └── layout.jade └── xmpp_client.js /README.md: -------------------------------------------------------------------------------- 1 | #danmaku 2 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var indexCtrl = require('./routes/indexCtrl'); 9 | var emitCtrl = require('./routes/emitCtrl'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'jade'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(__dirname + '/public/favicon.ico')); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | 26 | app.use('/', indexCtrl); 27 | app.use('/emit', emitCtrl); 28 | 29 | // catch 404 and forward to error handler 30 | app.use(function(req, res, next) { 31 | var err = new Error('Not Found'); 32 | err.status = 404; 33 | next(err); 34 | }); 35 | 36 | // error handlers 37 | 38 | // development error handler 39 | // will print stacktrace 40 | if (app.get('env') === 'development') { 41 | app.use(function(err, req, res, next) { 42 | res.status(err.status || 500); 43 | res.render('error', { 44 | message: err.message, 45 | error: err 46 | }); 47 | }); 48 | } 49 | 50 | // production error handler 51 | // no stacktraces leaked to user 52 | app.use(function(err, req, res, next) { 53 | res.status(err.status || 500); 54 | res.render('error', { 55 | message: err.message, 56 | error: {} 57 | }); 58 | }); 59 | 60 | 61 | module.exports = app; 62 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('danmaku:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | // Create socket.io 25 | var io = require('socket.io')(server); 26 | 27 | /** 28 | * Listen on provided port, on all network interfaces. 29 | */ 30 | 31 | server.listen(port); 32 | server.on('error', onError); 33 | server.on('listening', onListening); 34 | 35 | // Wait for socket event 36 | io.on('connection', function(socket){ 37 | console.log('a user connected'); 38 | socket.on('disconnect', function(){ 39 | console.log('user disconnected'); 40 | }); 41 | socket.on('danmaku send', function(msg){ 42 | console.log('message: ' + msg); 43 | io.emit('danmaku show', msg); 44 | }); 45 | }); 46 | 47 | 48 | /** 49 | * Normalize a port into a number, string, or false. 50 | */ 51 | 52 | function normalizePort(val) { 53 | var port = parseInt(val, 10); 54 | 55 | if (isNaN(port)) { 56 | // named pipe 57 | return val; 58 | } 59 | 60 | if (port >= 0) { 61 | // port number 62 | return port; 63 | } 64 | 65 | return false; 66 | } 67 | 68 | /** 69 | * Event listener for HTTP server "error" event. 70 | */ 71 | 72 | function onError(error) { 73 | if (error.syscall !== 'listen') { 74 | throw error; 75 | } 76 | 77 | var bind = typeof port === 'string' 78 | ? 'Pipe ' + port 79 | : 'Port ' + port; 80 | 81 | // handle specific listen errors with friendly messages 82 | switch (error.code) { 83 | case 'EACCES': 84 | console.error(bind + ' requires elevated privileges'); 85 | process.exit(1); 86 | break; 87 | case 'EADDRINUSE': 88 | console.error(bind + ' is already in use'); 89 | process.exit(1); 90 | break; 91 | default: 92 | throw error; 93 | } 94 | } 95 | 96 | /** 97 | * Event listener for HTTP server "listening" event. 98 | */ 99 | 100 | function onListening() { 101 | var addr = server.address(); 102 | var bind = typeof addr === 'string' 103 | ? 'pipe ' + addr 104 | : 'port ' + addr.port; 105 | debug('Listening on ' + bind); 106 | } 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "danmaku", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.12.0", 10 | "cookie-parser": "~1.3.4", 11 | "debug": "~2.1.1", 12 | "express": "~4.12.0", 13 | "jade": "~1.9.2", 14 | "morgan": "~1.5.1", 15 | "serve-favicon": "~2.2.0", 16 | "socket.io": "^1.3.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /public/javascripts/CommentCoreLibrary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Binary Search Stubs for JS Arrays 3 | * @license MIT 4 | * @author Jim Chen 5 | */ 6 | var BinArray = (function(){ 7 | var BinArray = {}; 8 | BinArray.bsearch = function(arr, what, how){ 9 | if(arr.length === 0) { 10 | return 0; 11 | } 12 | if(how(what,arr[0]) < 0) { 13 | return 0; 14 | } 15 | if(how(what,arr[arr.length - 1]) >=0) { 16 | return arr.length; 17 | } 18 | var low =0; 19 | var i = 0; 20 | var count = 0; 21 | var high = arr.length - 1; 22 | while(low<=high){ 23 | i = Math.floor((high + low + 1)/2); 24 | count++; 25 | if(how(what,arr[i-1])>=0 && how(what,arr[i])<0){ 26 | return i; 27 | } 28 | if(how(what,arr[i-1])<0){ 29 | high = i-1; 30 | }else if(how(what,arr[i])>=0){ 31 | low = i; 32 | }else { 33 | console.error('Program Error'); 34 | } 35 | if(count > 1500) { console.error('Too many run cycles.'); } 36 | } 37 | return -1; // Never actually run 38 | }; 39 | BinArray.binsert = function(arr, what, how){ 40 | var index = BinArray.bsearch(arr,what,how); 41 | arr.splice(index,0,what); 42 | return index; 43 | }; 44 | return BinArray; 45 | })(); 46 | 47 | var __extends = this.__extends || function (d, b) { 48 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 49 | function __() { this.constructor = d; } 50 | __.prototype = b.prototype; 51 | d.prototype = new __(); 52 | }; 53 | 54 | var CommentSpaceAllocator = (function () { 55 | function CommentSpaceAllocator(width, height) { 56 | if (typeof width === "undefined") { width = 0; } 57 | if (typeof height === "undefined") { height = 0; } 58 | this._pools = [ 59 | [] 60 | ]; 61 | this.avoid = 1; 62 | this._width = width; 63 | this._height = height; 64 | } 65 | CommentSpaceAllocator.prototype.willCollide = function (existing, check) { 66 | return existing.stime + existing.ttl >= check.stime + check.ttl / 2; 67 | }; 68 | 69 | CommentSpaceAllocator.prototype.pathCheck = function (y, comment, pool) { 70 | var bottom = y + comment.height; 71 | var right = comment.right; 72 | for (var i = 0; i < pool.length; i++) { 73 | if (pool[i].y > bottom || pool[i].bottom < y) { 74 | continue; 75 | } else if (pool[i].right < comment.x || pool[i].x > right) { 76 | if (this.willCollide(pool[i], comment)) { 77 | return false; 78 | } else { 79 | continue; 80 | } 81 | } else { 82 | return false; 83 | } 84 | } 85 | return true; 86 | }; 87 | 88 | CommentSpaceAllocator.prototype.assign = function (comment, cindex) { 89 | while (this._pools.length <= cindex) { 90 | this._pools.push([]); 91 | } 92 | var pool = this._pools[cindex]; 93 | if (pool.length === 0) { 94 | comment.cindex = cindex; 95 | return 0; 96 | } else if (this.pathCheck(0, comment, pool)) { 97 | comment.cindex = cindex; 98 | return 0; 99 | } 100 | var y = 0; 101 | for (var k = 0; k < pool.length; k++) { 102 | y = pool[k].bottom + this.avoid; 103 | if (y + comment.height > this._height) { 104 | break; 105 | } 106 | if (this.pathCheck(y, comment, pool)) { 107 | comment.cindex = cindex; 108 | return y; 109 | } 110 | } 111 | 112 | return this.assign(comment, cindex + 1); 113 | }; 114 | 115 | CommentSpaceAllocator.prototype.add = function (comment) { 116 | if (comment.height > this._height) { 117 | comment.cindex = -2; 118 | comment.y = 0; 119 | } else { 120 | comment.y = this.assign(comment, 0); 121 | BinArray.binsert(this._pools[comment.cindex], comment, function (a, b) { 122 | if (a.bottom < b.bottom) { 123 | return -1; 124 | } else if (a.bottom > b.bottom) { 125 | return 1; 126 | } else { 127 | return 0; 128 | } 129 | }); 130 | } 131 | }; 132 | 133 | CommentSpaceAllocator.prototype.remove = function (comment) { 134 | if (comment.cindex < 0) { 135 | return; 136 | } 137 | if (comment.cindex >= this._pools.length) { 138 | throw new Error("cindex out of bounds"); 139 | } 140 | var index = this._pools[comment.cindex].indexOf(comment); 141 | if (index < 0) 142 | return; 143 | this._pools[comment.cindex].splice(index, 1); 144 | }; 145 | 146 | CommentSpaceAllocator.prototype.setBounds = function (width, height) { 147 | this._width = width; 148 | this._height = height; 149 | }; 150 | return CommentSpaceAllocator; 151 | })(); 152 | 153 | var AnchorCommentSpaceAllocator = (function (_super) { 154 | __extends(AnchorCommentSpaceAllocator, _super); 155 | function AnchorCommentSpaceAllocator() { 156 | _super.apply(this, arguments); 157 | } 158 | AnchorCommentSpaceAllocator.prototype.add = function (comment) { 159 | _super.prototype.add.call(this, comment); 160 | comment.x = (this._width - comment.width) / 2; 161 | }; 162 | 163 | AnchorCommentSpaceAllocator.prototype.willCollide = function (a, b) { 164 | return true; 165 | }; 166 | 167 | AnchorCommentSpaceAllocator.prototype.pathCheck = function (y, comment, pool) { 168 | var bottom = y + comment.height; 169 | for (var i = 0; i < pool.length; i++) { 170 | if (pool[i].y > bottom || pool[i].bottom < y) { 171 | continue; 172 | } else { 173 | return false; 174 | } 175 | } 176 | return true; 177 | }; 178 | return AnchorCommentSpaceAllocator; 179 | })(CommentSpaceAllocator); 180 | 181 | var __extends = this.__extends || function (d, b) { 182 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 183 | function __() { this.constructor = d; } 184 | __.prototype = b.prototype; 185 | d.prototype = new __(); 186 | }; 187 | var CoreComment = (function () { 188 | function CoreComment(parent, init) { 189 | if (typeof init === "undefined") { init = {}; } 190 | this.mode = 1; 191 | this.stime = 0; 192 | this.text = ""; 193 | this.ttl = 4000; 194 | this.dur = 4000; 195 | this.cindex = -1; 196 | this.motion = []; 197 | this.movable = true; 198 | this._alphaMotion = null; 199 | this.absolute = true; 200 | this.align = 0; 201 | this._alpha = 1; 202 | this._size = 25; 203 | this._color = 0xffffff; 204 | this._border = false; 205 | this._shadow = true; 206 | this._font = ""; 207 | if (!parent) { 208 | throw new Error("Comment not bound to comment manager."); 209 | } else { 210 | this.parent = parent; 211 | } 212 | if (init.hasOwnProperty("stime")) { 213 | this.stime = init["stime"]; 214 | } 215 | if (init.hasOwnProperty("mode")) { 216 | this.mode = init["mode"]; 217 | } else { 218 | this.mode = 1; 219 | } 220 | if (init.hasOwnProperty("dur")) { 221 | this.dur = init["dur"]; 222 | this.ttl = this.dur; 223 | } 224 | this.dur *= this.parent.options.global.scale; 225 | this.ttl *= this.parent.options.global.scale; 226 | if (init.hasOwnProperty("text")) { 227 | this.text = init["text"]; 228 | } 229 | if (init.hasOwnProperty("motion")) { 230 | this._motionStart = []; 231 | this._motionEnd = []; 232 | this.motion = init["motion"]; 233 | var head = 0; 234 | for (var i = 0; i < init["motion"].length; i++) { 235 | this._motionStart.push(head); 236 | var maxDur = 0; 237 | for (var k in init["motion"][i]) { 238 | var m = init["motion"][i][k]; 239 | maxDur = Math.max(m.dur, maxDur); 240 | if (m.easing === null || m.easing === undefined) { 241 | init["motion"][i][k]["easing"] = CoreComment.LINEAR; 242 | } 243 | } 244 | head += maxDur; 245 | this._motionEnd.push(head); 246 | } 247 | this._curMotion = 0; 248 | } 249 | if (init.hasOwnProperty("color")) { 250 | this._color = init["color"]; 251 | } 252 | if (init.hasOwnProperty("size")) { 253 | this._size = init["size"]; 254 | } 255 | if (init.hasOwnProperty("border")) { 256 | this._border = init["border"]; 257 | } 258 | if (init.hasOwnProperty("opacity")) { 259 | this._alpha = init["opacity"]; 260 | } 261 | if (init.hasOwnProperty("alpha")) { 262 | this._alphaMotion = init["alpha"]; 263 | } 264 | if (init.hasOwnProperty("font")) { 265 | this._font = init["font"]; 266 | } 267 | if (init.hasOwnProperty("x")) { 268 | this._x = init["x"]; 269 | } 270 | if (init.hasOwnProperty("y")) { 271 | this._y = init["y"]; 272 | } 273 | if (init.hasOwnProperty("shadow")) { 274 | this._shadow = init["shadow"]; 275 | } 276 | if (init.hasOwnProperty("position")) { 277 | if (init["position"] === "relative") { 278 | this.absolute = false; 279 | if (this.mode < 7) { 280 | console.warn("Using relative position for CSA comment."); 281 | } 282 | } 283 | } 284 | } 285 | CoreComment.prototype.init = function (recycle) { 286 | if (typeof recycle === "undefined") { recycle = null; } 287 | if (recycle !== null) { 288 | this.dom = recycle.dom; 289 | } else { 290 | this.dom = document.createElement("div"); 291 | } 292 | this.dom.className = this.parent.options.global.className; 293 | this.dom.appendChild(document.createTextNode(this.text)); 294 | this.dom.textContent = this.text; 295 | this.dom.innerText = this.text; 296 | this.size = this._size; 297 | if (this._color != 0xffffff) { 298 | this.color = this._color; 299 | } 300 | this.shadow = this._shadow; 301 | if (this._border) { 302 | this.border = this._border; 303 | } 304 | if (this._font !== "") { 305 | this.font = this._font; 306 | } 307 | if (this._x !== undefined) { 308 | this.x = this._x; 309 | } 310 | if (this._y !== undefined) { 311 | this.y = this._y; 312 | } 313 | if (this._alpha !== 1 || this.parent.options.global.opacity < 1) { 314 | this.alpha = this._alpha; 315 | } 316 | if (this.motion.length > 0) { 317 | this.animate(); 318 | } 319 | }; 320 | 321 | Object.defineProperty(CoreComment.prototype, "x", { 322 | get: function () { 323 | if (this._x === null || this._x === undefined) { 324 | if (this.align % 2 === 0) { 325 | this._x = this.dom.offsetLeft; 326 | } else { 327 | this._x = this.parent.width - this.dom.offsetLeft - this.width; 328 | } 329 | } 330 | if (!this.absolute) { 331 | return this._x / this.parent.width; 332 | } 333 | return this._x; 334 | }, 335 | set: function (x) { 336 | this._x = x; 337 | if (!this.absolute) { 338 | this._x *= this.parent.width; 339 | } 340 | if (this.align % 2 === 0) { 341 | this.dom.style.left = this._x + "px"; 342 | } else { 343 | this.dom.style.right = this._x + "px"; 344 | } 345 | }, 346 | enumerable: true, 347 | configurable: true 348 | }); 349 | 350 | Object.defineProperty(CoreComment.prototype, "y", { 351 | get: function () { 352 | if (this._y === null || this._y === undefined) { 353 | if (this.align < 2) { 354 | this._y = this.dom.offsetTop; 355 | } else { 356 | this._y = this.parent.height - this.dom.offsetTop - this.height; 357 | } 358 | } 359 | if (!this.absolute) { 360 | return this._y / this.parent.height; 361 | } 362 | return this._y; 363 | }, 364 | set: function (y) { 365 | this._y = y; 366 | if (!this.absolute) { 367 | this._y *= this.parent.height; 368 | } 369 | if (this.align < 2) { 370 | this.dom.style.top = this._y + "px"; 371 | } else { 372 | this.dom.style.bottom = this._y + "px"; 373 | } 374 | }, 375 | enumerable: true, 376 | configurable: true 377 | }); 378 | 379 | Object.defineProperty(CoreComment.prototype, "bottom", { 380 | get: function () { 381 | return this.y + this.height; 382 | }, 383 | enumerable: true, 384 | configurable: true 385 | }); 386 | 387 | Object.defineProperty(CoreComment.prototype, "right", { 388 | get: function () { 389 | return this.x + this.width; 390 | }, 391 | enumerable: true, 392 | configurable: true 393 | }); 394 | 395 | Object.defineProperty(CoreComment.prototype, "width", { 396 | get: function () { 397 | if (this._width === null || this._width === undefined) { 398 | this._width = this.dom.offsetWidth; 399 | } 400 | return this._width; 401 | }, 402 | set: function (w) { 403 | this._width = w; 404 | this.dom.style.width = this._width + "px"; 405 | }, 406 | enumerable: true, 407 | configurable: true 408 | }); 409 | 410 | Object.defineProperty(CoreComment.prototype, "height", { 411 | get: function () { 412 | if (this._height === null || this._height === undefined) { 413 | this._height = this.dom.offsetHeight; 414 | } 415 | return this._height; 416 | }, 417 | set: function (h) { 418 | this._height = h; 419 | this.dom.style.height = this._height + "px"; 420 | }, 421 | enumerable: true, 422 | configurable: true 423 | }); 424 | 425 | Object.defineProperty(CoreComment.prototype, "size", { 426 | get: function () { 427 | return this._size; 428 | }, 429 | set: function (s) { 430 | this._size = s; 431 | this.dom.style.fontSize = this._size + "px"; 432 | }, 433 | enumerable: true, 434 | configurable: true 435 | }); 436 | 437 | Object.defineProperty(CoreComment.prototype, "color", { 438 | get: function () { 439 | return this._color; 440 | }, 441 | set: function (c) { 442 | this._color = c; 443 | var color = c.toString(16); 444 | color = color.length >= 6 ? color : new Array(6 - color.length + 1).join("0") + color; 445 | this.dom.style.color = "#" + color; 446 | if (this._color === 0) { 447 | this.dom.className = this.parent.options.global.className + " rshadow"; 448 | } 449 | }, 450 | enumerable: true, 451 | configurable: true 452 | }); 453 | 454 | Object.defineProperty(CoreComment.prototype, "alpha", { 455 | get: function () { 456 | return this._alpha; 457 | }, 458 | set: function (a) { 459 | this._alpha = a; 460 | this.dom.style.opacity = Math.min(this._alpha, this.parent.options.global.opacity) + ""; 461 | }, 462 | enumerable: true, 463 | configurable: true 464 | }); 465 | 466 | Object.defineProperty(CoreComment.prototype, "border", { 467 | get: function () { 468 | return this._border; 469 | }, 470 | set: function (b) { 471 | this._border = b; 472 | if (this._border) { 473 | this.dom.style.border = "1px solid #00ffff"; 474 | } else { 475 | this.dom.style.border = "none"; 476 | } 477 | }, 478 | enumerable: true, 479 | configurable: true 480 | }); 481 | 482 | Object.defineProperty(CoreComment.prototype, "shadow", { 483 | get: function () { 484 | return this._shadow; 485 | }, 486 | set: function (s) { 487 | this._shadow = s; 488 | if (!this._shadow) { 489 | this.dom.className = this.parent.options.global.className + " noshadow"; 490 | } 491 | }, 492 | enumerable: true, 493 | configurable: true 494 | }); 495 | 496 | Object.defineProperty(CoreComment.prototype, "font", { 497 | get: function () { 498 | return this._font; 499 | }, 500 | set: function (f) { 501 | this._font = f; 502 | if (this._font.length > 0) { 503 | this.dom.style.fontFamily = this._font; 504 | } else { 505 | this.dom.style.fontFamily = ""; 506 | } 507 | }, 508 | enumerable: true, 509 | configurable: true 510 | }); 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | CoreComment.prototype.time = function (time) { 523 | this.ttl -= time; 524 | if (this.ttl < 0) { 525 | this.ttl = 0; 526 | } 527 | if (this.movable) { 528 | this.update(); 529 | } 530 | if (this.ttl <= 0) { 531 | this.finish(); 532 | } 533 | }; 534 | 535 | CoreComment.prototype.update = function () { 536 | this.animate(); 537 | }; 538 | 539 | CoreComment.prototype.invalidate = function () { 540 | this._x = null; 541 | this._y = null; 542 | this._width = null; 543 | this._height = null; 544 | }; 545 | 546 | CoreComment.prototype._execMotion = function (currentMotion, time) { 547 | for (var prop in currentMotion) { 548 | if (currentMotion.hasOwnProperty(prop)) { 549 | var m = currentMotion[prop]; 550 | this[prop] = m.easing(Math.min(Math.max(time - m.delay, 0), m.dur), m.from, m.to - m.from, m.dur); 551 | } 552 | } 553 | }; 554 | 555 | CoreComment.prototype.animate = function () { 556 | if (this._alphaMotion) { 557 | this.alpha = (this.dur - this.ttl) * (this._alphaMotion["to"] - this._alphaMotion["from"]) / this.dur + this._alphaMotion["from"]; 558 | } 559 | if (this.motion.length === 0) { 560 | return; 561 | } 562 | var ttl = Math.max(this.ttl, 0); 563 | var time = (this.dur - ttl) - this._motionStart[this._curMotion]; 564 | this._execMotion(this.motion[this._curMotion], time); 565 | if (this.dur - ttl > this._motionEnd[this._curMotion]) { 566 | this._curMotion++; 567 | if (this._curMotion >= this.motion.length) { 568 | this._curMotion = this.motion.length - 1; 569 | } 570 | return; 571 | } 572 | }; 573 | 574 | CoreComment.prototype.finish = function () { 575 | this.parent.finish(this); 576 | }; 577 | 578 | CoreComment.prototype.toString = function () { 579 | return ["[", this.stime, "|", this.ttl, "/", this.dur, "]", "(", this.mode, ")", this.text].join(""); 580 | }; 581 | CoreComment.LINEAR = function (t, b, c, d) { 582 | return t * c / d + b; 583 | }; 584 | return CoreComment; 585 | })(); 586 | 587 | var ScrollComment = (function (_super) { 588 | __extends(ScrollComment, _super); 589 | function ScrollComment(parent, data) { 590 | _super.call(this, parent, data); 591 | this.dur *= this.parent.options.scroll.scale; 592 | this.ttl *= this.parent.options.scroll.scale; 593 | } 594 | Object.defineProperty(ScrollComment.prototype, "alpha", { 595 | set: function (a) { 596 | this._alpha = a; 597 | this.dom.style.opacity = Math.min(Math.min(this._alpha, this.parent.options.global.opacity), this.parent.options.scroll.opacity) + ""; 598 | }, 599 | enumerable: true, 600 | configurable: true 601 | }); 602 | 603 | ScrollComment.prototype.init = function (recycle) { 604 | if (typeof recycle === "undefined") { recycle = null; } 605 | _super.prototype.init.call(this, recycle); 606 | this.x = this.parent.width; 607 | if (this.parent.options.scroll.opacity < 1) { 608 | this.alpha = this._alpha; 609 | } 610 | this.absolute = true; 611 | }; 612 | 613 | ScrollComment.prototype.update = function () { 614 | this.x = (this.ttl / this.dur) * (this.parent.width + this.width) - this.width; 615 | }; 616 | return ScrollComment; 617 | })(CoreComment); 618 | 619 | /** 620 | * Comment Filters Module Simplified (only supports modifiers & types) 621 | * @license MIT 622 | * @author Jim Chen 623 | */ 624 | function CommentFilter(){ 625 | this.modifiers = []; 626 | this.runtime = null; 627 | this.allowTypes = { 628 | "1":true, 629 | "4":true, 630 | "5":true, 631 | "6":true, 632 | "7":true, 633 | "8":true, 634 | "17":true 635 | }; 636 | this.doModify = function(cmt){ 637 | for(var k=0;k 0) 729 | return; 730 | var lastTPos = new Date().getTime(); 731 | var cmMgr = this; 732 | __timer = window.setInterval(function(){ 733 | var elapsed = new Date().getTime() - lastTPos; 734 | lastTPos = new Date().getTime(); 735 | cmMgr.onTimerEvent(elapsed,cmMgr); 736 | },10); 737 | }; 738 | this.stopTimer = function(){ 739 | window.clearInterval(__timer); 740 | __timer = 0; 741 | }; 742 | } 743 | 744 | /** Public **/ 745 | CommentManager.prototype.stop = function(){ 746 | this.stopTimer(); 747 | }; 748 | 749 | CommentManager.prototype.start = function(){ 750 | this.startTimer(); 751 | }; 752 | 753 | CommentManager.prototype.seek = function(time){ 754 | this.position = BinArray.bsearch(this.timeline, time, function(a,b){ 755 | if(a < b.stime) return -1 756 | else if(a > b.stime) return 1; 757 | else return 0; 758 | }); 759 | }; 760 | 761 | CommentManager.prototype.validate = function(cmt){ 762 | if(cmt == null) 763 | return false; 764 | return this.filter.doValidate(cmt); 765 | }; 766 | 767 | CommentManager.prototype.load = function(a){ 768 | this.timeline = a; 769 | this.timeline.sort(function(a,b){ 770 | if(a.stime > b.stime) return 2; 771 | else if(a.stime < b.stime) return -2; 772 | else{ 773 | if(a.date > b.date) return 1; 774 | else if(a.date < b.date) return -1; 775 | else if(a.dbid != null && b.dbid != null){ 776 | if(a.dbid > b.dbid) return 1; 777 | else if(a.dbid < b.dbid) return -1; 778 | return 0; 779 | }else 780 | return 0; 781 | } 782 | }); 783 | this.dispatchEvent("load"); 784 | }; 785 | 786 | CommentManager.prototype.insert = function(c){ 787 | var index = BinArray.binsert(this.timeline, c, function(a,b){ 788 | if(a.stime > b.stime) return 2; 789 | else if(a.stime < b.stime) return -2; 790 | else{ 791 | if(a.date > b.date) return 1; 792 | else if(a.date < b.date) return -1; 793 | else if(a.dbid != null && b.dbid != null){ 794 | if(a.dbid > b.dbid) return 1; 795 | else if(a.dbid < b.dbid) return -1; 796 | return 0; 797 | }else 798 | return 0; 799 | } 800 | }); 801 | if(index <= this.position){ 802 | this.position++; 803 | } 804 | this.dispatchEvent("insert"); 805 | }; 806 | 807 | CommentManager.prototype.clear = function(){ 808 | while(this.runline.length > 0){ 809 | this.runline[0].finish(); 810 | } 811 | this.dispatchEvent("clear"); 812 | }; 813 | 814 | CommentManager.prototype.setBounds = function(){ 815 | this.width = this.stage.offsetWidth; 816 | this.height= this.stage.offsetHeight; 817 | this.dispatchEvent("resize"); 818 | for(var comAlloc in this.csa){ 819 | this.csa[comAlloc].setBounds(this.width,this.height); 820 | } 821 | // Update 3d perspective 822 | this.stage.style.perspective = this.width * Math.tan(40 * Math.PI/180) / 2 + "px"; 823 | this.stage.style.webkitPerspective = this.width * Math.tan(40 * Math.PI/180) / 2 + "px"; 824 | }; 825 | CommentManager.prototype.init = function(){ 826 | this.setBounds(); 827 | if(this.filter == null) 828 | this.filter = new CommentFilter(); //Only create a filter if none exist 829 | }; 830 | CommentManager.prototype.time = function(time){ 831 | time = time - 1; 832 | if(this.position >= this.timeline.length || Math.abs(this.lastPos - time) >= 2000){ 833 | this.seek(time); 834 | this.lastPos = time; 835 | if(this.timeline.length <= this.position) 836 | return; 837 | }else{ 838 | this.lastPos = time; 839 | } 840 | for(;this.position < this.timeline.length;this.position++){ 841 | if(this.options.limit > 0 && this.runline.length > this.limiter) break; 842 | if(this.validate(this.timeline[this.position]) && this.timeline[this.position]['stime']<=time){ 843 | this.send(this.timeline[this.position]); 844 | }else{ 845 | break; 846 | } 847 | } 848 | }; 849 | CommentManager.prototype.rescale = function(){ 850 | 851 | }; 852 | CommentManager.prototype.send = function(data){ 853 | if(data.mode === 8){ 854 | console.log(data); 855 | if(this.scripting){ 856 | console.log(this.scripting.eval(data.code)); 857 | } 858 | return; 859 | } 860 | if(this.filter != null){ 861 | data = this.filter.doModify(data); 862 | if(data == null) return; 863 | } 864 | if(data.mode === 1 || data.mode === 2 || data.mode === 6){ 865 | var cmt = new ScrollComment(this, data); 866 | }else{ 867 | var cmt = new CoreComment(this, data); 868 | } 869 | switch(cmt.mode){ 870 | case 1:cmt.align = 0;break; 871 | case 2:cmt.align = 2;break; 872 | case 4:cmt.align = 2;break; 873 | case 5:cmt.align = 0;break; 874 | case 6:cmt.align = 1;break; 875 | } 876 | cmt.init(); 877 | this.stage.appendChild(cmt.dom); 878 | switch(cmt.mode){ 879 | default: 880 | case 1:{this.csa.scroll.add(cmt);}break; 881 | case 2:{this.csa.scrollbtm.add(cmt);}break; 882 | case 4:{this.csa.bottom.add(cmt);}break; 883 | case 5:{this.csa.top.add(cmt);}break; 884 | case 6:{this.csa.reverse.add(cmt);}break; 885 | case 17: 886 | case 7:{ 887 | if(data.rY !== 0 || data.rZ !== 0){ 888 | /** TODO: revise when browser manufacturers make up their mind on Transform APIs **/ 889 | cmt.dom.style.transform = getRotMatrix(data.rY, data.rZ); 890 | cmt.dom.style.webkitTransform = getRotMatrix(data.rY, data.rZ); 891 | cmt.dom.style.OTransform = getRotMatrix(data.rY, data.rZ); 892 | cmt.dom.style.MozTransform = getRotMatrix(data.rY, data.rZ); 893 | cmt.dom.style.MSTransform = getRotMatrix(data.rY, data.rZ); 894 | } 895 | }break; 896 | } 897 | cmt.y = cmt.y; 898 | this.dispatchEvent("enterComment", cmt); 899 | this.runline.push(cmt); 900 | }; 901 | CommentManager.prototype.sendComment = function(data){ 902 | console.log("CommentManager.sendComment is deprecated. Please use send instead"); 903 | this.send(data); // Wrapper for Backwards Compatible APIs 904 | }; 905 | CommentManager.prototype.finish = function(cmt){ 906 | this.dispatchEvent("exitComment", cmt); 907 | this.stage.removeChild(cmt.dom); 908 | var index = this.runline.indexOf(cmt); 909 | if(index >= 0){ 910 | this.runline.splice(index, 1); 911 | } 912 | switch(cmt.mode){ 913 | default: 914 | case 1:{this.csa.scroll.remove(cmt);}break; 915 | case 2:{this.csa.scrollbtm.remove(cmt);}break; 916 | case 4:{this.csa.bottom.remove(cmt);}break; 917 | case 5:{this.csa.top.remove(cmt);}break; 918 | case 6:{this.csa.reverse.remove(cmt);}break; 919 | case 7:break; 920 | } 921 | }; 922 | CommentManager.prototype.addEventListener = function(event, listener){ 923 | if(typeof this._listeners[event] !== "undefined"){ 924 | this._listeners[event].push(listener); 925 | }else{ 926 | this._listeners[event] = [listener]; 927 | } 928 | }; 929 | CommentManager.prototype.dispatchEvent = function(event, data){ 930 | if(typeof this._listeners[event] !== "undefined"){ 931 | for(var i = 0; i < this._listeners[event].length; i++){ 932 | try{ 933 | this._listeners[event][i](data); 934 | }catch(e){ 935 | console.err(e.stack); 936 | } 937 | } 938 | } 939 | }; 940 | /** Static Functions **/ 941 | CommentManager.prototype.onTimerEvent = function(timePassed,cmObj){ 942 | for(var i= 0;i < cmObj.runline.length; i++){ 943 | var cmt = cmObj.runline[i]; 944 | if(cmt.hold){ 945 | continue; 946 | } 947 | cmt.time(timePassed); 948 | } 949 | }; 950 | return CommentManager; 951 | })(); 952 | 953 | /** 954 | * AcFun Format Parser 955 | * @license MIT License 956 | * An alternative format comment parser 957 | */ 958 | function AcfunParser(jsond){ 959 | var list = []; 960 | try{ 961 | var jsondt = JSON.parse(jsond); 962 | }catch(e){ 963 | console.log('Error: Could not parse json list!'); 964 | return []; 965 | } 966 | for(var i=0;i 0){ 971 | data.stime = parseFloat(xc[0]) * 1000; 972 | data.color = parseInt(xc[1]) 973 | data.mode = parseInt(xc[2]); 974 | data.size = parseInt(xc[3]); 975 | data.hash = xc[4]; 976 | data.date = parseInt(xc[5]); 977 | data.position = "absolute"; 978 | if(data.mode != 7){ 979 | data.text = jsondt[i].m.replace(/(\/n|\\n|\n|\r\n|\\r)/g,"\n"); 980 | data.text = data.text.replace(/\r/g,"\n"); 981 | data.text = data.text.replace(/\s/g,"\u00a0"); 982 | }else{ 983 | data.text = jsondt[i].m; 984 | } 985 | if(data.mode == 7){ 986 | //High level positioned dm 987 | try{ 988 | var x = JSON.parse(data.text); 989 | }catch(e){ 990 | console.log('[Err] Error parsing internal data for comment'); 991 | console.log('[Dbg] ' + data.text); 992 | continue; 993 | } 994 | data.position = "relative"; 995 | data.text = x.n; /*.replace(/\r/g,"\n");*/ 996 | data.text = data.text.replace(/\ /g,"\u00a0"); 997 | if(x.a != null){ 998 | data.opacity = x.a; 999 | }else{ 1000 | data.opacity = 1; 1001 | } 1002 | if(x.p != null){ 1003 | data.x = x.p.x / 1000; // relative position 1004 | data.y = x.p.y / 1000; 1005 | }else{ 1006 | data.x = 0; 1007 | data.y = 0; 1008 | } 1009 | data.shadow = x.b; 1010 | data.dur = 4000; 1011 | if(x.l != null) 1012 | data.moveDelay = x.l * 1000; 1013 | if(x.z != null && x.z.length > 0){ 1014 | data.movable = true; 1015 | data.motion = []; 1016 | var moveDuration = 0; 1017 | var last = {x:data.x, y:data.y, alpha:data.opacity, color:data.color}; 1018 | for(var m = 0; m < x.z.length; m++){ 1019 | var dur = x.z[m].l != null ? (x.z[m].l * 1000) : 500; 1020 | moveDuration += dur; 1021 | var motion = { 1022 | x:{from:last.x, to:x.z[m].x/1000, dur: dur, delay: 0}, 1023 | y:{from:last.y, to:x.z[m].y/1000, dur: dur, delay: 0} 1024 | }; 1025 | last.x = motion.x.to; 1026 | last.y = motion.y.to; 1027 | if(x.z[m].t !== last.alpha){ 1028 | motion.alpha = {from:last.alpha, to:x.z[m].t, dur: dur, delay: 0}; 1029 | last.alpha = motion.alpha.to; 1030 | } 1031 | if(x.z[m].c != null && x.z[m].c !== last.color){ 1032 | motion.color = {from:last.color, to:x.z[m].c, dur: dur, delay: 0}; 1033 | last.color = motion.color.to; 1034 | } 1035 | data.motion.push(motion); 1036 | } 1037 | data.dur = moveDuration + (data.moveDelay ? data.moveDelay : 0); 1038 | } 1039 | if(x.r != null && x.k != null){ 1040 | data.rX = x.r; 1041 | data.rY = x.k; 1042 | } 1043 | 1044 | } 1045 | list.push(data); 1046 | } 1047 | } 1048 | return list; 1049 | } 1050 | 1051 | /** 1052 | * Bilibili Format Parser 1053 | * @license MIT License 1054 | * Takes in an XMLDoc/LooseXMLDoc and parses that into a Generic Comment List 1055 | **/ 1056 | function BilibiliParser(xmlDoc, text, warn){ 1057 | function format(string){ 1058 | // Format the comment text to be JSON Valid. 1059 | return string.replace(/\t/,"\\t"); 1060 | } 1061 | 1062 | if(xmlDoc !== null){ 1063 | var elems = xmlDoc.getElementsByTagName('d'); 1064 | }else{ 1065 | if(!document || !document.createElement){ 1066 | // Maybe we are in a restricted context? Bail. 1067 | return []; 1068 | } 1069 | if(warn){ 1070 | if(!confirm("XML Parse Error. \n Allow tag soup parsing?\n[WARNING: This is unsafe.]")){ 1071 | return []; 1072 | } 1073 | }else{ 1074 | // TODO: Make this safer in the future 1075 | text = text.replace(new RegExp("= 7){ 1120 | obj.rZ = parseInt(adv[5], 10); 1121 | obj.rY = parseInt(adv[6], 10); 1122 | } 1123 | obj.motion = []; 1124 | obj.movable = false; 1125 | if(adv.length >= 11){ 1126 | obj.movable = true; 1127 | var singleStepDur = 500; 1128 | var motion = { 1129 | x:{from: obj.x, to:parseFloat(adv[7]), dur:singleStepDur, delay:0}, 1130 | y:{from: obj.y, to:parseFloat(adv[8]), dur:singleStepDur, delay:0}, 1131 | }; 1132 | if(adv[9] !== ''){ 1133 | singleStepDur = parseInt(adv[9], 10); 1134 | motion.x.dur = singleStepDur; 1135 | motion.y.dur = singleStepDur; 1136 | } 1137 | if(adv[10] !== ''){ 1138 | motion.x.delay = parseInt(adv[10], 10); 1139 | motion.y.delay = parseInt(adv[10], 10); 1140 | } 1141 | if(adv.length > 11){ 1142 | obj.shadow = adv[11]; 1143 | if(obj.shadow === "true"){ 1144 | obj.shadow = true; 1145 | } 1146 | if(obj.shadow === "false"){ 1147 | obj.shadow = false; 1148 | } 1149 | if(adv[12] != null){ 1150 | obj.font = adv[12]; 1151 | } 1152 | if(adv.length > 14){ 1153 | // Support for Bilibili Advanced Paths 1154 | if(obj.position === "relative"){ 1155 | console.log("Cannot mix relative and absolute positioning"); 1156 | obj.position = "absolute"; 1157 | } 1158 | var path = adv[14]; 1159 | var lastPoint = {x:motion.x.from, y:motion.y.from}; 1160 | var pathMotion = []; 1161 | var regex = new RegExp("([a-zA-Z])\\s*(\\d+)[, ](\\d+)","g"); 1162 | var counts = path.split(/[a-zA-Z]/).length - 1; 1163 | var m = regex.exec(path); 1164 | while(m !== null){ 1165 | switch(m[1]){ 1166 | case "M":{ 1167 | lastPoint.x = parseInt(m[2],10); 1168 | lastPoint.y = parseInt(m[3],10); 1169 | }break; 1170 | case "L":{ 1171 | pathMotion.push({ 1172 | "x":{"from":lastPoint.x, "to":parseInt(m[2],10), "dur": singleStepDur / counts, "delay": 0}, 1173 | "y":{"from":lastPoint.y, "to":parseInt(m[3],10), "dur": singleStepDur / counts, "delay": 0} 1174 | }); 1175 | lastPoint.x = parseInt(m[2],10); 1176 | lastPoint.y = parseInt(m[3],10); 1177 | }break; 1178 | } 1179 | m = regex.exec(path); 1180 | } 1181 | motion = null; 1182 | obj.motion = pathMotion; 1183 | } 1184 | } 1185 | if(motion !== null){ 1186 | obj.motion.push(motion); 1187 | } 1188 | } 1189 | obj.dur = 2500; 1190 | if(adv[3] < 12){ 1191 | obj.dur = adv[3] * 1000; 1192 | } 1193 | var tmp = adv[2].split('-'); 1194 | if(tmp != null && tmp.length>1){ 1195 | var alphaFrom = parseFloat(tmp[0]); 1196 | var alphaTo = parseFloat(tmp[1]); 1197 | obj.opacity = alphaFrom; 1198 | if(alphaFrom !== alphaTo){ 1199 | obj.alpha = {from:alphaFrom, to:alphaTo} 1200 | } 1201 | } 1202 | }catch(e){ 1203 | console.log('[Err] Error occurred in JSON parsing'); 1204 | console.log('[Dbg] ' + text); 1205 | } 1206 | }else if(obj.mode == 8){ 1207 | obj.code = text; //Code comments are special 1208 | } 1209 | } 1210 | if(obj.text != null) 1211 | obj.text = obj.text.replace(/\u25a0/g,"\u2588"); 1212 | tlist.push(obj); 1213 | } 1214 | } 1215 | return tlist; 1216 | } 1217 | -------------------------------------------------------------------------------- /public/javascripts/emit.js: -------------------------------------------------------------------------------- 1 | var socket = io(); 2 | 3 | $('#popupMenu_font a').click(function(e){ 4 | $('#size').text($(e.target).text()).attr("danmaku-size",$(e.target).attr("danmaku-size")); 5 | }); 6 | 7 | $('#popupMenu_mode a').click(function(e){ 8 | $('#mode').text($(e.target).text()).attr("danmaku-mode",$(e.target).attr("danmaku-mode")); 9 | }); 10 | 11 | $('#popupMenu_color a').click(function(e){ 12 | $('#color').text($(e.target).text()).attr("danmaku-color",$(e.target).attr("danmaku-color")); 13 | }); 14 | 15 | $('#btnSend').click(function(e){ 16 | e.preventDefault(); 17 | var danmaku = { 18 | "mode": Number($("#mode").attr("danmaku-mode")), 19 | "text": $('#msg').val(), 20 | "stime":0, 21 | "size": Number($("#size").attr("danmaku-size")), 22 | "color":parseInt($("#color").attr("danmaku-color"),16), 23 | "dur":10000 24 | }; 25 | var msg=JSON.stringify(danmaku); 26 | console.log(msg); 27 | socket.emit('danmaku send',msg); 28 | $('#msg').val(""); 29 | }); -------------------------------------------------------------------------------- /public/javascripts/index.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function () { 2 | // 在窗体载入完毕后再绑定 3 | var CM = new CommentManager($('#my-comment-stage')); 4 | CM.init(); 5 | // 先启用弹幕播放(之后可以停止) 6 | CM.start(); 7 | // 开放 CM 对象到全局这样就可以在 console 终端里操控 8 | window.CM = CM; 9 | 10 | var socket = io(); 11 | socket.on('danmaku show', function (msg) { 12 | console.log(msg); 13 | $('#messages').append($('
  • ').text(msg)); 14 | var danmaku = JSON.parse(msg); 15 | CM.send(danmaku); 16 | }); 17 | }); -------------------------------------------------------------------------------- /public/jsons/config.json: -------------------------------------------------------------------------------- 1 | {"sizes":[{"size":12,"title":"非常小"},{"size":16,"title":"较小"},{"size":18,"title":"小"},{"size":25,"title":"中"},{"size":36,"title":"大"},{"size":45,"title":"较大"},{"size":64,"title":"非常大"}], 2 | 3 | "modes":[{"mode":1,"title":"顶端滚动"},{"mode":2,"title":"底端滚动"},{"mode":5,"title":"顶端渐隐"},{"mode":4,"title":"底端渐隐"},{"mode":6,"title":"逆向滚动"}], 4 | 5 | "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":"洋红"}], 6 | 7 | "inits":{"size":3,"mode":0,"color":4}} -------------------------------------------------------------------------------- /public/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | * { margin: 0; padding: 0; box-sizing: border-box; } 2 | #messages { list-style-type: none; margin: 0; padding: 0; } 3 | #messages li { padding: 5px 10px; } 4 | #messages li:nth-child(odd) { background: #eee; } 5 | body { 6 | margin:0px; 7 | padding:0px; 8 | font-family: "Segoe UI", "Microsoft Yahei", sans-serif; 9 | } -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | .abp{ 2 | position:relative; 3 | } 4 | .abp .container{ 5 | -webkit-transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1); 6 | transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1); 7 | position: absolute; 8 | display: block; 9 | overflow: hidden; 10 | margin: 0; 11 | border: 0; 12 | top: 0; 13 | left: 0; 14 | bottom: 0; 15 | right: 0; 16 | z-index: 9999; 17 | touch-callout: none; 18 | -webkit-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | .abp .container .cmt{ 24 | -webkit-transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1); 25 | transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1); 26 | -webkit-transform-origin: 0% 0%; 27 | -ms-transform-origin: 0% 0%; 28 | transform-origin: 0% 0%; 29 | position: absolute; 30 | padding: 3px 0 0 0; 31 | margin: 0; 32 | color: #fff; 33 | font-family: SimHei, SimSun, Heiti, "MS Mincho", "Meiryo", "Microsoft YaHei", monospace; 34 | font-size: 25px; 35 | text-decoration: none; 36 | text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; 37 | -webkit-text-size-adjust: none; 38 | -ms-text-size-adjust: none; 39 | text-size-adjust: none; 40 | line-height: 100%; 41 | letter-spacing: 0; 42 | word-break: keep-all; 43 | white-space: pre; 44 | 45 | } 46 | .abp .container .cmt.noshadow{ 47 | text-shadow: none; 48 | } 49 | .abp .container .cmt.rshadow{ 50 | text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white; 51 | } 52 | 53 | /** Aliases for Chinese named fonts because they don't work on *nix **/ 54 | @font-face{ 55 | font-family: "\9ED1\4F53"; 56 | src:local('SimHei'); 57 | } 58 | 59 | @font-face{ 60 | font-family: "\5B8B\4F53"; 61 | src:local('SimSun'); 62 | } 63 | 64 | @font-face{ 65 | font-family: "\534E\6587\6977\4F53"; 66 | src:local('SimKai'); 67 | } 68 | 69 | @font-face{ 70 | font-family: "\5E7C\5706"; 71 | src:local('YouYuan'); 72 | } 73 | 74 | @font-face{ 75 | font-family: "\5FAE\8F6F\96C5\9ED1"; 76 | src:local('Microsoft YaHei'); 77 | } 78 | -------------------------------------------------------------------------------- /routes/emitCtrl.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var fs = require('fs'); 3 | var router = express.Router(); 4 | 5 | /* GET emit page. */ 6 | router.get('/', function(req, res, next) { 7 | var config = JSON.parse(fs.readFileSync(__dirname + '/../public/jsons/config.json')); 8 | res.render('emit', { title: 'Emitter', sizes: config.sizes, modes: config.modes, colors: config.colors, inits: config.inits}); 9 | }); 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /routes/indexCtrl.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index',{title:"danmaku"}); 7 | }); 8 | 9 | module.exports = router; -------------------------------------------------------------------------------- /views/emit.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | meta(name='viewport', content='width=device-width, initial-scale=1,maximum-scale=1') 6 | link(rel='stylesheet',href='http://cdn.bootcss.com/jquery-mobile/1.4.3/jquery.mobile.css') 7 | script(src='http://cdn.bootcss.com/socket.io/1.3.2/socket.io.js') 8 | script(src='http://cdn.bootcss.com/jquery/2.1.3/jquery.min.js') 9 | script(src='http://cdn.bootcss.com/jquery-mobile/1.4.3/jquery.mobile.js') 10 | body 11 | div(data-role='page') 12 | div(data-role='content') 13 | div.ui-grid-b 14 | 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 15 | #popupMenu_font(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;') 16 | ul(data-role='listview') 17 | each val, index in sizes 18 | li 19 | a(data-rel='back',danmaku-size=val.size)= val.title 20 | 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 21 | #popupMenu_mode(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;') 22 | 23 | ul(data-role='listview') 24 | each val, index in modes 25 | li 26 | a(data-rel='back',danmaku-mode=val.mode)= val.title 27 | 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 28 | #popupMenu_color(data-role='popup', data-theme='b',data-overlay-theme='b', style='min-width:210px;') 29 | .ui-grid-b 30 | - var i=0; 31 | each val, index in colors 32 | case i++%3 33 | 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 34 | 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 35 | 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 36 | 37 | textarea#msg(placeholder='来一发弹幕~') 38 | button#btnSend 发射 39 | script(src='/javascripts/emit.js') -------------------------------------------------------------------------------- /views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | link(rel='stylesheet', href='/stylesheets/index.css') 7 | script(src='/javascripts/CommentCoreLibrary.js') 8 | body 9 | #my-player.abp(style='width:100%; height:600px; background:#000;') 10 | #my-comment-stage.container 11 | ul#messages 12 | script(src='http://cdn.bootcss.com/socket.io/1.3.2/socket.io.js') 13 | script(src='http://cdn.bootcss.com/jquery/2.1.3/jquery.js') 14 | script(src='/javascripts/index.js') -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /xmpp_client.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict' 3 | 4 | /* 5 | * example usage: 6 | * 7 | * node examples/answer_bot.js me@evilprofessor.co.uk password component.evilprofessor.co.uk 8 | * 9 | * Chats with the example component in example/send_message_component.js 10 | */ 11 | 12 | var Client = require('node-xmpp-client') 13 | 14 | var component = 'ios1@jabbermobile.com' 15 | var client = new Client({ 16 | jid: 'jinren@cisco.com', 17 | password: '****', 18 | host: 'isj3cmx.webexconnect.com', 19 | reconnect: true 20 | }) 21 | 22 | var x = 0 23 | var old = x 24 | var average = 0 25 | 26 | setInterval(function () { 27 | var n = x - old 28 | console.log(n, average) 29 | average = (n + average) * 0.5 30 | old = x 31 | }, 1e3) 32 | 33 | var interval 34 | var firstMessage = true 35 | 36 | var c = 0 37 | client.on('stanza', function (stanza) { 38 | console.log('Received stanza: ', c++, stanza.toString()) 39 | if (stanza.is('message') && stanza.attrs.type === 'chat' && !stanza.getChild('delay')) { 40 | clearInterval(interval) 41 | if (firstMessage) console.log('Someone started chatting …') 42 | firstMessage = false 43 | var i = parseInt(stanza.getChildText('body'), 10) 44 | x = i 45 | var reply = new Client.Stanza('message', { 46 | to: stanza.attrs.from, 47 | from: stanza.attrs.to, 48 | type: 'chat' 49 | }) 50 | reply.c('body').t(isNaN(i) ? 'i can count!' : ('' + (i + 1))) 51 | setTimeout(function () { 52 | client.send(reply) 53 | }, 321) 54 | } 55 | }) 56 | 57 | client.on('online', function () { 58 | console.log('Client is online') 59 | firstMessage = true 60 | client.send('') 61 | interval = setInterval(function () { 62 | if (!firstMessage) return 63 | // firstMessage = false 64 | console.log('Start chatting …') 65 | var reply = new Client.Stanza('message', { 66 | to: component, 67 | type: 'chat' 68 | }) 69 | reply.c('body').t('0') 70 | client.send(reply) 71 | }, 321) 72 | }) 73 | 74 | client.on('offline', function () { 75 | console.log('Client is offline') 76 | }) 77 | 78 | client.on('connect', function () { 79 | console.log('Client is connected') 80 | }) 81 | 82 | client.on('reconnect', function () { 83 | console.log('Client reconnects …') 84 | }) 85 | 86 | client.on('disconnect', function (e) { 87 | console.log('Client is disconnected', client.connection.reconnect, e) 88 | }) 89 | 90 | client.on('error', function (e) { 91 | console.error(e) 92 | process.exit(1) 93 | }) 94 | 95 | process.on('exit', function () { 96 | client.end() 97 | }) --------------------------------------------------------------------------------