@xpadev-net/niconicomments 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +12 -4
  2. package/dist/bundle.js +909 -1069
  3. package/package.json +9 -4
package/dist/bundle.js CHANGED
@@ -1,1078 +1,918 @@
1
1
  /*!
2
- niconicomments.js v0.2.2
2
+ niconicomments.js v0.2.3
3
3
  (c) 2021 xpadev-net https://xpadev.net
4
4
  Released under the MIT License.
5
5
  */
6
6
  (function (global, factory) {
7
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
- typeof define === 'function' && define.amd ? define(factory) :
9
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.NiconiComments = factory());
7
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
+ typeof define === 'function' && define.amd ? define(factory) :
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.NiconiComments = factory());
10
10
  })(this, (function () { 'use strict';
11
11
 
12
- class NiconiComments {
13
- /**
14
- * NiconiComments Constructor
15
- * @param {HTMLCanvasElement} canvas - 描画対象のキャンバス
16
- * @param {[]} data - 描画用のコメント
17
- * @param {{useLegacy: boolean, formatted: boolean, video: HTMLVideoElement|null}, showCollision: boolean, showFPS: boolean, showCommentCount: boolean, drawAllImageOnLoad: boolean} options - 細かい設定類
18
- */
19
- constructor(canvas, data, options = {
20
- useLegacy: false,
21
- formatted: false,
22
- video: null,
23
- showCollision: false,
24
- showFPS: false,
25
- showCommentCount: false,
26
- drawAllImageOnLoad: false
27
- }) {
28
- this.canvas = canvas;
29
- this.context = canvas.getContext("2d");
30
- this.context.strokeStyle = "rgba(0,0,0,0.7)";
31
- this.context.textAlign = "start";
32
- this.context.textBaseline = "alphabetic";
33
- this.context.lineWidth = 4;
34
- this.commentYPaddingTop = 0.08;
35
- this.commentYMarginBottom = 0.24;
36
- this.fontSize = {
37
- "small": {
38
- "default": 47,
39
- "resized": 26.1
40
- },
41
- "medium": {
42
- "default": 74,
43
- "resized": 38.7
44
- },
45
- "big": {
46
- "default": 111,
47
- "resized": 61
48
- }
49
- };
50
- this.doubleResizeMaxWidth = {
51
- full: {
52
- legacy: 3020,
53
- default: 3220
54
- },
55
- normal: {
56
- legacy: 2540,
57
- default: 2740
58
- }
59
- };
60
-
61
- if (options.formatted) {
62
- this.data = data;
63
- } else {
64
- this.data = this.parseData(data);
65
- }
66
-
67
- this.video = options.video ? options.video : null;
68
- this.showCollision = options.showCollision;
69
- this.showFPS = options.showFPS;
70
- this.showCommentCount = options.showCommentCount;
71
- this.timeline = {};
72
- this.nicoScripts = {
73
- "reverse": [],
74
- "default": []
75
- };
76
- this.collision_right = {};
77
- this.collision_left = {};
78
- this.collision_ue = {};
79
- this.collision_shita = {};
80
- this.lastVpos = null;
81
- this.useLegacy = options.useLegacy;
82
- this.preRendering(options.drawAllImageOnLoad);
83
- this.fpsCount = 0;
84
- this.fps = 0;
85
- this.fpsClock = setInterval(() => {
86
- this.fps = this.fpsCount * 2;
87
- this.fpsCount = 0;
88
- }, 500);
89
- }
90
- /**
91
- * ニコニコが吐き出したデータを処理しやすいように変換する
92
- * @param {[]} data - ニコニコが吐き出したコメントデータ
93
- * @returns {*[]} - 独自フォーマットのコメントデータ
94
- */
95
-
96
-
97
- parseData(data) {
98
- let data_ = [];
99
-
100
- for (let i = 0; i < data.length; i++) {
101
- for (let key in data[i]) {
102
- let value = data[i][key];
103
-
104
- if (key === "chat" && value["deleted"] !== 1 && !value["content"].startsWith("/")) {
105
- let tmpParam = {
106
- "id": value["no"],
107
- "vpos": value["vpos"],
108
- "content": value["content"],
109
- "date": value["date"],
110
- "date_usec": value["date_usec"],
111
- "owner": !value["user_id"],
112
- "premium": value["premium"] === 1
113
- };
114
-
115
- if (value["mail"]) {
116
- tmpParam["mail"] = value["mail"].split(/[\s ]/g);
117
- } else {
118
- tmpParam["mail"] = [];
119
- }
120
-
121
- data_.push(tmpParam);
122
- }
123
- }
124
- }
125
-
126
- data_.sort((a, b) => {
127
- if (a.vpos < b.vpos) return -1;
128
- if (a.vpos > b.vpos) return 1;
129
- if (a.date < b.date) return -1;
130
- if (a.date > b.date) return 1;
131
- if (a.date_usec < b.date_usec) return -1;
132
- if (a.date_usec > b.date_usec) return 1;
133
- return 0;
134
- });
135
- return data_;
136
- }
137
- /**
138
- * 事前に当たり判定を考慮してコメントの描画場所を決定する
139
- * @param {boolean} drawAll - 読み込み時にすべてのコメント画像を生成する
140
- * ※読み込み時めちゃくちゃ重くなるので途中で絶対にカクついてほしくないという場合以外は非推奨
141
- */
142
-
143
-
144
- preRendering(drawAll) {
145
- this.getFont();
146
- this.getCommentSize();
147
- this.getCommentPos();
148
- this.sortComment();
149
-
150
- if (drawAll) {
151
- for (let i in this.data) {
152
- this.getTextImage(i);
153
- }
154
- }
155
- }
156
- /**
157
- * コマンドをもとに各コメントに適用するフォントを決定する
158
- */
159
-
160
-
161
- getFont() {
162
- for (let i in this.data) {
163
- let comment = this.data[i];
164
- let command = this.parseCommandAndNicoscript(comment);
165
- this.data[i].loc = command.loc;
166
- this.data[i].size = command.size;
167
- this.data[i].fontSize = command.fontSize;
168
- this.data[i].font = command.font;
169
- this.data[i].color = command.color;
170
- this.data[i].full = command.full;
171
- this.data[i].ender = command.ender;
172
- this.data[i]._live = command._live;
173
- this.data[i].long = command.long;
174
- this.data[i].invisible = command.invisible;
175
- this.data[i].content = this.data[i].content.replaceAll("\t", "  ");
176
- }
177
- }
178
- /**
179
- * コメントの描画サイズを計算する
180
- */
181
-
182
-
183
- getCommentSize() {
184
- let tmpData = groupBy(this.data, "font", "fontSize");
185
-
186
- for (let i in tmpData) {
187
- for (let j in tmpData[i]) {
188
- this.context.font = parseFont(i, j, this.useLegacy);
189
-
190
- for (let k in tmpData[i][j]) {
191
- let comment = tmpData[i][j][k];
192
-
193
- if (comment.invisible) {
194
- continue;
195
- }
196
-
197
- let measure = this.measureText(comment);
198
- this.data[comment.index].height = measure.height;
199
- this.data[comment.index].width = measure.width;
200
- this.data[comment.index].width_max = measure.width_max;
201
- this.data[comment.index].width_min = measure.width_min;
202
-
203
- if (measure.resized) {
204
- this.data[comment.index].fontSize = measure.fontSize;
205
- this.context.font = parseFont(i, j, this.useLegacy);
206
- }
207
- }
208
- }
209
- }
210
- }
211
- /**
212
- * 計算された描画サイズをもとに各コメントの配置位置を決定する
213
- */
214
-
215
-
216
- getCommentPos() {
217
- let data = this.data;
218
-
219
- for (let i in data) {
220
- let comment = data[i];
221
-
222
- if (comment.invisible) {
223
- continue;
224
- }
225
-
226
- for (let j = 0; j < 500; j++) {
227
- if (!this.timeline[comment.vpos + j]) {
228
- this.timeline[comment.vpos + j] = [];
229
- }
230
-
231
- if (!this.collision_right[comment.vpos + j]) {
232
- this.collision_right[comment.vpos + j] = [];
233
- }
234
-
235
- if (!this.collision_left[comment.vpos + j]) {
236
- this.collision_left[comment.vpos + j] = [];
237
- }
238
-
239
- if (!this.collision_ue[comment.vpos + j]) {
240
- this.collision_ue[comment.vpos + j] = [];
241
- }
242
-
243
- if (!this.collision_shita[comment.vpos + j]) {
244
- this.collision_shita[comment.vpos + j] = [];
245
- }
246
- }
247
-
248
- if (comment.loc === "naka") {
249
- comment.vpos -= 70;
250
- this.data[i].vpos -= 70;
251
- let posY = 0,
252
- is_break = false,
253
- is_change = true,
254
- count = 0;
255
-
256
- if (1080 < comment.height) {
257
- posY = (comment.height - 1080) / -2;
258
- } else {
259
- while (is_change && count < 10) {
260
- is_change = false;
261
- count++;
262
-
263
- for (let j = 0; j < 500; j++) {
264
- let vpos = comment.vpos + j;
265
- let left_pos = 1920 - (1920 + comment.width_max) * j / 500;
266
-
267
- if (left_pos + comment.width_max >= 1880) {
268
- for (let k in this.collision_right[vpos]) {
269
- let l = this.collision_right[vpos][k];
270
-
271
- if (posY < data[l].posY + data[l].height && posY + comment.height > data[l].posY && data[l].owner === comment.owner) {
272
- if (data[l].posY + data[l].height > posY) {
273
- posY = data[l].posY + data[l].height;
274
- is_change = true;
275
- }
276
-
277
- if (posY + comment.height > 1080) {
278
- if (1080 < comment.height) {
279
- posY = (comment.height - 1080) / -2;
280
- } else {
281
- posY = Math.floor(Math.random() * (1080 - comment.height));
282
- }
283
-
284
- is_break = true;
285
- break;
286
- }
287
- }
288
- }
289
-
290
- if (is_break) {
291
- break;
292
- }
293
- }
294
-
295
- if (left_pos <= 40 && is_break === false) {
296
- for (let k in this.collision_left[vpos]) {
297
- let l = this.collision_left[vpos][k];
298
-
299
- if (posY < data[l].posY + data[l].height && posY + comment.height > data[l].posY && data[l].owner === comment.owner) {
300
- if (data[l].posY + data[l].height > posY) {
301
- posY = data[l].posY + data[l].height;
302
- is_change = true;
303
- }
304
-
305
- if (posY + comment.height > 1080) {
306
- if (1080 < comment.height) {
307
- posY = 0;
308
- } else {
309
- posY = Math.random() * (1080 - comment.height);
310
- }
311
-
312
- is_break = true;
313
- break;
314
- }
315
- }
316
- }
317
-
318
- if (is_break) {
319
- break;
320
- }
321
- }
322
-
323
- if (is_break) {
324
- break;
325
- }
326
- }
327
- }
328
- }
329
-
330
- for (let j = 0; j < 500; j++) {
331
- let vpos = comment.vpos + j;
332
- let left_pos = 1920 - (1920 + comment.width_max) * j / 500;
333
- arrayPush(this.timeline, vpos, i);
334
-
335
- if (left_pos + comment.width_max >= 1880) {
336
- arrayPush(this.collision_right, vpos, i);
337
- }
338
-
339
- if (left_pos <= 40) {
340
- arrayPush(this.collision_left, vpos, i);
341
- }
342
- }
343
-
344
- this.data[i].posY = posY;
345
- } else {
346
- let posY = 0,
347
- is_break = false,
348
- is_change = true,
349
- count = 0,
350
- collision;
351
-
352
- if (comment.loc === "ue") {
353
- collision = this.collision_ue;
354
- } else if (comment.loc === "shita") {
355
- collision = this.collision_shita;
356
- }
357
-
358
- while (is_change && count < 10) {
359
- is_change = false;
360
- count++;
361
-
362
- for (let j = 0; j < 300; j++) {
363
- let vpos = comment.vpos + j;
364
-
365
- for (let k in collision[vpos]) {
366
- let l = collision[vpos][k];
367
-
368
- if (posY < data[l].posY + data[l].height && posY + comment.height > data[l].posY && data[l].owner === comment.owner) {
369
- if (data[l].posY + data[l].height > posY) {
370
- posY = data[l].posY + data[l].height;
371
- is_change = true;
372
- }
373
-
374
- if (posY + comment.height > 1080) {
375
- if (1000 <= comment.height) {
376
- posY = 0;
377
- } else {
378
- posY = Math.floor(Math.random() * (1080 - comment.height));
379
- }
380
-
381
- is_break = true;
382
- break;
383
- }
384
- }
385
- }
386
-
387
- if (is_break) {
388
- break;
389
- }
390
- }
391
- }
392
-
393
- for (let j = 0; j < comment.long; j++) {
394
- let vpos = comment.vpos + j;
395
- arrayPush(this.timeline, vpos, i);
396
-
397
- if (comment.loc === "ue") {
398
- arrayPush(this.collision_ue, vpos, i);
399
- } else {
400
- arrayPush(this.collision_shita, vpos, i);
401
- }
402
- }
403
-
404
- this.data[i].posY = posY;
405
- }
406
- }
407
- }
408
- /**
409
- * 投稿者コメントを前に移動
410
- */
411
-
412
-
413
- sortComment() {
414
- for (let vpos in this.timeline) {
415
- this.timeline[vpos].sort((a, b) => {
416
- const A = this.data[a];
417
- const B = this.data[b];
418
-
419
- if (!A.owner && B.owner) {
420
- return -1;
421
- } else if (A.owner && !B.owner) {
422
- return 1;
423
- } else {
424
- return 0;
425
- }
426
- });
427
- }
428
- }
429
- /**
430
- * context.measureTextの複数行対応版
431
- * 画面外にはみ出すコメントの縮小も行う
432
- * @param comment - 独自フォーマットのコメントデータ
433
- * @returns {{resized: boolean, width: number, width_max: number, fontSize: number, width_min: number, height: number}} - 描画サイズとリサイズの情報
434
- */
435
-
436
-
437
- measureText(comment) {
438
- let width,
439
- width_max,
440
- width_min,
441
- height,
442
- width_arr = [],
443
- lines = comment.content.split("\n");
444
-
445
- if (!comment.resized && !comment.ender) {
446
- if (comment.size === "big" && lines.length > 2) {
447
- comment.fontSize = this.fontSize.big.resized;
448
- comment.resized = true;
449
- comment.tateRisized = true;
450
- this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
451
- } else if (comment.size === "medium" && lines.length > 4) {
452
- comment.fontSize = this.fontSize.medium.resized;
453
- comment.resized = true;
454
- comment.tateRisized = true;
455
- this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
456
- } else if (comment.size === "small" && lines.length > 6) {
457
- comment.fontSize = this.fontSize.small.resized;
458
- comment.resized = true;
459
- comment.tateRisized = true;
460
- this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
461
- }
462
- }
463
-
464
- for (let i = 0; i < lines.length; i++) {
465
- let measure = this.context.measureText(lines[i]);
466
- width_arr.push(measure.width);
467
- }
468
-
469
- width = width_arr.reduce((p, c) => p + c, 0) / width_arr.length;
470
- width_max = Math.max(...width_arr);
471
- width_min = Math.min(...width_arr);
472
- height = comment.fontSize * (1 + this.commentYPaddingTop) * lines.length + this.commentYMarginBottom * comment.fontSize;
473
-
474
- if (comment.loc !== "naka" && !comment.tateRisized) {
475
- if (comment.full && width_max > 1840) {
476
- comment.fontSize -= 1;
477
- comment.resized = true;
478
- comment.yokoResized = true;
479
- this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
480
- return this.measureText(comment);
481
- } else if (!comment.full && width_max > 1440) {
482
- comment.fontSize -= 1;
483
- comment.resized = true;
484
- comment.yokoResized = true;
485
- this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
486
- return this.measureText(comment);
487
- }
488
- } else if (comment.loc !== "naka" && comment.tateRisized && (comment.full && width_max > 1920 || !comment.full && width_max > 1440) && !comment.yokoResized) {
489
- comment.fontSize = this.fontSize[comment.size].default;
490
- comment.resized = true;
491
- comment.yokoResized = true;
492
- this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
493
- return this.measureText(comment);
494
- } else if (comment.loc !== "naka" && comment.tateRisized && comment.yokoResized) {
495
- if (comment.full && width_max > this.doubleResizeMaxWidth.full[this.useLegacy ? "legacy" : "default"]) {
496
- comment.fontSize -= 1;
497
- this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
498
- return this.measureText(comment);
499
- } else if (!comment.full && width_max > this.doubleResizeMaxWidth.normal[this.useLegacy ? "legacy" : "default"]) {
500
- comment.fontSize -= 1.;
501
- this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
502
- return this.measureText(comment);
503
- }
504
- }
505
-
506
- return {
507
- "width": width,
508
- "width_max": width_max,
509
- "width_min": width_min,
510
- "height": height,
511
- "resized": comment.resized,
512
- "fontSize": comment.fontSize
513
- };
514
- }
515
- /**
516
- * コマンドをもとに所定の位置に事前に生成したコメントを表示する
517
- * @param comment - 独自フォーマットのコメントデータ
518
- * @param {number} vpos - 動画の現在位置の100倍 ニコニコから吐き出されるコメントの位置情報は主にこれ
519
- */
520
-
521
-
522
- drawText(comment, vpos) {
523
- let reverse = false;
524
-
525
- for (let i in this.nicoScripts.reverse) {
526
- let range = this.nicoScripts.reverse[i];
527
-
528
- if (range.target === "コメ" && comment.owner || range.target === "投コメ" && !comment.owner) {
529
- break;
530
- }
531
-
532
- if (range.start < vpos && vpos < range.end) {
533
- reverse = true;
534
- }
535
- }
536
-
537
- let posX = (1920 - comment.width_max) / 2,
538
- posY = comment.posY;
539
-
540
- if (comment.loc === "naka") {
541
- if (reverse) {
542
- posX = (1920 + comment.width_max) * (vpos - comment.vpos) / 500 - comment.width_max;
543
- } else {
544
- posX = 1920 - (1920 + comment.width_max) * (vpos - comment.vpos) / 500;
545
- }
546
- } else if (comment.loc === "shita") {
547
- posY = 1080 - comment.posY - comment.height;
548
- }
549
-
550
- this.context.drawImage(comment.image, posX, posY);
551
- }
552
- /**
553
- * drawTextで毎回fill/strokeすると重いので画像化して再利用できるようにする
554
- * @param {number} i - コメントデータのインデックス
555
- */
556
-
557
-
558
- getTextImage(i) {
559
- let value = this.data[i];
560
-
561
- if (value.invisible) {
562
- return;
563
- }
564
-
565
- let image = document.createElement("canvas");
566
- image.width = value.width_max;
567
- image.height = value.height;
568
- let context = image.getContext("2d");
569
- context.strokeStyle = "rgba(0,0,0,0.7)";
570
- context.textAlign = "start";
571
- context.textBaseline = "alphabetic";
572
- context.lineWidth = 4;
573
- context.font = parseFont(value.font, value.fontSize, this.useLegacy);
574
-
575
- if (value._live) {
576
- let rgb = hex2rgb(value.color);
577
- context.fillStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`;
578
- } else {
579
- context.fillStyle = value.color;
580
- }
581
-
582
- if (value.color === "#000000") {
583
- context.strokeStyle = "rgba(255,255,255,0.7)";
584
- }
585
-
586
- if (this.showCollision) {
587
- context.strokeStyle = "rgba(0,255,255,1)";
588
- context.strokeRect(0, 0, value.width_max, value.height);
589
-
590
- if (value.color === "#000000") {
591
- context.strokeStyle = "rgba(255,255,255,0.7)";
592
- } else {
593
- context.strokeStyle = "rgba(0,0,0,0.7)";
594
- }
595
- }
596
-
597
- let lines = value.content.split("\n");
598
-
599
- for (let i in lines) {
600
- let line = lines[i],
601
- posY;
602
- posY = (Number(i) + 1) * value.fontSize * (1 + this.commentYPaddingTop);
603
- context.strokeText(line, 0, posY);
604
- context.fillText(line, 0, posY);
605
-
606
- if (this.showCollision) {
607
- context.strokeStyle = "rgba(255,255,0,0.5)";
608
- context.strokeRect(0, posY, value.width_max, value.fontSize * -1);
609
-
610
- if (value.color === "#000000") {
611
- context.strokeStyle = "rgba(255,255,255,0.7)";
612
- } else {
613
- context.strokeStyle = "rgba(0,0,0,0.7)";
614
- }
615
- }
616
- }
617
-
618
- this.data[i].image = image;
619
- }
620
- /**
621
- * コメントに含まれるコマンドを解釈する
622
- * @param comment- 独自フォーマットのコメントデータ
623
- * @returns {{loc: string|null, size: string|null, color: string|null, fontSize: number|null, ender: boolean, font: string|null, full: boolean, _live: boolean, invisible: boolean, long:number|null}}
624
- */
625
-
626
-
627
- parseCommand(comment) {
628
- let metadata = comment.mail,
629
- loc = null,
630
- size = null,
631
- fontSize = null,
632
- color = null,
633
- font = null,
634
- full = false,
635
- ender = false,
636
- _live = false,
637
- invisible = false,
638
- long = null;
639
-
640
- for (let i in metadata) {
641
- let command = metadata[i].toLowerCase();
642
- const match = command.match(/^@([0-9.]+)/);
643
-
644
- if (match) {
645
- long = match[1];
646
- }
647
-
648
- if (loc === null) {
649
- switch (command) {
650
- case "ue":
651
- loc = "ue";
652
- break;
653
-
654
- case "shita":
655
- loc = "shita";
656
- break;
657
- }
658
- }
659
-
660
- if (size === null) {
661
- switch (command) {
662
- case "big":
663
- size = "big";
664
- fontSize = this.fontSize.big.default;
665
- break;
666
-
667
- case "small":
668
- size = "small";
669
- fontSize = this.fontSize.small.default;
670
- break;
671
- }
672
- }
673
-
674
- if (color === null) {
675
- switch (command) {
676
- case "white":
677
- color = "#FFFFFF";
678
- break;
679
-
680
- case "red":
681
- color = "#FF0000";
682
- break;
683
-
684
- case "pink":
685
- color = "#FF8080";
686
- break;
687
-
688
- case "orange":
689
- color = "#FFC000";
690
- break;
691
-
692
- case "yellow":
693
- color = "#FFFF00";
694
- break;
695
-
696
- case "green":
697
- color = "#00FF00";
698
- break;
699
-
700
- case "cyan":
701
- color = "#00FFFF";
702
- break;
703
-
704
- case "blue":
705
- color = "#0000FF";
706
- break;
707
-
708
- case "purple":
709
- color = "#C000FF";
710
- break;
711
-
712
- case "black":
713
- color = "#000000";
714
- break;
715
-
716
- case "white2":
717
- case "niconicowhite":
718
- color = "#CCCC99";
719
- break;
720
-
721
- case "red2":
722
- case "truered":
723
- color = "#CC0033";
724
- break;
725
-
726
- case "pink2":
727
- color = "#FF33CC";
728
- break;
729
-
730
- case "orange2":
731
- case "passionorange":
732
- color = "#FF6600";
733
- break;
734
-
735
- case "yellow2":
736
- case "madyellow":
737
- color = "#999900";
738
- break;
739
-
740
- case "green2":
741
- case "elementalgreen":
742
- color = "#00CC66";
743
- break;
744
-
745
- case "cyan2":
746
- color = "#00CCCC";
747
- break;
748
-
749
- case "blue2":
750
- case "marineblue":
751
- color = "#3399FF";
752
- break;
753
-
754
- case "purple2":
755
- case "nobleviolet":
756
- color = "#6633CC";
757
- break;
758
-
759
- case "black2":
760
- color = "#666666";
761
- break;
762
-
763
- default:
764
- const match = command.match(/#[0-9a-z]{3,6}/);
765
-
766
- if (match && comment.premium) {
767
- color = match[0].toUpperCase();
768
- }
769
-
770
- break;
771
- }
772
- }
773
-
774
- if (font === null) {
775
- switch (command) {
776
- case "gothic":
777
- font = "gothic";
778
- break;
779
-
780
- case "mincho":
781
- font = "mincho";
782
- break;
783
- }
784
- }
785
-
786
- switch (command) {
787
- case "full":
788
- full = true;
789
- break;
790
-
791
- case "ender":
792
- ender = true;
793
- break;
794
-
795
- case "_live":
796
- _live = true;
797
- break;
798
-
799
- case "invisible":
800
- invisible = true;
801
- break;
802
- }
803
- }
804
-
805
- return {
806
- loc,
807
- size,
808
- fontSize,
809
- color,
810
- font,
811
- full,
812
- ender,
813
- _live,
814
- invisible,
815
- long
816
- };
817
- }
818
-
819
- parseCommandAndNicoscript(comment) {
820
- let data = this.parseCommand(comment),
821
- nicoscript = comment.content.match(/^@(デフォルト|置換|逆|コメント禁止|シーク禁止|ジャンプ)/);
822
-
823
- if (nicoscript) {
824
- switch (nicoscript[1]) {
825
- case "デフォルト":
826
- this.nicoScripts.default.push({
827
- start: comment.vpos,
828
- long: data.long === null ? null : Math.floor(data.long * 100),
829
- color: data.color,
830
- size: data.size,
831
- font: data.font,
832
- loc: data.loc
833
- });
834
- break;
835
-
836
- case "逆":
837
- let reverse = comment.content.match(/^@逆 ?(全|コメ|投コメ)?/);
838
-
839
- if (!reverse[1]) {
840
- reverse[1] = "全";
841
- }
842
-
843
- if (data.long === null) {
844
- data.long = 30;
845
- }
846
-
847
- this.nicoScripts.reverse.push({
848
- "start": comment.vpos,
849
- "end": comment.vpos + data.long * 100,
850
- "target": reverse[1]
851
- });
852
- break;
853
- }
854
-
855
- data.invisible = true;
856
- }
857
-
858
- let color = "#FFFFFF",
859
- size = "medium",
860
- font = "defont",
861
- loc = "naka";
862
-
863
- for (let i in this.nicoScripts.default) {
864
- if (this.nicoScripts.default[i].long !== null && this.nicoScripts.default[i].start + this.nicoScripts.default[i].long < comment.vpos) {
865
- this.nicoScripts.default = this.nicoScripts.default.splice(Number(i), 1);
866
- continue;
867
- }
868
-
869
- if (this.nicoScripts.default[i].loc) {
870
- loc = this.nicoScripts.default[i].loc;
871
- }
872
-
873
- if (this.nicoScripts.default[i].color) {
874
- color = this.nicoScripts.default[i].color;
875
- }
876
-
877
- if (this.nicoScripts.default[i].size) {
878
- size = this.nicoScripts.default[i].size;
879
- }
880
-
881
- if (this.nicoScripts.default[i].font) {
882
- font = this.nicoScripts.default[i].font;
883
- }
884
- }
885
-
886
- if (!data.loc) {
887
- data.loc = loc;
888
- }
889
-
890
- if (!data.color) {
891
- data.color = color;
892
- }
893
-
894
- if (!data.size) {
895
- data.size = size;
896
- data.fontSize = this.fontSize[data.size].default;
897
- }
898
-
899
- if (!data.font) {
900
- data.font = font;
901
- }
902
-
903
- if (data.loc !== "naka") {
904
- if (!data.long) {
905
- data.long = 300;
906
- } else {
907
- data.long = Math.floor(data.long * 100);
908
- }
909
- }
910
-
911
- return data;
912
- }
913
- /**
914
- * キャンバスを描画する
915
- * @param vpos - 動画の現在位置の100倍 ニコニコから吐き出されるコメントの位置情報は主にこれ
916
- */
917
-
918
-
919
- drawCanvas(vpos) {
920
- if (this.lastVpos === vpos) return;
921
- this.lastVpos = vpos;
922
- this.fpsCount++;
923
- this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
924
-
925
- if (this.video) {
926
- let offsetX,
927
- offsetY,
928
- scale,
929
- height = this.canvas.height / this.video.videoHeight,
930
- width = this.canvas.width / this.video.videoWidth;
931
-
932
- if (height > width) {
933
- scale = width;
934
- } else {
935
- scale = height;
936
- }
937
-
938
- offsetX = (this.canvas.width - this.video.videoWidth * scale) * 0.5;
939
- offsetY = (this.canvas.height - this.video.videoHeight * scale) * 0.5;
940
- this.context.drawImage(this.video, offsetX, offsetY, this.video.videoWidth * scale, this.video.videoHeight * scale);
941
- }
942
-
943
- if (this.timeline[vpos]) {
944
- for (let index in this.timeline[vpos]) {
945
- let comment = this.data[this.timeline[vpos][index]];
946
-
947
- if (comment.invisible) {
948
- continue;
949
- }
950
-
951
- if (!comment.image) {
952
- this.getTextImage(this.timeline[vpos][index]);
953
- }
954
-
955
- this.drawText(comment, vpos);
956
- }
957
- }
958
-
959
- if (this.showFPS) {
960
- this.context.font = parseFont("defont", 60, this.useLegacy);
961
- this.context.fillStyle = "#00FF00";
962
- this.context.strokeText("FPS:" + this.fps, 100, 100);
963
- this.context.fillText("FPS:" + this.fps, 100, 100);
964
- }
965
-
966
- if (this.showCommentCount) {
967
- this.context.font = parseFont("defont", 60, this.useLegacy);
968
- this.context.fillStyle = "#00FF00";
969
-
970
- if (this.timeline[vpos]) {
971
- this.context.strokeText("Count:" + this.timeline[vpos].length, 100, 200);
972
- this.context.fillText("Count:" + this.timeline[vpos].length, 100, 200);
973
- } else {
974
- this.context.strokeText("Count:0", 100, 200);
975
- this.context.fillText("Count:0", 100, 200);
976
- }
977
- }
978
- }
979
- /**
980
- * キャンバスを消去する
981
- */
982
-
983
-
984
- clear() {
985
- this.context.clearRect(0, 0, 1920, 1080);
986
- }
987
-
988
- }
989
- /**
990
- * 配列を複数のキーでグループ化する
991
- * @param {{}} array
992
- * @param {string} key
993
- * @param {string} key2
994
- * @returns {{}}
995
- */
996
-
997
-
998
- const groupBy = (array, key, key2) => {
999
- let data = {};
1000
-
1001
- for (let i in array) {
1002
- if (!data[array[i][key]]) {
1003
- data[array[i][key]] = {};
1004
- }
1005
-
1006
- if (!data[array[i][key]][array[i][key2]]) {
1007
- data[array[i][key]][array[i][key2]] = [];
1008
- }
1009
-
1010
- array[i].index = i;
1011
- data[array[i][key]][array[i][key2]].push(array[i]);
1012
- }
1013
-
1014
- return data;
1015
- };
1016
- /**
1017
- * フォント名とサイズをもとにcontextで使えるフォントを生成する
1018
- * @param {string} font
1019
- * @param {number} size
1020
- * @param {boolean} useLegacy
1021
- * @returns {string}
1022
- */
1023
-
1024
-
1025
- const parseFont = (font, size, useLegacy) => {
1026
- switch (font) {
1027
- case "gothic":
1028
- return `normal 400 ${size}px "游ゴシック体", "游ゴシック", "Yu Gothic", YuGothic, yugothic, YuGo-Medium`;
1029
-
1030
- case "mincho":
1031
- return `normal 400 ${size}px "游明朝体", "游明朝", "Yu Mincho", YuMincho, yumincho, YuMin-Medium`;
1032
-
1033
- default:
1034
- if (useLegacy) {
1035
- return `normal 600 ${size}px Arial, "MS Pゴシック", "MS PGothic", MSPGothic, MS-PGothic`;
1036
- } else {
1037
- return `normal 600 ${size}px sans-serif, Arial, "MS Pゴシック", "MS PGothic", MSPGothic, MS-PGothic`;
1038
- }
1039
-
1040
- }
1041
- };
1042
- /**
1043
- * phpのarray_push的なあれ
1044
- * @param array
1045
- * @param {string} key
1046
- * @param push
1047
- */
1048
-
1049
-
1050
- const arrayPush = (array, key, push) => {
1051
- if (!array) {
1052
- array = {};
1053
- }
1054
-
1055
- if (!array[key]) {
1056
- array[key] = [];
1057
- }
1058
-
1059
- array[key].push(push);
1060
- };
1061
- /**
1062
- * Hexからrgbに変換する(_live用)
1063
- * @param {string} hex
1064
- * @return {array} RGB
1065
- */
1066
-
1067
-
1068
- const hex2rgb = hex => {
1069
- if (hex.slice(0, 1) === "#") hex = hex.slice(1);
1070
- if (hex.length === 3) hex = hex.slice(0, 1) + hex.slice(0, 1) + hex.slice(1, 2) + hex.slice(1, 2) + hex.slice(2, 3) + hex.slice(2, 3);
1071
- return [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map(function (str) {
1072
- return parseInt(str, 16);
1073
- });
1074
- };
1075
-
1076
- return NiconiComments;
12
+ /*! *****************************************************************************
13
+ Copyright (c) Microsoft Corporation.
14
+
15
+ Permission to use, copy, modify, and/or distribute this software for any
16
+ purpose with or without fee is hereby granted.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
19
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
20
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
21
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
22
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
23
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
24
+ PERFORMANCE OF THIS SOFTWARE.
25
+ ***************************************************************************** */
26
+ var __assign = function () {
27
+ __assign = Object.assign || function __assign(t) {
28
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
29
+ s = arguments[i];
30
+
31
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
32
+ }
33
+
34
+ return t;
35
+ };
36
+
37
+ return __assign.apply(this, arguments);
38
+ };
39
+
40
+ var NiconiComments = (function () {
41
+ function NiconiComments(canvas, data, options) {
42
+ if (options === void 0) { options = {
43
+ useLegacy: false,
44
+ formatted: false,
45
+ video: null,
46
+ showCollision: false,
47
+ showFPS: false,
48
+ showCommentCount: false,
49
+ drawAllImageOnLoad: false
50
+ }; }
51
+ var _this = this;
52
+ this.canvas = canvas;
53
+ this.context = canvas.getContext("2d");
54
+ this.context.strokeStyle = "rgba(0,0,0,0.7)";
55
+ this.context.textAlign = "start";
56
+ this.context.textBaseline = "alphabetic";
57
+ this.context.lineWidth = 4;
58
+ this.commentYPaddingTop = 0.08;
59
+ this.commentYMarginBottom = 0.24;
60
+ this.fontSize = {
61
+ "small": {
62
+ "default": 47,
63
+ "resized": 26.1
64
+ },
65
+ "medium": {
66
+ "default": 74,
67
+ "resized": 38.7
68
+ },
69
+ "big": {
70
+ "default": 111,
71
+ "resized": 61
72
+ }
73
+ };
74
+ this.doubleResizeMaxWidth = {
75
+ full: {
76
+ legacy: 3020,
77
+ "default": 3220
78
+ },
79
+ normal: {
80
+ legacy: 2540,
81
+ "default": 2740
82
+ }
83
+ };
84
+ var parsedData = options.formatted ? data : this.parseData(data);
85
+ this.video = options.video ? options.video : null;
86
+ this.showCollision = options.showCollision;
87
+ this.showFPS = options.showFPS;
88
+ this.showCommentCount = options.showCommentCount;
89
+ this.timeline = {};
90
+ this.nicoScripts = { reverse: [], "default": [], replace: [], ban: [] };
91
+ this.collision_right = {};
92
+ this.collision_left = {};
93
+ this.collision_ue = {};
94
+ this.collision_shita = {};
95
+ this.lastVpos = -1;
96
+ this.useLegacy = options.useLegacy;
97
+ this.preRendering(parsedData, options.drawAllImageOnLoad);
98
+ this.fpsCount = 0;
99
+ this.fps = 0;
100
+ this.fpsClock = window.setInterval(function () {
101
+ _this.fps = _this.fpsCount * 2;
102
+ _this.fpsCount = 0;
103
+ }, 500);
104
+ }
105
+ NiconiComments.prototype.parseData = function (data) {
106
+ var data_ = [];
107
+ for (var i = 0; i < data.length; i++) {
108
+ for (var key in data[i]) {
109
+ var value = data[i][key];
110
+ if (key === "chat" && value["deleted"] !== 1) {
111
+ var tmpParam = {
112
+ "id": value["no"],
113
+ "vpos": value["vpos"],
114
+ "content": value["content"],
115
+ "date": value["date"],
116
+ "date_usec": value["date_usec"],
117
+ "owner": !value["user_id"],
118
+ "premium": value["premium"] === 1,
119
+ "mail": []
120
+ };
121
+ if (value["mail"]) {
122
+ tmpParam["mail"] = value["mail"].split(/[\s ]/g);
123
+ }
124
+ if (value["content"].startsWith("/") && !value["user_id"]) {
125
+ tmpParam["mail"].push("invisible");
126
+ }
127
+ data_.push(tmpParam);
128
+ }
129
+ }
130
+ }
131
+ data_.sort(function (a, b) {
132
+ if (a.vpos < b.vpos)
133
+ return -1;
134
+ if (a.vpos > b.vpos)
135
+ return 1;
136
+ if (a.date < b.date)
137
+ return -1;
138
+ if (a.date > b.date)
139
+ return 1;
140
+ if (a.date_usec < b.date_usec)
141
+ return -1;
142
+ if (a.date_usec > b.date_usec)
143
+ return 1;
144
+ return 0;
145
+ });
146
+ return data_;
147
+ };
148
+ NiconiComments.prototype.preRendering = function (rawData, drawAll) {
149
+ var parsedData = this.getCommentPos(this.getCommentSize(this.getFont(rawData)));
150
+ this.data = this.sortComment(parsedData);
151
+ if (drawAll) {
152
+ for (var i in parsedData) {
153
+ this.getTextImage(Number(i));
154
+ }
155
+ }
156
+ };
157
+ NiconiComments.prototype.getFont = function (parsedData) {
158
+ var result = [];
159
+ for (var i in parsedData) {
160
+ result[i] = this.parseCommandAndNicoscript(parsedData[i]);
161
+ }
162
+ return result;
163
+ };
164
+ NiconiComments.prototype.getCommentSize = function (parsedData) {
165
+ var tmpData = groupBy(parsedData, "font", "fontSize");
166
+ var result = [];
167
+ for (var i in tmpData) {
168
+ for (var j in tmpData[i]) {
169
+ this.context.font = parseFont(i, j, this.useLegacy);
170
+ for (var k in tmpData[i][j]) {
171
+ var comment = tmpData[i][j][k];
172
+ if (comment.invisible) {
173
+ continue;
174
+ }
175
+ var measure = this.measureText(comment);
176
+ result[comment.index] = parsedData[comment.index];
177
+ result[comment.index].height = measure.height;
178
+ result[comment.index].width = measure.width;
179
+ result[comment.index].width_max = measure.width_max;
180
+ result[comment.index].width_min = measure.width_min;
181
+ if (measure.resized) {
182
+ result[comment.index].fontSize = measure.fontSize;
183
+ this.context.font = parseFont(i, j, this.useLegacy);
184
+ }
185
+ }
186
+ }
187
+ }
188
+ return result;
189
+ };
190
+ NiconiComments.prototype.getCommentPos = function (parsedData) {
191
+ var data = parsedData;
192
+ for (var i in data) {
193
+ var comment = data[i];
194
+ if (comment.invisible) {
195
+ continue;
196
+ }
197
+ for (var j = 0; j < 500; j++) {
198
+ if (!this.timeline[comment.vpos + j]) {
199
+ this.timeline[comment.vpos + j] = [];
200
+ }
201
+ if (!this.collision_right[comment.vpos + j]) {
202
+ this.collision_right[comment.vpos + j] = [];
203
+ }
204
+ if (!this.collision_left[comment.vpos + j]) {
205
+ this.collision_left[comment.vpos + j] = [];
206
+ }
207
+ if (!this.collision_ue[comment.vpos + j]) {
208
+ this.collision_ue[comment.vpos + j] = [];
209
+ }
210
+ if (!this.collision_shita[comment.vpos + j]) {
211
+ this.collision_shita[comment.vpos + j] = [];
212
+ }
213
+ }
214
+ if (comment.loc === "naka") {
215
+ comment.vpos -= 70;
216
+ parsedData[i].vpos -= 70;
217
+ var posY = 0, is_break = false, is_change = true, count = 0;
218
+ if (1080 < comment.height) {
219
+ posY = (comment.height - 1080) / -2;
220
+ }
221
+ else {
222
+ while (is_change && count < 10) {
223
+ is_change = false;
224
+ count++;
225
+ for (var j = 0; j < 500; j++) {
226
+ var vpos = comment.vpos + j;
227
+ var left_pos = 1920 - ((1920 + comment.width_max) * j / 500);
228
+ if (left_pos + comment.width_max >= 1880) {
229
+ for (var k in this.collision_right[vpos]) {
230
+ var l = this.collision_right[vpos][k];
231
+ if ((posY < data[l].posY + data[l].height && posY + comment.height > data[l].posY) && data[l].owner === comment.owner) {
232
+ if (data[l].posY + data[l].height > posY) {
233
+ posY = data[l].posY + data[l].height;
234
+ is_change = true;
235
+ }
236
+ if (posY + comment.height > 1080) {
237
+ if (1080 < comment.height) {
238
+ posY = (comment.height - 1080) / -2;
239
+ }
240
+ else {
241
+ posY = Math.floor(Math.random() * (1080 - comment.height));
242
+ }
243
+ is_break = true;
244
+ break;
245
+ }
246
+ }
247
+ }
248
+ if (is_break) {
249
+ break;
250
+ }
251
+ }
252
+ if (left_pos <= 40 && is_break === false) {
253
+ for (var k in this.collision_left[vpos]) {
254
+ var l = this.collision_left[vpos][k];
255
+ if ((posY < data[l].posY + data[l].height && posY + comment.height > data[l].posY) && data[l].owner === comment.owner) {
256
+ if (data[l].posY + data[l].height > posY) {
257
+ posY = data[l].posY + data[l].height;
258
+ is_change = true;
259
+ }
260
+ if (posY + comment.height > 1080) {
261
+ if (1080 < comment.height) {
262
+ posY = 0;
263
+ }
264
+ else {
265
+ posY = Math.random() * (1080 - comment.height);
266
+ }
267
+ is_break = true;
268
+ break;
269
+ }
270
+ }
271
+ }
272
+ if (is_break) {
273
+ break;
274
+ }
275
+ }
276
+ if (is_break) {
277
+ break;
278
+ }
279
+ }
280
+ }
281
+ }
282
+ for (var j = 0; j < 500; j++) {
283
+ var vpos = comment.vpos + j;
284
+ var left_pos = 1920 - ((1920 + comment.width_max) * j / 500);
285
+ arrayPush(this.timeline, vpos, i);
286
+ if (left_pos + comment.width_max >= 1880) {
287
+ arrayPush(this.collision_right, vpos, i);
288
+ }
289
+ if (left_pos <= 40) {
290
+ arrayPush(this.collision_left, vpos, i);
291
+ }
292
+ }
293
+ parsedData[i].posY = posY;
294
+ }
295
+ else {
296
+ var posY = 0, is_break = false, is_change = true, count = 0, collision = void 0;
297
+ if (comment.loc === "ue") {
298
+ collision = this.collision_ue;
299
+ }
300
+ else if (comment.loc === "shita") {
301
+ collision = this.collision_shita;
302
+ }
303
+ while (is_change && count < 10) {
304
+ is_change = false;
305
+ count++;
306
+ for (var j = 0; j < 300; j++) {
307
+ var vpos = comment.vpos + j;
308
+ for (var k in collision[vpos]) {
309
+ var l = collision[vpos][k];
310
+ if ((posY < data[l].posY + data[l].height && posY + comment.height > data[l].posY) && data[l].owner === comment.owner) {
311
+ if (data[l].posY + data[l].height > posY) {
312
+ posY = data[l].posY + data[l].height;
313
+ is_change = true;
314
+ }
315
+ if (posY + comment.height > 1080) {
316
+ if (1000 <= comment.height) {
317
+ posY = 0;
318
+ }
319
+ else {
320
+ posY = Math.floor(Math.random() * (1080 - comment.height));
321
+ }
322
+ is_break = true;
323
+ break;
324
+ }
325
+ }
326
+ }
327
+ if (is_break) {
328
+ break;
329
+ }
330
+ }
331
+ }
332
+ for (var j = 0; j < comment.long; j++) {
333
+ var vpos = comment.vpos + j;
334
+ arrayPush(this.timeline, vpos, i);
335
+ if (comment.loc === "ue") {
336
+ arrayPush(this.collision_ue, vpos, i);
337
+ }
338
+ else {
339
+ arrayPush(this.collision_shita, vpos, i);
340
+ }
341
+ }
342
+ parsedData[i].posY = posY;
343
+ }
344
+ }
345
+ return parsedData;
346
+ };
347
+ NiconiComments.prototype.sortComment = function (parsedData) {
348
+ for (var vpos in this.timeline) {
349
+ this.timeline[vpos].sort(function (a, b) {
350
+ var A = parsedData[a];
351
+ var B = parsedData[b];
352
+ if (!A.owner && B.owner) {
353
+ return -1;
354
+ }
355
+ else if (A.owner && !B.owner) {
356
+ return 1;
357
+ }
358
+ else {
359
+ return 0;
360
+ }
361
+ });
362
+ }
363
+ return parsedData;
364
+ };
365
+ NiconiComments.prototype.measureText = function (comment) {
366
+ var width, width_max, width_min, height, width_arr = [], lines = comment.content.split("\n");
367
+ if (!comment.resized && !comment.ender) {
368
+ if (comment.size === "big" && lines.length > 2) {
369
+ comment.fontSize = this.fontSize.big.resized;
370
+ comment.resized = true;
371
+ comment.tateRisized = true;
372
+ this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
373
+ }
374
+ else if (comment.size === "medium" && lines.length > 4) {
375
+ comment.fontSize = this.fontSize.medium.resized;
376
+ comment.resized = true;
377
+ comment.tateRisized = true;
378
+ this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
379
+ }
380
+ else if (comment.size === "small" && lines.length > 6) {
381
+ comment.fontSize = this.fontSize.small.resized;
382
+ comment.resized = true;
383
+ comment.tateRisized = true;
384
+ this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
385
+ }
386
+ }
387
+ for (var i = 0; i < lines.length; i++) {
388
+ var measure = this.context.measureText(lines[i]);
389
+ width_arr.push(measure.width);
390
+ }
391
+ width = width_arr.reduce(function (p, c) { return p + c; }, 0) / width_arr.length;
392
+ width_max = Math.max.apply(Math, width_arr);
393
+ width_min = Math.min.apply(Math, width_arr);
394
+ height = (comment.fontSize * (1 + this.commentYPaddingTop) * lines.length) + (this.commentYMarginBottom * comment.fontSize);
395
+ if (comment.loc !== "naka" && !comment.tateRisized) {
396
+ if (comment.full && width_max > 1840) {
397
+ comment.fontSize -= 1;
398
+ comment.resized = true;
399
+ comment.yokoResized = true;
400
+ this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
401
+ return this.measureText(comment);
402
+ }
403
+ else if (!comment.full && width_max > 1440) {
404
+ comment.fontSize -= 1;
405
+ comment.resized = true;
406
+ comment.yokoResized = true;
407
+ this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
408
+ return this.measureText(comment);
409
+ }
410
+ }
411
+ else if (comment.loc !== "naka" && comment.tateRisized && (comment.full && width_max > 1920 || !comment.full && width_max > 1440) && !comment.yokoResized) {
412
+ comment.fontSize = this.fontSize[comment.size]["default"];
413
+ comment.resized = true;
414
+ comment.yokoResized = true;
415
+ this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
416
+ return this.measureText(comment);
417
+ }
418
+ else if (comment.loc !== "naka" && comment.tateRisized && comment.yokoResized) {
419
+ if (comment.full && width_max > this.doubleResizeMaxWidth.full[this.useLegacy ? "legacy" : "default"]) {
420
+ comment.fontSize -= 1;
421
+ this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
422
+ return this.measureText(comment);
423
+ }
424
+ else if (!comment.full && width_max > this.doubleResizeMaxWidth.normal[this.useLegacy ? "legacy" : "default"]) {
425
+ comment.fontSize -= 1.;
426
+ this.context.font = parseFont(comment.font, comment.fontSize, this.useLegacy);
427
+ return this.measureText(comment);
428
+ }
429
+ }
430
+ return {
431
+ "width": width,
432
+ "width_max": width_max,
433
+ "width_min": width_min,
434
+ "height": height,
435
+ "resized": comment.resized,
436
+ "fontSize": comment.fontSize
437
+ };
438
+ };
439
+ NiconiComments.prototype.drawText = function (comment, vpos) {
440
+ var reverse = false;
441
+ for (var i in this.nicoScripts.reverse) {
442
+ var range = this.nicoScripts.reverse[i];
443
+ if ((range.target === "コメ" && comment.owner) || (range.target === "投コメ" && !comment.owner)) {
444
+ break;
445
+ }
446
+ if (range.start < vpos && vpos < range.end) {
447
+ reverse = true;
448
+ }
449
+ }
450
+ for (var i in this.nicoScripts.ban) {
451
+ var range = this.nicoScripts.ban[i];
452
+ if (range.start < vpos && vpos < range.end) {
453
+ return;
454
+ }
455
+ }
456
+ var posX = (1920 - comment.width_max) / 2, posY = comment.posY;
457
+ if (comment.loc === "naka") {
458
+ if (reverse) {
459
+ posX = ((1920 + comment.width_max) * (vpos - comment.vpos) / 500) - comment.width_max;
460
+ }
461
+ else {
462
+ posX = 1920 - ((1920 + comment.width_max) * (vpos - comment.vpos) / 500);
463
+ }
464
+ }
465
+ else if (comment.loc === "shita") {
466
+ posY = 1080 - comment.posY - comment.height;
467
+ }
468
+ this.context.drawImage(comment.image, posX, posY);
469
+ };
470
+ NiconiComments.prototype.getTextImage = function (i) {
471
+ var value = this.data[i];
472
+ if (value.invisible) {
473
+ return;
474
+ }
475
+ var image = document.createElement("canvas");
476
+ image.width = value.width_max;
477
+ image.height = value.height;
478
+ var context = image.getContext("2d");
479
+ context.strokeStyle = "rgba(0,0,0,0.7)";
480
+ context.textAlign = "start";
481
+ context.textBaseline = "alphabetic";
482
+ context.lineWidth = 4;
483
+ context.font = parseFont(value.font, value.fontSize, this.useLegacy);
484
+ if (value._live) {
485
+ var rgb = hex2rgb(value.color);
486
+ context.fillStyle = "rgba(".concat(rgb[0], ",").concat(rgb[1], ",").concat(rgb[2], ",0.5)");
487
+ }
488
+ else {
489
+ context.fillStyle = value.color;
490
+ }
491
+ if (value.color === "#000000") {
492
+ context.strokeStyle = "rgba(255,255,255,0.7)";
493
+ }
494
+ if (this.showCollision) {
495
+ context.strokeStyle = "rgba(0,255,255,1)";
496
+ context.strokeRect(0, 0, value.width_max, value.height);
497
+ if (value.color === "#000000") {
498
+ context.strokeStyle = "rgba(255,255,255,0.7)";
499
+ }
500
+ else {
501
+ context.strokeStyle = "rgba(0,0,0,0.7)";
502
+ }
503
+ }
504
+ var lines = value.content.split("\n");
505
+ for (var i_1 in lines) {
506
+ var line = lines[i_1], posY = void 0;
507
+ posY = (Number(i_1) + 1) * (value.fontSize) * (1 + this.commentYPaddingTop);
508
+ context.strokeText(line, 0, posY);
509
+ context.fillText(line, 0, posY);
510
+ if (this.showCollision) {
511
+ context.strokeStyle = "rgba(255,255,0,0.5)";
512
+ context.strokeRect(0, posY, value.width_max, value.fontSize * -1);
513
+ if (value.color === "#000000") {
514
+ context.strokeStyle = "rgba(255,255,255,0.7)";
515
+ }
516
+ else {
517
+ context.strokeStyle = "rgba(0,0,0,0.7)";
518
+ }
519
+ }
520
+ }
521
+ this.data[i].image = image;
522
+ };
523
+ NiconiComments.prototype.parseCommand = function (comment) {
524
+ var metadata = comment.mail, loc = null, size = null, fontSize = null, color = null, font = null, full = false, ender = false, _live = false, invisible = false, long = null;
525
+ for (var i in metadata) {
526
+ var command = metadata[i].toLowerCase();
527
+ var match = command.match(/^@([0-9.]+)/);
528
+ if (match) {
529
+ long = match[1];
530
+ }
531
+ if (loc === null) {
532
+ switch (command) {
533
+ case "ue":
534
+ loc = "ue";
535
+ break;
536
+ case "shita":
537
+ loc = "shita";
538
+ break;
539
+ }
540
+ }
541
+ if (size === null) {
542
+ switch (command) {
543
+ case "big":
544
+ size = "big";
545
+ fontSize = this.fontSize.big["default"];
546
+ break;
547
+ case "small":
548
+ size = "small";
549
+ fontSize = this.fontSize.small["default"];
550
+ break;
551
+ }
552
+ }
553
+ if (color === null) {
554
+ switch (command) {
555
+ case "white":
556
+ color = "#FFFFFF";
557
+ break;
558
+ case "red":
559
+ color = "#FF0000";
560
+ break;
561
+ case "pink":
562
+ color = "#FF8080";
563
+ break;
564
+ case "orange":
565
+ color = "#FFC000";
566
+ break;
567
+ case "yellow":
568
+ color = "#FFFF00";
569
+ break;
570
+ case "green":
571
+ color = "#00FF00";
572
+ break;
573
+ case "cyan":
574
+ color = "#00FFFF";
575
+ break;
576
+ case "blue":
577
+ color = "#0000FF";
578
+ break;
579
+ case "purple":
580
+ color = "#C000FF";
581
+ break;
582
+ case "black":
583
+ color = "#000000";
584
+ break;
585
+ case "white2":
586
+ case "niconicowhite":
587
+ color = "#CCCC99";
588
+ break;
589
+ case "red2":
590
+ case "truered":
591
+ color = "#CC0033";
592
+ break;
593
+ case "pink2":
594
+ color = "#FF33CC";
595
+ break;
596
+ case "orange2":
597
+ case "passionorange":
598
+ color = "#FF6600";
599
+ break;
600
+ case "yellow2":
601
+ case "madyellow":
602
+ color = "#999900";
603
+ break;
604
+ case "green2":
605
+ case "elementalgreen":
606
+ color = "#00CC66";
607
+ break;
608
+ case "cyan2":
609
+ color = "#00CCCC";
610
+ break;
611
+ case "blue2":
612
+ case "marineblue":
613
+ color = "#3399FF";
614
+ break;
615
+ case "purple2":
616
+ case "nobleviolet":
617
+ color = "#6633CC";
618
+ break;
619
+ case "black2":
620
+ color = "#666666";
621
+ break;
622
+ default:
623
+ var match_1 = command.match(/#[0-9a-z]{3,6}/);
624
+ if (match_1 && comment.premium) {
625
+ color = match_1[0].toUpperCase();
626
+ }
627
+ break;
628
+ }
629
+ }
630
+ if (font === null) {
631
+ switch (command) {
632
+ case "gothic":
633
+ font = "gothic";
634
+ break;
635
+ case "mincho":
636
+ font = "mincho";
637
+ break;
638
+ }
639
+ }
640
+ switch (command) {
641
+ case "full":
642
+ full = true;
643
+ break;
644
+ case "ender":
645
+ ender = true;
646
+ break;
647
+ case "_live":
648
+ _live = true;
649
+ break;
650
+ case "invisible":
651
+ invisible = true;
652
+ break;
653
+ }
654
+ }
655
+ return { loc: loc, size: size, fontSize: fontSize, color: color, font: font, full: full, ender: ender, _live: _live, invisible: invisible, long: long };
656
+ };
657
+ NiconiComments.prototype.parseCommandAndNicoscript = function (comment) {
658
+ comment.content = comment.content.replace("/\t/g", "  ");
659
+ var data = this.parseCommand(comment), nicoscript = comment.content.match(/^@(デフォルト|置換|逆|コメント禁止|シーク禁止|ジャンプ)/);
660
+ if (nicoscript) {
661
+ switch (nicoscript[1]) {
662
+ case "デフォルト":
663
+ this.nicoScripts["default"].push({
664
+ start: comment.vpos,
665
+ long: data.long === null ? null : Math.floor(data.long * 100),
666
+ color: data.color,
667
+ size: data.size,
668
+ font: data.font,
669
+ loc: data.loc
670
+ });
671
+ break;
672
+ case "逆":
673
+ var reverse = comment.content.match(/^@逆 ?(全|コメ|投コメ)?/);
674
+ if (!reverse[1]) {
675
+ reverse[1] = "全";
676
+ }
677
+ if (data.long === null) {
678
+ data.long = 30;
679
+ }
680
+ this.nicoScripts.reverse.push({
681
+ start: comment.vpos,
682
+ end: comment.vpos + (data.long * 100),
683
+ target: reverse[1]
684
+ });
685
+ break;
686
+ case "コメント禁止":
687
+ if (data.long === null) {
688
+ data.long = 30;
689
+ }
690
+ this.nicoScripts.reverse.push({
691
+ start: comment.vpos,
692
+ end: comment.vpos + (data.long * 100)
693
+ });
694
+ break;
695
+ case "置換":
696
+ var content = comment.content.split(""), quote = "", last_i = "", string = "", result = [];
697
+ for (var _i = 0, _a = content.slice(4); _i < _a.length; _i++) {
698
+ var i = _a[_i];
699
+ if (i.match(/["'「]/) && quote === "") {
700
+ quote = i;
701
+ }
702
+ else if (i.match(/["']/) && quote === i && last_i !== "\\") {
703
+ result.push(string.replace("\\n", "\n"));
704
+ quote = "";
705
+ string = "";
706
+ }
707
+ else if (i.match(/」/) && quote === "「") {
708
+ result.push(string);
709
+ quote = "";
710
+ string = "";
711
+ }
712
+ else if (quote === "" && i.match(/[\s ]/)) {
713
+ if (string) {
714
+ result.push(string);
715
+ string = "";
716
+ }
717
+ }
718
+ else {
719
+ string += i;
720
+ }
721
+ last_i = i;
722
+ }
723
+ result.push(string);
724
+ this.nicoScripts.replace.push({
725
+ start: comment.vpos,
726
+ long: data.long === null ? null : Math.floor(data.long * 100),
727
+ keyword: result[0],
728
+ replace: result[1] || "",
729
+ range: result[2] || "単",
730
+ target: result[3] || "コメ",
731
+ condition: result[4] || "部分一致",
732
+ color: data.color,
733
+ size: data.size,
734
+ font: data.font,
735
+ loc: data.loc
736
+ });
737
+ break;
738
+ }
739
+ data.invisible = true;
740
+ }
741
+ var color = "#FFFFFF", size = "medium", font = "defont", loc = "naka";
742
+ for (var i in this.nicoScripts["default"]) {
743
+ if (this.nicoScripts["default"][i].long !== null && this.nicoScripts["default"][i].start + this.nicoScripts["default"][i].long < comment.vpos) {
744
+ this.nicoScripts["default"] = this.nicoScripts["default"].splice(Number(i), 1);
745
+ continue;
746
+ }
747
+ if (this.nicoScripts["default"][i].loc) {
748
+ loc = this.nicoScripts["default"][i].loc;
749
+ }
750
+ if (this.nicoScripts["default"][i].color) {
751
+ color = this.nicoScripts["default"][i].color;
752
+ }
753
+ if (this.nicoScripts["default"][i].size) {
754
+ size = this.nicoScripts["default"][i].size;
755
+ }
756
+ if (this.nicoScripts["default"][i].font) {
757
+ font = this.nicoScripts["default"][i].font;
758
+ }
759
+ }
760
+ for (var i in this.nicoScripts.replace) {
761
+ if (this.nicoScripts.replace[i].long !== null && this.nicoScripts.replace[i].start + this.nicoScripts.replace[i].long < comment.vpos) {
762
+ this.nicoScripts["default"] = this.nicoScripts["default"].splice(Number(i), 1);
763
+ continue;
764
+ }
765
+ var item = this.nicoScripts.replace[i];
766
+ if ((item.target === "コメ" && comment.owner) || (item.target === "投コメ" && !comment.owner) || (item.target === "含まない" && comment.owner))
767
+ continue;
768
+ if ((item.condition === "完全一致" && comment.content === item.keyword) || (item.condition === "部分一致" && comment.content.indexOf(item.keyword) !== -1)) {
769
+ console.log(item, comment);
770
+ if (item.range === "単") {
771
+ comment.content = comment.content.replace(new RegExp(item.keyword, "g"), item.replace);
772
+ }
773
+ else {
774
+ comment.content = item.replace;
775
+ }
776
+ console.log(comment);
777
+ if (item.loc) {
778
+ loc = item.loc;
779
+ }
780
+ if (item.color) {
781
+ color = item.color;
782
+ }
783
+ if (item.size) {
784
+ size = item.size;
785
+ }
786
+ if (item.font) {
787
+ font = item.font;
788
+ }
789
+ }
790
+ }
791
+ if (!data.loc) {
792
+ data.loc = loc;
793
+ }
794
+ if (!data.color) {
795
+ data.color = color;
796
+ }
797
+ if (!data.size) {
798
+ data.size = size;
799
+ data.fontSize = this.fontSize[data.size]["default"];
800
+ }
801
+ if (!data.font) {
802
+ data.font = font;
803
+ }
804
+ if (data.loc !== "naka") {
805
+ if (!data.long) {
806
+ data.long = 300;
807
+ }
808
+ else {
809
+ data.long = Math.floor(data.long * 100);
810
+ }
811
+ }
812
+ return __assign(__assign({}, comment), data);
813
+ };
814
+ NiconiComments.prototype.drawCanvas = function (vpos) {
815
+ if (this.lastVpos === vpos)
816
+ return;
817
+ this.lastVpos = vpos;
818
+ this.fpsCount++;
819
+ this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
820
+ if (this.video) {
821
+ var offsetX = void 0, offsetY = void 0, scale = void 0, height = this.canvas.height / this.video.videoHeight, width = this.canvas.width / this.video.videoWidth;
822
+ if (height > width) {
823
+ scale = width;
824
+ }
825
+ else {
826
+ scale = height;
827
+ }
828
+ offsetX = (this.canvas.width - this.video.videoWidth * scale) * 0.5;
829
+ offsetY = (this.canvas.height - this.video.videoHeight * scale) * 0.5;
830
+ this.context.drawImage(this.video, offsetX, offsetY, this.video.videoWidth * scale, this.video.videoHeight * scale);
831
+ }
832
+ if (this.timeline[vpos]) {
833
+ for (var index in this.timeline[vpos]) {
834
+ var comment = this.data[this.timeline[vpos][index]];
835
+ if (comment.invisible) {
836
+ continue;
837
+ }
838
+ if (!comment.image) {
839
+ this.getTextImage(this.timeline[vpos][index]);
840
+ }
841
+ this.drawText(comment, vpos);
842
+ }
843
+ }
844
+ if (this.showFPS) {
845
+ this.context.font = parseFont("defont", 60, this.useLegacy);
846
+ this.context.fillStyle = "#00FF00";
847
+ this.context.strokeText("FPS:" + this.fps, 100, 100);
848
+ this.context.fillText("FPS:" + this.fps, 100, 100);
849
+ }
850
+ if (this.showCommentCount) {
851
+ this.context.font = parseFont("defont", 60, this.useLegacy);
852
+ this.context.fillStyle = "#00FF00";
853
+ if (this.timeline[vpos]) {
854
+ this.context.strokeText("Count:" + this.timeline[vpos].length, 100, 200);
855
+ this.context.fillText("Count:" + this.timeline[vpos].length, 100, 200);
856
+ }
857
+ else {
858
+ this.context.strokeText("Count:0", 100, 200);
859
+ this.context.fillText("Count:0", 100, 200);
860
+ }
861
+ }
862
+ };
863
+ NiconiComments.prototype.clear = function () {
864
+ this.context.clearRect(0, 0, 1920, 1080);
865
+ };
866
+ return NiconiComments;
867
+ }());
868
+ var groupBy = function (array, key, key2) {
869
+ var data = {};
870
+ for (var i in array) {
871
+ if (!data[array[i][key]]) {
872
+ data[array[i][key]] = {};
873
+ }
874
+ if (!data[array[i][key]][array[i][key2]]) {
875
+ data[array[i][key]][array[i][key2]] = [];
876
+ }
877
+ array[i].index = i;
878
+ data[array[i][key]][array[i][key2]].push(array[i]);
879
+ }
880
+ return data;
881
+ };
882
+ var parseFont = function (font, size, useLegacy) {
883
+ switch (font) {
884
+ case "gothic":
885
+ return "normal 400 ".concat(size, "px \"\u6E38\u30B4\u30B7\u30C3\u30AF\u4F53\", \"\u6E38\u30B4\u30B7\u30C3\u30AF\", \"Yu Gothic\", YuGothic, yugothic, YuGo-Medium");
886
+ case "mincho":
887
+ return "normal 400 ".concat(size, "px \"\u6E38\u660E\u671D\u4F53\", \"\u6E38\u660E\u671D\", \"Yu Mincho\", YuMincho, yumincho, YuMin-Medium");
888
+ default:
889
+ if (useLegacy) {
890
+ return "normal 600 ".concat(size, "px Arial, \"\uFF2D\uFF33 \uFF30\u30B4\u30B7\u30C3\u30AF\", \"MS PGothic\", MSPGothic, MS-PGothic");
891
+ }
892
+ else {
893
+ return "normal 600 ".concat(size, "px sans-serif, Arial, \"\uFF2D\uFF33 \uFF30\u30B4\u30B7\u30C3\u30AF\", \"MS PGothic\", MSPGothic, MS-PGothic");
894
+ }
895
+ }
896
+ };
897
+ var arrayPush = function (array, key, push) {
898
+ if (!array) {
899
+ array = {};
900
+ }
901
+ if (!array[key]) {
902
+ array[key] = [];
903
+ }
904
+ array[key].push(push);
905
+ };
906
+ var hex2rgb = function (hex) {
907
+ if (hex.slice(0, 1) === "#")
908
+ hex = hex.slice(1);
909
+ if (hex.length === 3)
910
+ hex = hex.slice(0, 1) + hex.slice(0, 1) + hex.slice(1, 2) + hex.slice(1, 2) + hex.slice(2, 3) + hex.slice(2, 3);
911
+ return [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map(function (str) {
912
+ return parseInt(str, 16);
913
+ });
914
+ };
915
+
916
+ return NiconiComments;
1077
917
 
1078
918
  }));