1 | (function (global, factory) {
|
2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
3 | typeof define === 'function' && define.amd ? define(factory) :
|
4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Danmaku = factory());
|
5 | }(this, (function () { 'use strict';
|
6 |
|
7 | var transform = (function() {
|
8 | var properties = [
|
9 | 'oTransform',
|
10 | 'msTransform',
|
11 | 'mozTransform',
|
12 | 'webkitTransform',
|
13 | 'transform'
|
14 | ];
|
15 | var style = document.createElement('div').style;
|
16 | for (var i = 0; i < properties.length; i++) {
|
17 |
|
18 | if (properties[i] in style) {
|
19 | return properties[i];
|
20 | }
|
21 | }
|
22 |
|
23 | return 'transform';
|
24 | }());
|
25 |
|
26 | var canvasHeightCache = Object.create(null);
|
27 |
|
28 | function canvasHeight(font, fontSize) {
|
29 | if (canvasHeightCache[font]) {
|
30 | return canvasHeightCache[font];
|
31 | }
|
32 | var height = 12;
|
33 | var regex = /(\d+(?:\.\d+)?)(px|%|em|rem)(?:\s*\/\s*(\d+(?:\.\d+)?)(px|%|em|rem)?)?/;
|
34 | var p = font.match(regex);
|
35 | if (p) {
|
36 | var fs = p[1] * 1 || 10;
|
37 | var fsu = p[2];
|
38 | var lh = p[3] * 1 || 1.2;
|
39 | var lhu = p[4];
|
40 | if (fsu === '%') fs *= fontSize.container / 100;
|
41 | if (fsu === 'em') fs *= fontSize.container;
|
42 | if (fsu === 'rem') fs *= fontSize.root;
|
43 | if (lhu === 'px') height = lh;
|
44 | if (lhu === '%') height = fs * lh / 100;
|
45 | if (lhu === 'em') height = fs * lh;
|
46 | if (lhu === 'rem') height = fontSize.root * lh;
|
47 | if (lhu === undefined) height = fs * lh;
|
48 | }
|
49 | canvasHeightCache[font] = height;
|
50 | return height;
|
51 | }
|
52 |
|
53 | function createCommentCanvas(cmt, fontSize) {
|
54 | if (typeof cmt.render === 'function') {
|
55 | var cvs = cmt.render();
|
56 | if (cvs instanceof HTMLCanvasElement) {
|
57 | cmt.width = cvs.width;
|
58 | cmt.height = cvs.height;
|
59 | return cvs;
|
60 | }
|
61 | }
|
62 | var canvas = document.createElement('canvas');
|
63 | var ctx = canvas.getContext('2d');
|
64 | var style = cmt.style || {};
|
65 | style.font = style.font || '10px sans-serif';
|
66 | style.textBaseline = style.textBaseline || 'bottom';
|
67 | var strokeWidth = style.lineWidth * 1;
|
68 | strokeWidth = (strokeWidth > 0 && strokeWidth !== Infinity)
|
69 | ? Math.ceil(strokeWidth)
|
70 | : !!style.strokeStyle * 1;
|
71 | ctx.font = style.font;
|
72 | cmt.width = cmt.width ||
|
73 | Math.max(1, Math.ceil(ctx.measureText(cmt.text).width) + strokeWidth * 2);
|
74 | cmt.height = cmt.height ||
|
75 | Math.ceil(canvasHeight(style.font, fontSize)) + strokeWidth * 2;
|
76 | canvas.width = cmt.width;
|
77 | canvas.height = cmt.height;
|
78 | for (var key in style) {
|
79 | ctx[key] = style[key];
|
80 | }
|
81 | var baseline = 0;
|
82 | switch (style.textBaseline) {
|
83 | case 'top':
|
84 | case 'hanging':
|
85 | baseline = strokeWidth;
|
86 | break;
|
87 | case 'middle':
|
88 | baseline = cmt.height >> 1;
|
89 | break;
|
90 | default:
|
91 | baseline = cmt.height - strokeWidth;
|
92 | }
|
93 | if (style.strokeStyle) {
|
94 | ctx.strokeText(cmt.text, strokeWidth, baseline);
|
95 | }
|
96 | ctx.fillText(cmt.text, strokeWidth, baseline);
|
97 | return canvas;
|
98 | }
|
99 |
|
100 | function computeFontSize(el) {
|
101 | return window
|
102 | .getComputedStyle(el, null)
|
103 | .getPropertyValue('font-size')
|
104 | .match(/(.+)px/)[1] * 1;
|
105 | }
|
106 |
|
107 | function init(container) {
|
108 | var stage = document.createElement('canvas');
|
109 | stage.context = stage.getContext('2d');
|
110 | stage._fontSize = {
|
111 | root: computeFontSize(document.getElementsByTagName('html')[0]),
|
112 | container: computeFontSize(container)
|
113 | };
|
114 | return stage;
|
115 | }
|
116 |
|
117 | function clear(stage, comments) {
|
118 | stage.context.clearRect(0, 0, stage.width, stage.height);
|
119 |
|
120 | for (var i = 0; i < comments.length; i++) {
|
121 | comments[i].canvas = null;
|
122 | }
|
123 | }
|
124 |
|
125 | function resize() {
|
126 |
|
127 | }
|
128 |
|
129 | function framing(stage) {
|
130 | stage.context.clearRect(0, 0, stage.width, stage.height);
|
131 | }
|
132 |
|
133 | function setup(stage, comments) {
|
134 | for (var i = 0; i < comments.length; i++) {
|
135 | var cmt = comments[i];
|
136 | cmt.canvas = createCommentCanvas(cmt, stage._fontSize);
|
137 | }
|
138 | }
|
139 |
|
140 | function render(stage, cmt) {
|
141 | stage.context.drawImage(cmt.canvas, cmt.x, cmt.y);
|
142 | }
|
143 |
|
144 | function remove(stage, cmt) {
|
145 |
|
146 | cmt.canvas = null;
|
147 | }
|
148 |
|
149 | var canvasEngine = {
|
150 | name: 'canvas',
|
151 | init: init,
|
152 | clear: clear,
|
153 | resize: resize,
|
154 | framing: framing,
|
155 | setup: setup,
|
156 | render: render,
|
157 | remove: remove,
|
158 | };
|
159 |
|
160 |
|
161 | function allocate(cmt) {
|
162 | var that = this;
|
163 | var ct = this.media ? this.media.currentTime : Date.now() / 1000;
|
164 | var pbr = this.media ? this.media.playbackRate : 1;
|
165 | function willCollide(cr, cmt) {
|
166 | if (cmt.mode === 'top' || cmt.mode === 'bottom') {
|
167 | return ct - cr.time < that._.duration;
|
168 | }
|
169 | var crTotalWidth = that._.stage.width + cr.width;
|
170 | var crElapsed = crTotalWidth * (ct - cr.time) * pbr / that._.duration;
|
171 | if (cr.width > crElapsed) {
|
172 | return true;
|
173 | }
|
174 |
|
175 | var crLeftTime = that._.duration + cr.time - ct;
|
176 | var cmtTotalWidth = that._.stage.width + cmt.width;
|
177 | var cmtTime = that.media ? cmt.time : cmt._utc;
|
178 | var cmtElapsed = cmtTotalWidth * (ct - cmtTime) * pbr / that._.duration;
|
179 | var cmtArrival = that._.stage.width - cmtElapsed;
|
180 |
|
181 | var cmtArrivalTime = that._.duration * cmtArrival / (that._.stage.width + cmt.width);
|
182 | return crLeftTime > cmtArrivalTime;
|
183 | }
|
184 | var crs = this._.space[cmt.mode];
|
185 | var last = 0;
|
186 | var curr = 0;
|
187 | for (var i = 1; i < crs.length; i++) {
|
188 | var cr = crs[i];
|
189 | var requiredRange = cmt.height;
|
190 | if (cmt.mode === 'top' || cmt.mode === 'bottom') {
|
191 | requiredRange += cr.height;
|
192 | }
|
193 | if (cr.range - cr.height - crs[last].range >= requiredRange) {
|
194 | curr = i;
|
195 | break;
|
196 | }
|
197 | if (willCollide(cr, cmt)) {
|
198 | last = i;
|
199 | }
|
200 | }
|
201 | var channel = crs[last].range;
|
202 | var crObj = {
|
203 | range: channel + cmt.height,
|
204 | time: this.media ? cmt.time : cmt._utc,
|
205 | width: cmt.width,
|
206 | height: cmt.height
|
207 | };
|
208 | crs.splice(last + 1, curr - last - 1, crObj);
|
209 |
|
210 | if (cmt.mode === 'bottom') {
|
211 | return this._.stage.height - cmt.height - channel % this._.stage.height;
|
212 | }
|
213 | return channel % (this._.stage.height - cmt.height);
|
214 | }
|
215 |
|
216 |
|
217 | function createEngine(framing, setup, render, remove) {
|
218 | return function() {
|
219 | framing(this._.stage);
|
220 | var dn = Date.now() / 1000;
|
221 | var ct = this.media ? this.media.currentTime : dn;
|
222 | var pbr = this.media ? this.media.playbackRate : 1;
|
223 | var cmt = null;
|
224 | var cmtt = 0;
|
225 | var i = 0;
|
226 | for (i = this._.runningList.length - 1; i >= 0; i--) {
|
227 | cmt = this._.runningList[i];
|
228 | cmtt = this.media ? cmt.time : cmt._utc;
|
229 | if (ct - cmtt > this._.duration) {
|
230 | remove(this._.stage, cmt);
|
231 | this._.runningList.splice(i, 1);
|
232 | }
|
233 | }
|
234 | var pendingList = [];
|
235 | while (this._.position < this.comments.length) {
|
236 | cmt = this.comments[this._.position];
|
237 | cmtt = this.media ? cmt.time : cmt._utc;
|
238 | if (cmtt >= ct) {
|
239 | break;
|
240 | }
|
241 |
|
242 |
|
243 |
|
244 | if (ct - cmtt > this._.duration) {
|
245 | ++this._.position;
|
246 | continue;
|
247 | }
|
248 | if (this.media) {
|
249 | cmt._utc = dn - (this.media.currentTime - cmt.time);
|
250 | }
|
251 | pendingList.push(cmt);
|
252 | ++this._.position;
|
253 | }
|
254 | setup(this._.stage, pendingList);
|
255 | for (i = 0; i < pendingList.length; i++) {
|
256 | cmt = pendingList[i];
|
257 | cmt.y = allocate.call(this, cmt);
|
258 | if (cmt.mode === 'top' || cmt.mode === 'bottom') {
|
259 | cmt.x = (this._.stage.width - cmt.width) >> 1;
|
260 | }
|
261 | this._.runningList.push(cmt);
|
262 | }
|
263 | for (i = 0; i < this._.runningList.length; i++) {
|
264 | cmt = this._.runningList[i];
|
265 | var totalWidth = this._.stage.width + cmt.width;
|
266 | var elapsed = totalWidth * (dn - cmt._utc) * pbr / this._.duration;
|
267 | if (cmt.mode === 'ltr') cmt.x = (elapsed - cmt.width + .5) | 0;
|
268 | if (cmt.mode === 'rtl') cmt.x = (this._.stage.width - elapsed + .5) | 0;
|
269 | render(this._.stage, cmt);
|
270 | }
|
271 | };
|
272 | }
|
273 |
|
274 | var raf =
|
275 | window.requestAnimationFrame ||
|
276 | window.mozRequestAnimationFrame ||
|
277 | window.webkitRequestAnimationFrame ||
|
278 | function(cb) {
|
279 | return setTimeout(cb, 50 / 3);
|
280 | };
|
281 |
|
282 | var caf =
|
283 | window.cancelAnimationFrame ||
|
284 | window.mozCancelAnimationFrame ||
|
285 | window.webkitCancelAnimationFrame ||
|
286 | clearTimeout;
|
287 |
|
288 | function binsearch(arr, prop, key) {
|
289 | var mid = 0;
|
290 | var left = 0;
|
291 | var right = arr.length;
|
292 | while (left < right - 1) {
|
293 | mid = (left + right) >> 1;
|
294 | if (key >= arr[mid][prop]) {
|
295 | left = mid;
|
296 | } else {
|
297 | right = mid;
|
298 | }
|
299 | }
|
300 | if (arr[left] && key < arr[left][prop]) {
|
301 | return left;
|
302 | }
|
303 | return right;
|
304 | }
|
305 |
|
306 |
|
307 | function formatMode(mode) {
|
308 | if (!/^(ltr|top|bottom)$/i.test(mode)) {
|
309 | return 'rtl';
|
310 | }
|
311 | return mode.toLowerCase();
|
312 | }
|
313 |
|
314 | function collidableRange() {
|
315 | var max = 9007199254740991;
|
316 | return [
|
317 | { range: 0, time: -max, width: max, height: 0 },
|
318 | { range: max, time: max, width: 0, height: 0 }
|
319 | ];
|
320 | }
|
321 |
|
322 | function resetSpace(space) {
|
323 | space.ltr = collidableRange();
|
324 | space.rtl = collidableRange();
|
325 | space.top = collidableRange();
|
326 | space.bottom = collidableRange();
|
327 | }
|
328 |
|
329 |
|
330 | function play() {
|
331 | if (!this._.visible || !this._.paused) {
|
332 | return this;
|
333 | }
|
334 | this._.paused = false;
|
335 | if (this.media) {
|
336 | for (var i = 0; i < this._.runningList.length; i++) {
|
337 | var cmt = this._.runningList[i];
|
338 | cmt._utc = Date.now() / 1000 - (this.media.currentTime - cmt.time);
|
339 | }
|
340 | }
|
341 | var that = this;
|
342 | var engine = createEngine(
|
343 | this._.engine.framing.bind(this),
|
344 | this._.engine.setup.bind(this),
|
345 | this._.engine.render.bind(this),
|
346 | this._.engine.remove.bind(this)
|
347 | );
|
348 | function frame() {
|
349 | engine.call(that);
|
350 | that._.requestID = raf(frame);
|
351 | }
|
352 | this._.requestID = raf(frame);
|
353 | return this;
|
354 | }
|
355 |
|
356 |
|
357 | function pause() {
|
358 | if (!this._.visible || this._.paused) {
|
359 | return this;
|
360 | }
|
361 | this._.paused = true;
|
362 | caf(this._.requestID);
|
363 | this._.requestID = 0;
|
364 | return this;
|
365 | }
|
366 |
|
367 |
|
368 | function seek() {
|
369 | if (!this.media) {
|
370 | return this;
|
371 | }
|
372 | this.clear();
|
373 | resetSpace(this._.space);
|
374 | var position = binsearch(this.comments, 'time', this.media.currentTime);
|
375 | this._.position = Math.max(0, position - 1);
|
376 | return this;
|
377 | }
|
378 |
|
379 |
|
380 | function bindEvents(_) {
|
381 | _.play = play.bind(this);
|
382 | _.pause = pause.bind(this);
|
383 | _.seeking = seek.bind(this);
|
384 | this.media.addEventListener('play', _.play);
|
385 | this.media.addEventListener('pause', _.pause);
|
386 | this.media.addEventListener('playing', _.play);
|
387 | this.media.addEventListener('waiting', _.pause);
|
388 | this.media.addEventListener('seeking', _.seeking);
|
389 | }
|
390 |
|
391 |
|
392 | function unbindEvents(_) {
|
393 | this.media.removeEventListener('play', _.play);
|
394 | this.media.removeEventListener('pause', _.pause);
|
395 | this.media.removeEventListener('playing', _.play);
|
396 | this.media.removeEventListener('waiting', _.pause);
|
397 | this.media.removeEventListener('seeking', _.seeking);
|
398 | _.play = null;
|
399 | _.pause = null;
|
400 | _.seeking = null;
|
401 | }
|
402 |
|
403 |
|
404 | function init$1(opt) {
|
405 | this._ = {};
|
406 | this.container = opt.container || document.createElement('div');
|
407 | this.media = opt.media;
|
408 | this._.visible = true;
|
409 |
|
410 | {
|
411 | this.engine = 'canvas';
|
412 | this._.engine = canvasEngine;
|
413 | }
|
414 |
|
415 | this._.requestID = 0;
|
416 |
|
417 | this._.speed = Math.max(0, opt.speed) || 144;
|
418 | this._.duration = 4;
|
419 |
|
420 | this.comments = opt.comments || [];
|
421 | this.comments.sort(function(a, b) {
|
422 | return a.time - b.time;
|
423 | });
|
424 | for (var i = 0; i < this.comments.length; i++) {
|
425 | this.comments[i].mode = formatMode(this.comments[i].mode);
|
426 | }
|
427 | this._.runningList = [];
|
428 | this._.position = 0;
|
429 |
|
430 | this._.paused = true;
|
431 | if (this.media) {
|
432 | this._.listener = {};
|
433 | bindEvents.call(this, this._.listener);
|
434 | }
|
435 |
|
436 | this._.stage = this._.engine.init(this.container);
|
437 | this._.stage.style.cssText += 'position:relative;pointer-events:none;';
|
438 |
|
439 | this.resize();
|
440 | this.container.appendChild(this._.stage);
|
441 |
|
442 | this._.space = {};
|
443 | resetSpace(this._.space);
|
444 |
|
445 | if (!this.media || !this.media.paused) {
|
446 | seek.call(this);
|
447 | play.call(this);
|
448 | }
|
449 | return this;
|
450 | }
|
451 |
|
452 |
|
453 | function destroy() {
|
454 | if (!this.container) {
|
455 | return this;
|
456 | }
|
457 |
|
458 | pause.call(this);
|
459 | this.clear();
|
460 | this.container.removeChild(this._.stage);
|
461 | if (this.media) {
|
462 | unbindEvents.call(this, this._.listener);
|
463 | }
|
464 | for (var key in this) {
|
465 |
|
466 | if (Object.prototype.hasOwnProperty.call(this, key)) {
|
467 | this[key] = null;
|
468 | }
|
469 | }
|
470 | return this;
|
471 | }
|
472 |
|
473 | var properties = ['mode', 'time', 'text', 'render', 'style'];
|
474 |
|
475 |
|
476 | function emit(obj) {
|
477 | if (!obj || Object.prototype.toString.call(obj) !== '[object Object]') {
|
478 | return this;
|
479 | }
|
480 | var cmt = {};
|
481 | for (var i = 0; i < properties.length; i++) {
|
482 | if (obj[properties[i]] !== undefined) {
|
483 | cmt[properties[i]] = obj[properties[i]];
|
484 | }
|
485 | }
|
486 | cmt.text = (cmt.text || '').toString();
|
487 | cmt.mode = formatMode(cmt.mode);
|
488 | cmt._utc = Date.now() / 1000;
|
489 | if (this.media) {
|
490 | var position = 0;
|
491 | if (cmt.time === undefined) {
|
492 | cmt.time = this.media.currentTime;
|
493 | position = this._.position;
|
494 | } else {
|
495 | position = binsearch(this.comments, 'time', cmt.time);
|
496 | if (position < this._.position) {
|
497 | this._.position += 1;
|
498 | }
|
499 | }
|
500 | this.comments.splice(position, 0, cmt);
|
501 | } else {
|
502 | this.comments.push(cmt);
|
503 | }
|
504 | return this;
|
505 | }
|
506 |
|
507 |
|
508 | function show() {
|
509 | if (this._.visible) {
|
510 | return this;
|
511 | }
|
512 | this._.visible = true;
|
513 | if (this.media && this.media.paused) {
|
514 | return this;
|
515 | }
|
516 | seek.call(this);
|
517 | play.call(this);
|
518 | return this;
|
519 | }
|
520 |
|
521 |
|
522 | function hide() {
|
523 | if (!this._.visible) {
|
524 | return this;
|
525 | }
|
526 | pause.call(this);
|
527 | this.clear();
|
528 | this._.visible = false;
|
529 | return this;
|
530 | }
|
531 |
|
532 |
|
533 | function clear$1() {
|
534 | this._.engine.clear(this._.stage, this._.runningList);
|
535 | this._.runningList = [];
|
536 | return this;
|
537 | }
|
538 |
|
539 |
|
540 | function resize$1() {
|
541 | this._.stage.width = this.container.offsetWidth;
|
542 | this._.stage.height = this.container.offsetHeight;
|
543 | this._.engine.resize(this._.stage);
|
544 | this._.duration = this._.stage.width / this._.speed;
|
545 | return this;
|
546 | }
|
547 |
|
548 | var speed = {
|
549 | get: function() {
|
550 | return this._.speed;
|
551 | },
|
552 | set: function(s) {
|
553 | if (typeof s !== 'number' ||
|
554 | isNaN(s) ||
|
555 | !isFinite(s) ||
|
556 | s <= 0) {
|
557 | return this._.speed;
|
558 | }
|
559 | this._.speed = s;
|
560 | if (this._.stage.width) {
|
561 | this._.duration = this._.stage.width / s;
|
562 | }
|
563 | return s;
|
564 | }
|
565 | };
|
566 |
|
567 | function Danmaku(opt) {
|
568 | opt && init$1.call(this, opt);
|
569 | }
|
570 | Danmaku.prototype.destroy = function() {
|
571 | return destroy.call(this);
|
572 | };
|
573 | Danmaku.prototype.emit = function(cmt) {
|
574 | return emit.call(this, cmt);
|
575 | };
|
576 | Danmaku.prototype.show = function() {
|
577 | return show.call(this);
|
578 | };
|
579 | Danmaku.prototype.hide = function() {
|
580 | return hide.call(this);
|
581 | };
|
582 | Danmaku.prototype.clear = function() {
|
583 | return clear$1.call(this);
|
584 | };
|
585 | Danmaku.prototype.resize = function() {
|
586 | return resize$1.call(this);
|
587 | };
|
588 | Object.defineProperty(Danmaku.prototype, 'speed', speed);
|
589 |
|
590 | return Danmaku;
|
591 |
|
592 | })));
|