@xpadev-net/niconicomments 0.2.0 → 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 -1066
  3. package/package.json +9 -4
package/dist/bundle.js CHANGED
@@ -1,1075 +1,918 @@
1
1
  /*!
2
- niconicomments.js v0.2.0
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
-
539
- if (comment.loc === "naka") {
540
- if (reverse) {
541
- posX = (1920 + comment.width_max) * (vpos - comment.vpos) / 500 - comment.width_max;
542
- } else {
543
- posX = 1920 - (1920 + comment.width_max) * (vpos - comment.vpos) / 500;
544
- }
545
- }
546
-
547
- this.context.drawImage(comment.image, posX, comment.posY);
548
- }
549
- /**
550
- * drawTextで毎回fill/strokeすると重いので画像化して再利用できるようにする
551
- * @param {number} i - コメントデータのインデックス
552
- */
553
-
554
-
555
- getTextImage(i) {
556
- let value = this.data[i];
557
-
558
- if (value.invisible) {
559
- return;
560
- }
561
-
562
- let image = document.createElement("canvas");
563
- image.width = value.width_max;
564
- image.height = value.height;
565
- let context = image.getContext("2d");
566
- context.strokeStyle = "rgba(0,0,0,0.7)";
567
- context.textAlign = "start";
568
- context.textBaseline = "alphabetic";
569
- context.lineWidth = 4;
570
- context.font = parseFont(value.font, value.fontSize, this.useLegacy);
571
-
572
- if (value._live) {
573
- let rgb = hex2rgb(value.color);
574
- context.fillStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},0.5)`;
575
- } else {
576
- context.fillStyle = value.color;
577
- }
578
-
579
- if (value.color === "#000000") {
580
- context.strokeStyle = "rgba(255,255,255,0.7)";
581
- }
582
-
583
- if (this.showCollision) {
584
- context.strokeStyle = "rgba(0,255,255,1)";
585
- context.strokeRect(0, 0, value.width_max, value.height);
586
-
587
- if (value.color === "#000000") {
588
- context.strokeStyle = "rgba(255,255,255,0.7)";
589
- } else {
590
- context.strokeStyle = "rgba(0,0,0,0.7)";
591
- }
592
- }
593
-
594
- let lines = value.content.split("\n");
595
-
596
- for (let i in lines) {
597
- let line = lines[i],
598
- posY;
599
- posY = (Number(i) + 1) * value.fontSize * (1 + this.commentYPaddingTop);
600
- context.strokeText(line, 0, posY);
601
- context.fillText(line, 0, posY);
602
-
603
- if (this.showCollision) {
604
- context.strokeStyle = "rgba(255,255,0,0.5)";
605
- context.strokeRect(0, posY, value.width_max, value.fontSize * -1);
606
-
607
- if (value.color === "#000000") {
608
- context.strokeStyle = "rgba(255,255,255,0.7)";
609
- } else {
610
- context.strokeStyle = "rgba(0,0,0,0.7)";
611
- }
612
- }
613
- }
614
-
615
- this.data[i].image = image;
616
- }
617
- /**
618
- * コメントに含まれるコマンドを解釈する
619
- * @param comment- 独自フォーマットのコメントデータ
620
- * @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}}
621
- */
622
-
623
-
624
- parseCommand(comment) {
625
- let metadata = comment.mail,
626
- loc = null,
627
- size = null,
628
- fontSize = null,
629
- color = null,
630
- font = null,
631
- full = false,
632
- ender = false,
633
- _live = false,
634
- invisible = false,
635
- long = null;
636
-
637
- for (let i in metadata) {
638
- let command = metadata[i].toLowerCase();
639
- const match = command.match(/^@([0-9.]+)/);
640
-
641
- if (match) {
642
- long = match[1];
643
- }
644
-
645
- if (loc === null) {
646
- switch (command) {
647
- case "ue":
648
- loc = "ue";
649
- break;
650
-
651
- case "shita":
652
- loc = "shita";
653
- break;
654
- }
655
- }
656
-
657
- if (size === null) {
658
- switch (command) {
659
- case "big":
660
- size = "big";
661
- fontSize = this.fontSize.big.default;
662
- break;
663
-
664
- case "small":
665
- size = "small";
666
- fontSize = this.fontSize.small.default;
667
- break;
668
- }
669
- }
670
-
671
- if (color === null) {
672
- switch (command) {
673
- case "white":
674
- color = "#FFFFFF";
675
- break;
676
-
677
- case "red":
678
- color = "#FF0000";
679
- break;
680
-
681
- case "pink":
682
- color = "#FF8080";
683
- break;
684
-
685
- case "orange":
686
- color = "#FFC000";
687
- break;
688
-
689
- case "yellow":
690
- color = "#FFFF00";
691
- break;
692
-
693
- case "green":
694
- color = "#00FF00";
695
- break;
696
-
697
- case "cyan":
698
- color = "#00FFFF";
699
- break;
700
-
701
- case "blue":
702
- color = "#0000FF";
703
- break;
704
-
705
- case "purple":
706
- color = "#C000FF";
707
- break;
708
-
709
- case "black":
710
- color = "#000000";
711
- break;
712
-
713
- case "white2":
714
- case "niconicowhite":
715
- color = "#CCCC99";
716
- break;
717
-
718
- case "red2":
719
- case "truered":
720
- color = "#CC0033";
721
- break;
722
-
723
- case "pink2":
724
- color = "#FF33CC";
725
- break;
726
-
727
- case "orange2":
728
- case "passionorange":
729
- color = "#FF6600";
730
- break;
731
-
732
- case "yellow2":
733
- case "madyellow":
734
- color = "#999900";
735
- break;
736
-
737
- case "green2":
738
- case "elementalgreen":
739
- color = "#00CC66";
740
- break;
741
-
742
- case "cyan2":
743
- color = "#00CCCC";
744
- break;
745
-
746
- case "blue2":
747
- case "marineblue":
748
- color = "#3399FF";
749
- break;
750
-
751
- case "purple2":
752
- case "nobleviolet":
753
- color = "#6633CC";
754
- break;
755
-
756
- case "black2":
757
- color = "#666666";
758
- break;
759
-
760
- default:
761
- const match = command.match(/#[0-9a-z]{3,6}/);
762
-
763
- if (match && comment.premium) {
764
- color = match[0].toUpperCase();
765
- }
766
-
767
- break;
768
- }
769
- }
770
-
771
- if (font === null) {
772
- switch (command) {
773
- case "gothic":
774
- font = "gothic";
775
- break;
776
-
777
- case "mincho":
778
- font = "mincho";
779
- break;
780
- }
781
- }
782
-
783
- switch (command) {
784
- case "full":
785
- full = true;
786
- break;
787
-
788
- case "ender":
789
- ender = true;
790
- break;
791
-
792
- case "_live":
793
- _live = true;
794
- break;
795
-
796
- case "invisible":
797
- invisible = true;
798
- break;
799
- }
800
- }
801
-
802
- return {
803
- loc,
804
- size,
805
- fontSize,
806
- color,
807
- font,
808
- full,
809
- ender,
810
- _live,
811
- invisible,
812
- long
813
- };
814
- }
815
-
816
- parseCommandAndNicoscript(comment) {
817
- let data = this.parseCommand(comment),
818
- nicoscript = comment.content.match(/^@(デフォルト|置換|逆|コメント禁止|シーク禁止|ジャンプ)/);
819
-
820
- if (nicoscript) {
821
- switch (nicoscript[1]) {
822
- case "デフォルト":
823
- this.nicoScripts.default.push({
824
- start: comment.vpos,
825
- long: data.long === null ? null : Math.floor(data.long * 100),
826
- color: data.color,
827
- size: data.size,
828
- font: data.font,
829
- loc: data.loc
830
- });
831
- break;
832
-
833
- case "逆":
834
- let reverse = comment.content.match(/^@逆 ?(全|コメ|投コメ)?/);
835
-
836
- if (!reverse[1]) {
837
- reverse[1] = "全";
838
- }
839
-
840
- if (data.long === null) {
841
- data.long = 30;
842
- }
843
-
844
- this.nicoScripts.reverse.push({
845
- "start": comment.vpos,
846
- "end": comment.vpos + data.long * 100,
847
- "target": reverse[1]
848
- });
849
- break;
850
- }
851
-
852
- data.invisible = true;
853
- }
854
-
855
- let color = "#FFFFFF",
856
- size = "medium",
857
- font = "defont",
858
- loc = "naka";
859
-
860
- for (let i in this.nicoScripts.default) {
861
- if (this.nicoScripts.default[i].long !== null && this.nicoScripts.default[i].start + this.nicoScripts.default[i].long < comment.vpos) {
862
- this.nicoScripts.default = this.nicoScripts.default.splice(Number(i), 1);
863
- continue;
864
- }
865
-
866
- if (this.nicoScripts.default[i].loc) {
867
- loc = this.nicoScripts.default[i].loc;
868
- }
869
-
870
- if (this.nicoScripts.default[i].color) {
871
- color = this.nicoScripts.default[i].color;
872
- }
873
-
874
- if (this.nicoScripts.default[i].size) {
875
- size = this.nicoScripts.default[i].size;
876
- }
877
-
878
- if (this.nicoScripts.default[i].font) {
879
- font = this.nicoScripts.default[i].font;
880
- }
881
- }
882
-
883
- if (!data.loc) {
884
- data.loc = loc;
885
- }
886
-
887
- if (!data.color) {
888
- data.color = color;
889
- }
890
-
891
- if (!data.size) {
892
- data.size = size;
893
- data.fontSize = this.fontSize[data.size].default;
894
- }
895
-
896
- if (!data.font) {
897
- data.font = font;
898
- }
899
-
900
- if (data.loc !== "naka") {
901
- if (!data.long) {
902
- data.long = 300;
903
- } else {
904
- data.long = Math.floor(data.long * 100);
905
- }
906
- }
907
-
908
- return data;
909
- }
910
- /**
911
- * キャンバスを描画する
912
- * @param vpos - 動画の現在位置の100倍 ニコニコから吐き出されるコメントの位置情報は主にこれ
913
- */
914
-
915
-
916
- drawCanvas(vpos) {
917
- if (this.lastVpos === vpos) return;
918
- this.lastVpos = vpos;
919
- this.fpsCount++;
920
- this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
921
-
922
- if (this.video) {
923
- let offsetX,
924
- offsetY,
925
- scale,
926
- height = this.canvas.height / this.video.videoHeight,
927
- width = this.canvas.width / this.video.videoWidth;
928
-
929
- if (height > width) {
930
- scale = width;
931
- } else {
932
- scale = height;
933
- }
934
-
935
- offsetX = (this.canvas.width - this.video.videoWidth * scale) * 0.5;
936
- offsetY = (this.canvas.height - this.video.videoHeight * scale) * 0.5;
937
- this.context.drawImage(this.video, offsetX, offsetY, this.video.videoWidth * scale, this.video.videoHeight * scale);
938
- }
939
-
940
- if (this.timeline[vpos]) {
941
- for (let index in this.timeline[vpos]) {
942
- let comment = this.data[this.timeline[vpos][index]];
943
-
944
- if (comment.invisible) {
945
- continue;
946
- }
947
-
948
- if (!comment.image) {
949
- this.getTextImage(this.timeline[vpos][index]);
950
- }
951
-
952
- this.drawText(comment, vpos);
953
- }
954
- }
955
-
956
- if (this.showFPS) {
957
- this.context.font = parseFont("defont", 60, this.useLegacy);
958
- this.context.fillStyle = "#00FF00";
959
- this.context.strokeText("FPS:" + this.fps, 100, 100);
960
- this.context.fillText("FPS:" + this.fps, 100, 100);
961
- }
962
-
963
- if (this.showCommentCount) {
964
- this.context.font = parseFont("defont", 60, this.useLegacy);
965
- this.context.fillStyle = "#00FF00";
966
-
967
- if (this.timeline[vpos]) {
968
- this.context.strokeText("Count:" + this.timeline[vpos].length, 100, 200);
969
- this.context.fillText("Count:" + this.timeline[vpos].length, 100, 200);
970
- } else {
971
- this.context.strokeText("Count:0", 100, 200);
972
- this.context.fillText("Count:0", 100, 200);
973
- }
974
- }
975
- }
976
- /**
977
- * キャンバスを消去する
978
- */
979
-
980
-
981
- clear() {
982
- this.context.clearRect(0, 0, 1920, 1080);
983
- }
984
-
985
- }
986
- /**
987
- * 配列を複数のキーでグループ化する
988
- * @param {{}} array
989
- * @param {string} key
990
- * @param {string} key2
991
- * @returns {{}}
992
- */
993
-
994
-
995
- const groupBy = (array, key, key2) => {
996
- let data = {};
997
-
998
- for (let i in array) {
999
- if (!data[array[i][key]]) {
1000
- data[array[i][key]] = {};
1001
- }
1002
-
1003
- if (!data[array[i][key]][array[i][key2]]) {
1004
- data[array[i][key]][array[i][key2]] = [];
1005
- }
1006
-
1007
- array[i].index = i;
1008
- data[array[i][key]][array[i][key2]].push(array[i]);
1009
- }
1010
-
1011
- return data;
1012
- };
1013
- /**
1014
- * フォント名とサイズをもとにcontextで使えるフォントを生成する
1015
- * @param {string} font
1016
- * @param {number} size
1017
- * @param {boolean} useLegacy
1018
- * @returns {string}
1019
- */
1020
-
1021
-
1022
- const parseFont = (font, size, useLegacy) => {
1023
- switch (font) {
1024
- case "gothic":
1025
- return `normal 400 ${size}px "游ゴシック体", "游ゴシック", "Yu Gothic", YuGothic, yugothic, YuGo-Medium`;
1026
-
1027
- case "mincho":
1028
- return `normal 400 ${size}px "游明朝体", "游明朝", "Yu Mincho", YuMincho, yumincho, YuMin-Medium`;
1029
-
1030
- default:
1031
- if (useLegacy) {
1032
- return `normal 600 ${size}px Arial, "MS Pゴシック", "MS PGothic", MSPGothic, MS-PGothic`;
1033
- } else {
1034
- return `normal 600 ${size}px sans-serif, Arial, "MS Pゴシック", "MS PGothic", MSPGothic, MS-PGothic`;
1035
- }
1036
-
1037
- }
1038
- };
1039
- /**
1040
- * phpのarray_push的なあれ
1041
- * @param array
1042
- * @param {string} key
1043
- * @param push
1044
- */
1045
-
1046
-
1047
- const arrayPush = (array, key, push) => {
1048
- if (!array) {
1049
- array = {};
1050
- }
1051
-
1052
- if (!array[key]) {
1053
- array[key] = [];
1054
- }
1055
-
1056
- array[key].push(push);
1057
- };
1058
- /**
1059
- * Hexからrgbに変換する(_live用)
1060
- * @param {string} hex
1061
- * @return {array} RGB
1062
- */
1063
-
1064
-
1065
- const hex2rgb = hex => {
1066
- if (hex.slice(0, 1) === "#") hex = hex.slice(1);
1067
- 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);
1068
- return [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map(function (str) {
1069
- return parseInt(str, 16);
1070
- });
1071
- };
1072
-
1073
- 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;
1074
917
 
1075
918
  }));