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