jassub 1.4.5 → 1.5.1

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.
package/dist/jassub.es.js CHANGED
@@ -1,514 +1,516 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => {
4
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
- return value;
6
- };
7
- if (!("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype) {
8
- HTMLVideoElement.prototype._rvfcpolyfillmap = {};
9
- HTMLVideoElement.prototype.requestVideoFrameCallback = function(callback) {
10
- const quality = this.getVideoPlaybackQuality();
11
- const baseline = this.mozPresentedFrames || this.mozPaintedFrames || quality.totalVideoFrames - quality.droppedVideoFrames;
12
- const check = (old, now2) => {
13
- const newquality = this.getVideoPlaybackQuality();
14
- const presentedFrames = this.mozPresentedFrames || this.mozPaintedFrames || newquality.totalVideoFrames - newquality.droppedVideoFrames;
15
- if (presentedFrames > baseline) {
16
- const processingDuration = this.mozFrameDelay || newquality.totalFrameDelay - quality.totalFrameDelay || 0;
17
- const timediff = now2 - old;
18
- callback(now2, {
19
- presentationTime: now2 + processingDuration * 1e3,
20
- expectedDisplayTime: now2 + timediff,
21
- width: this.videoWidth,
22
- height: this.videoHeight,
23
- mediaTime: Math.max(0, this.currentTime || 0) + timediff / 1e3,
24
- presentedFrames,
25
- processingDuration
26
- });
27
- delete this._rvfcpolyfillmap[handle];
28
- } else {
29
- this._rvfcpolyfillmap[handle] = requestAnimationFrame((newer) => check(now2, newer));
30
- }
31
- };
32
- const handle = Date.now();
33
- const now = performance.now();
34
- this._rvfcpolyfillmap[handle] = requestAnimationFrame((newer) => check(now, newer));
35
- return handle;
36
- };
37
- HTMLVideoElement.prototype.cancelVideoFrameCallback = function(handle) {
38
- cancelAnimationFrame(this._rvfcpolyfillmap[handle]);
39
- delete this._rvfcpolyfillmap[handle];
40
- };
41
- }
42
- const _JASSUB = class extends EventTarget {
43
- constructor(options = {}) {
44
- var _a, _b, _c;
45
- super();
46
- if (!globalThis.Worker) {
47
- this.destroy("Worker not supported");
48
- }
49
- _JASSUB._test();
50
- const blendMode = options.blendMode || "js";
51
- const asyncRender = typeof createImageBitmap !== "undefined" && ((_a = options.asyncRender) != null ? _a : true);
52
- const offscreenRender = typeof OffscreenCanvas !== "undefined" && ((_b = options.offscreenRender) != null ? _b : true);
53
- this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && ((_c = options.onDemandRender) != null ? _c : true);
54
- this.timeOffset = options.timeOffset || 0;
55
- this._video = options.video;
56
- this._videoHeight = 0;
57
- this._videoWidth = 0;
58
- this._canvas = options.canvas;
59
- if (this._video && !this._canvas) {
60
- this._canvasParent = document.createElement("div");
61
- this._canvasParent.className = "JASSUB";
62
- this._canvasParent.style.position = "relative";
63
- this._canvas = document.createElement("canvas");
64
- this._canvas.style.display = "block";
65
- this._canvas.style.position = "absolute";
66
- this._canvas.style.pointerEvents = "none";
67
- this._canvasParent.appendChild(this._canvas);
68
- if (this._video.nextSibling) {
69
- this._video.parentNode.insertBefore(this._canvasParent, this._video.nextSibling);
70
- } else {
71
- this._video.parentNode.appendChild(this._canvasParent);
72
- }
73
- } else if (!this._canvas) {
74
- this.destroy("Don't know where to render: you should give video or canvas in options.");
75
- }
76
- this._bufferCanvas = document.createElement("canvas");
77
- this._bufferCtx = this._bufferCanvas.getContext("2d", { desynchronized: true, willReadFrequently: true });
78
- this._canvasctrl = offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas;
79
- this._ctx = !offscreenRender && this._canvasctrl.getContext("2d", { desynchronized: true });
80
- this._lastRenderTime = 0;
81
- this.debug = !!options.debug;
82
- this.prescaleFactor = options.prescaleFactor || 1;
83
- this.prescaleHeightLimit = options.prescaleHeightLimit || 1080;
84
- this.maxRenderHeight = options.maxRenderHeight || 0;
85
- this._worker = new Worker(_JASSUB._supportsWebAssembly ? options.workerUrl || "jassub-worker.js" : options.legacyWorkerUrl || "jassub-worker-legacy.js");
86
- this._worker.onmessage = (e) => this._onmessage(e);
87
- this._worker.onerror = (e) => this._error(e);
88
- this._loaded = new Promise((resolve) => {
89
- this._init = () => {
90
- var _a2, _b2;
91
- if (this._destroyed)
92
- return;
93
- this._worker.postMessage({
94
- target: "init",
95
- asyncRender,
96
- onDemandRender: this._onDemandRender,
97
- width: this._canvasctrl.width,
98
- height: this._canvasctrl.height,
99
- preMain: true,
100
- blendMode,
101
- subUrl: options.subUrl,
102
- subContent: options.subContent || null,
103
- fonts: options.fonts || [],
104
- availableFonts: options.availableFonts || { "liberation sans": "./default.woff2" },
105
- fallbackFont: options.fallbackFont || "liberation sans",
106
- debug: this.debug,
107
- targetFps: options.targetFps || 24,
108
- dropAllAnimations: options.dropAllAnimations,
109
- libassMemoryLimit: options.libassMemoryLimit || 0,
110
- libassGlyphLimit: options.libassGlyphLimit || 0,
111
- hasAlphaBug: _JASSUB._hasAlphaBug,
112
- useLocalFonts: "queryLocalFonts" in self && ((_a2 = options.useLocalFonts) != null ? _a2 : true)
113
- });
114
- if (offscreenRender === true)
115
- this.sendMessage("offscreenCanvas", null, [this._canvasctrl]);
116
- this._boundResize = this.resize.bind(this);
117
- this._boundTimeUpdate = this._timeupdate.bind(this);
118
- this._boundSetRate = this.setRate.bind(this);
119
- if (this._video)
120
- this.setVideo(options.video);
121
- if (this._onDemandRender) {
122
- this.busy = false;
123
- this._lastDemandTime = null;
124
- (_b2 = this._video) == null ? void 0 : _b2.requestVideoFrameCallback(this._handleRVFC.bind(this));
125
- }
126
- resolve();
127
- };
128
- });
129
- }
130
- static _test() {
131
- if (_JASSUB._supportsWebAssembly !== null)
132
- return null;
133
- const canvas1 = document.createElement("canvas");
134
- const ctx1 = canvas1.getContext("2d", { willReadFrequently: true });
135
- if (typeof ImageData.prototype.constructor === "function") {
136
- try {
137
- new ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1);
138
- } catch (e) {
139
- console.log("detected that ImageData is not constructable despite browser saying so");
140
- self.ImageData = function(data, width, height) {
141
- const imageData = ctx1.createImageData(width, height);
142
- if (data)
143
- imageData.data.set(data);
144
- return imageData;
145
- };
146
- }
147
- }
148
- try {
149
- if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
150
- const module = new WebAssembly.Module(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0));
151
- if (module instanceof WebAssembly.Module)
152
- _JASSUB._supportsWebAssembly = new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
153
- }
154
- } catch (e) {
155
- _JASSUB._supportsWebAssembly = false;
156
- }
157
- const canvas2 = document.createElement("canvas");
158
- const ctx2 = canvas2.getContext("2d", { willReadFrequently: true });
159
- canvas1.width = canvas2.width = 1;
160
- canvas1.height = canvas2.height = 1;
161
- ctx1.clearRect(0, 0, 1, 1);
162
- ctx2.clearRect(0, 0, 1, 1);
163
- const prePut = ctx2.getImageData(0, 0, 1, 1).data;
164
- ctx1.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0);
165
- ctx2.drawImage(canvas1, 0, 0);
166
- const postPut = ctx2.getImageData(0, 0, 1, 1).data;
167
- _JASSUB._hasAlphaBug = prePut[1] !== postPut[1];
168
- if (_JASSUB._hasAlphaBug)
169
- console.log("Detected a browser having issue with transparent pixels, applying workaround");
170
- canvas1.remove();
171
- canvas2.remove();
172
- }
173
- resize(width = 0, height = 0, top = 0, left = 0, force = ((_a) => (_a = this._video) == null ? void 0 : _a.paused)()) {
174
- if ((!width || !height) && this._video) {
175
- const videoSize = this._getVideoPosition();
176
- let renderSize = null;
177
- if (this._videoWidth) {
178
- const widthRatio = this._video.videoWidth / this._videoWidth;
179
- const heightRatio = this._video.videoHeight / this._videoHeight;
180
- renderSize = this._computeCanvasSize((videoSize.width || 0) / widthRatio, (videoSize.height || 0) / heightRatio);
181
- } else {
182
- renderSize = this._computeCanvasSize(videoSize.width || 0, videoSize.height || 0);
183
- }
184
- width = renderSize.width;
185
- height = renderSize.height;
186
- if (this._canvasParent) {
187
- top = videoSize.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top);
188
- left = videoSize.x;
189
- }
190
- this._canvas.style.width = videoSize.width + "px";
191
- this._canvas.style.height = videoSize.height + "px";
192
- }
193
- this._canvas.style.top = top + "px";
194
- this._canvas.style.left = left + "px";
195
- this.sendMessage("canvas", { width, height, force: force && this.busy === false });
196
- }
197
- _getVideoPosition(width = this._video.videoWidth, height = this._video.videoHeight) {
198
- const videoRatio = width / height;
199
- const { offsetWidth, offsetHeight } = this._video;
200
- const elementRatio = offsetWidth / offsetHeight;
201
- width = offsetWidth;
202
- height = offsetHeight;
203
- if (elementRatio > videoRatio) {
204
- width = Math.floor(offsetHeight * videoRatio);
205
- } else {
206
- height = Math.floor(offsetWidth / videoRatio);
207
- }
208
- const x = (offsetWidth - width) / 2;
209
- const y = (offsetHeight - height) / 2;
210
- return { width, height, x, y };
211
- }
212
- _computeCanvasSize(width = 0, height = 0) {
213
- const scalefactor = this.prescaleFactor <= 0 ? 1 : this.prescaleFactor;
214
- const ratio = self.devicePixelRatio || 1;
215
- if (height <= 0 || width <= 0) {
216
- width = 0;
217
- height = 0;
218
- } else {
219
- const sgn = scalefactor < 1 ? -1 : 1;
220
- let newH = height * ratio;
221
- if (sgn * newH * scalefactor <= sgn * this.prescaleHeightLimit) {
222
- newH *= scalefactor;
223
- } else if (sgn * newH < sgn * this.prescaleHeightLimit) {
224
- newH = this.prescaleHeightLimit;
225
- }
226
- if (this.maxRenderHeight > 0 && newH > this.maxRenderHeight)
227
- newH = this.maxRenderHeight;
228
- width *= ratio * newH / height;
229
- height = newH;
230
- }
231
- return { width, height };
232
- }
233
- _timeupdate({ type }) {
234
- const eventmap = {
235
- seeking: true,
236
- waiting: true,
237
- playing: false
238
- };
239
- const playing = eventmap[type];
240
- if (playing != null)
241
- this._playstate = playing;
242
- this.setCurrentTime(this._video.paused || this._playstate, this._video.currentTime + this.timeOffset);
243
- }
244
- setVideo(video) {
245
- if (video instanceof HTMLVideoElement) {
246
- this._removeListeners();
247
- this._video = video;
248
- if (this._onDemandRender) {
249
- this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
250
- } else {
251
- this._playstate = video.paused;
252
- video.addEventListener("timeupdate", this._boundTimeUpdate, false);
253
- video.addEventListener("progress", this._boundTimeUpdate, false);
254
- video.addEventListener("waiting", this._boundTimeUpdate, false);
255
- video.addEventListener("seeking", this._boundTimeUpdate, false);
256
- video.addEventListener("playing", this._boundTimeUpdate, false);
257
- video.addEventListener("ratechange", this._boundSetRate, false);
258
- video.addEventListener("resize", this._boundResize);
259
- }
260
- if (video.videoWidth > 0)
261
- this.resize();
262
- if (typeof ResizeObserver !== "undefined") {
263
- if (!this._ro)
264
- this._ro = new ResizeObserver(() => this.resize());
265
- this._ro.observe(video);
266
- }
267
- } else {
268
- this._error("Video element invalid!");
269
- }
270
- }
271
- runBenchmark() {
272
- this.sendMessage("runBenchmark");
273
- }
274
- setTrackByUrl(url) {
275
- this.sendMessage("setTrackByUrl", { url });
276
- }
277
- setTrack(content) {
278
- this.sendMessage("setTrack", { content });
279
- }
280
- freeTrack() {
281
- this.sendMessage("freeTrack");
282
- }
283
- setIsPaused(isPaused) {
284
- this.sendMessage("video", { isPaused });
285
- }
286
- setRate(rate) {
287
- this.sendMessage("video", { rate });
288
- }
289
- setCurrentTime(isPaused, currentTime, rate) {
290
- this.sendMessage("video", { isPaused, currentTime, rate });
291
- }
292
- createEvent(event) {
293
- this.sendMessage("createEvent", { event });
294
- }
295
- setEvent(event, index) {
296
- this.sendMessage("setEvent", { event, index });
297
- }
298
- removeEvent(index) {
299
- this.sendMessage("removeEvent", { index });
300
- }
301
- getEvents(callback) {
302
- this._fetchFromWorker({
303
- target: "getEvents"
304
- }, (err, { events }) => {
305
- callback(err, events);
306
- });
307
- }
308
- createStyle(style) {
309
- this.sendMessage("createStyle", { style });
310
- }
311
- setStyle(event, index) {
312
- this.sendMessage("setStyle", { event, index });
313
- }
314
- removeStyle(index) {
315
- this.sendMessage("removeStyle", { index });
316
- }
317
- getStyles(callback) {
318
- this._fetchFromWorker({
319
- target: "getStyles"
320
- }, (err, { styles }) => {
321
- callback(err, styles);
322
- });
323
- }
324
- addFont(font) {
325
- this.sendMessage("addFont", { font });
326
- }
327
- _sendLocalFont(name) {
328
- try {
329
- queryLocalFonts().then((fontData) => {
330
- const font = fontData == null ? void 0 : fontData.find((obj) => obj.fullName.toLowerCase() === name);
331
- if (font) {
332
- font.blob().then((blob) => {
333
- blob.arrayBuffer().then((buffer) => {
334
- this.addFont(new Uint8Array(buffer));
335
- });
336
- });
337
- }
338
- });
339
- } catch (e) {
340
- console.warn("Local fonts API:", e);
341
- }
342
- }
343
- _getLocalFont({ font }) {
344
- var _a;
345
- try {
346
- if ((_a = navigator == null ? void 0 : navigator.permissions) == null ? void 0 : _a.query) {
347
- navigator.permissions.query({ name: "local-fonts" }).then((permission) => {
348
- if (permission.state === "granted") {
349
- this._sendLocalFont(font);
350
- }
351
- });
352
- } else {
353
- this._sendLocalFont(font);
354
- }
355
- } catch (e) {
356
- console.warn("Local fonts API:", e);
357
- }
358
- }
359
- _unbusy() {
360
- if (this._lastDemandTime) {
361
- this._demandRender(this._lastDemandTime);
362
- } else {
363
- this.busy = false;
364
- }
365
- }
366
- _handleRVFC(now, { mediaTime, width, height }) {
367
- if (this._destroyed)
368
- return null;
369
- if (this.busy) {
370
- this._lastDemandTime = { mediaTime, width, height };
371
- } else {
372
- this.busy = true;
373
- this._demandRender({ mediaTime, width, height });
374
- }
375
- this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
376
- }
377
- _demandRender({ mediaTime, width, height }) {
378
- this._lastDemandTime = null;
379
- if (width !== this._videoWidth || height !== this._videoHeight) {
380
- this._videoWidth = width;
381
- this._videoHeight = height;
382
- this.resize();
383
- }
384
- this.sendMessage("demand", { time: mediaTime + this.timeOffset });
385
- }
386
- _render({ images, async, times, width, height }) {
387
- this._unbusy();
388
- const drawStartTime = Date.now();
389
- if (this._canvasctrl.width !== width || this._canvasctrl.height !== height) {
390
- this._canvasctrl.width = width;
391
- this._canvasctrl.height = height;
392
- }
393
- this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height);
394
- for (const image of images) {
395
- if (image.image) {
396
- if (async) {
397
- this._ctx.drawImage(image.image, image.x, image.y);
398
- image.image.close();
399
- } else {
400
- this._bufferCanvas.width = image.w;
401
- this._bufferCanvas.height = image.h;
402
- this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(image.image)), image.w, image.h), 0, 0);
403
- this._ctx.drawImage(this._bufferCanvas, image.x, image.y);
404
- }
405
- }
406
- }
407
- if (this.debug) {
408
- times.drawTime = Date.now() - drawStartTime;
409
- let total = 0;
410
- for (const key in times)
411
- total += times[key];
412
- console.log("Bitmaps: " + images.length + " Total: " + Math.round(total) + "ms", times);
413
- }
414
- }
415
- _fixAlpha(uint8) {
416
- if (_JASSUB._hasAlphaBug) {
417
- for (let j = 3; j < uint8.length; j += 4) {
418
- uint8[j] = uint8[j] > 1 ? uint8[j] : 1;
419
- }
420
- }
421
- return uint8;
422
- }
423
- _ready() {
424
- this._init();
425
- this.dispatchEvent(new CustomEvent("ready"));
426
- }
427
- async sendMessage(target, data = {}, transferable) {
428
- await this._loaded;
429
- if (transferable) {
430
- this._worker.postMessage({
431
- target,
432
- transferable,
433
- ...data
434
- }, [...transferable]);
435
- } else {
436
- this._worker.postMessage({
437
- target,
438
- ...data
439
- });
440
- }
441
- }
442
- _fetchFromWorker(workerOptions, callback) {
443
- try {
444
- const target = workerOptions.target;
445
- const timeout = setTimeout(() => {
446
- reject(new Error("Error: Timeout while try to fetch " + target));
447
- }, 5e3);
448
- const resolve = ({ data }) => {
449
- if (data.target === target) {
450
- callback(null, data);
451
- this._worker.removeEventListener("message", resolve);
452
- this._worker.removeEventListener("error", reject);
453
- clearTimeout(timeout);
454
- }
455
- };
456
- const reject = (event) => {
457
- callback(event);
458
- this._worker.removeEventListener("message", resolve);
459
- this._worker.removeEventListener("error", reject);
460
- clearTimeout(timeout);
461
- };
462
- this._worker.addEventListener("message", resolve);
463
- this._worker.addEventListener("error", reject);
464
- this._worker.postMessage(workerOptions);
465
- } catch (error) {
466
- this._error(error);
467
- }
468
- }
469
- _console({ content, command }) {
470
- console[command].apply(console, JSON.parse(content));
471
- }
472
- _onmessage({ data }) {
473
- if (this["_" + data.target])
474
- this["_" + data.target](data);
475
- }
476
- _error(err) {
477
- this.dispatchEvent(err instanceof ErrorEvent ? new ErrorEvent(err.type, err) : new ErrorEvent("error", { cause: err instanceof Error ? err.cause : err }));
478
- if (!(err instanceof Error)) {
479
- if (err instanceof ErrorEvent) {
480
- err = err.error;
481
- } else {
482
- err = new Error("error", { cause: err });
483
- }
484
- }
485
- console.error(err);
486
- }
487
- _removeListeners() {
488
- if (this._video) {
489
- if (this._ro)
490
- this._ro.unobserve(this._video);
491
- this._video.removeEventListener("timeupdate", this._boundTimeUpdate);
492
- this._video.removeEventListener("progress", this._boundTimeUpdate);
493
- this._video.removeEventListener("waiting", this._boundTimeUpdate);
494
- this._video.removeEventListener("seeking", this._boundTimeUpdate);
495
- this._video.removeEventListener("playing", this._boundTimeUpdate);
496
- this._video.removeEventListener("ratechange", this._boundSetRate);
497
- this._video.removeEventListener("resize", this._boundResize);
498
- }
499
- }
500
- destroy(err) {
501
- if (err)
502
- this._error(err);
503
- if (this._video && this._canvasParent)
504
- this._video.parentNode.removeChild(this._canvasParent);
505
- this._destroyed = true;
506
- this._removeListeners();
507
- this.sendMessage("destroy");
508
- this._worker.terminate();
509
- }
510
- };
511
- let JASSUB = _JASSUB;
512
- __publicField(JASSUB, "_supportsWebAssembly", null);
513
- __publicField(JASSUB, "_hasAlphaBug", null);
514
- export { JASSUB as default };
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => {
4
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ return value;
6
+ };
7
+ if (!("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype) {
8
+ HTMLVideoElement.prototype._rvfcpolyfillmap = {};
9
+ HTMLVideoElement.prototype.requestVideoFrameCallback = function(callback) {
10
+ const quality = this.getVideoPlaybackQuality();
11
+ const baseline = this.mozPresentedFrames || this.mozPaintedFrames || quality.totalVideoFrames - quality.droppedVideoFrames;
12
+ const check = (old, now2) => {
13
+ const newquality = this.getVideoPlaybackQuality();
14
+ const presentedFrames = this.mozPresentedFrames || this.mozPaintedFrames || newquality.totalVideoFrames - newquality.droppedVideoFrames;
15
+ if (presentedFrames > baseline) {
16
+ const processingDuration = this.mozFrameDelay || newquality.totalFrameDelay - quality.totalFrameDelay || 0;
17
+ const timediff = now2 - old;
18
+ callback(now2, {
19
+ presentationTime: now2 + processingDuration * 1e3,
20
+ expectedDisplayTime: now2 + timediff,
21
+ width: this.videoWidth,
22
+ height: this.videoHeight,
23
+ mediaTime: Math.max(0, this.currentTime || 0) + timediff / 1e3,
24
+ presentedFrames,
25
+ processingDuration
26
+ });
27
+ delete this._rvfcpolyfillmap[handle];
28
+ } else {
29
+ this._rvfcpolyfillmap[handle] = requestAnimationFrame((newer) => check(now2, newer));
30
+ }
31
+ };
32
+ const handle = Date.now();
33
+ const now = performance.now();
34
+ this._rvfcpolyfillmap[handle] = requestAnimationFrame((newer) => check(now, newer));
35
+ return handle;
36
+ };
37
+ HTMLVideoElement.prototype.cancelVideoFrameCallback = function(handle) {
38
+ cancelAnimationFrame(this._rvfcpolyfillmap[handle]);
39
+ delete this._rvfcpolyfillmap[handle];
40
+ };
41
+ }
42
+ const _JASSUB = class extends EventTarget {
43
+ constructor(options = {}) {
44
+ var _a, _b, _c;
45
+ super();
46
+ if (!globalThis.Worker) {
47
+ this.destroy("Worker not supported");
48
+ }
49
+ _JASSUB._test();
50
+ const blendMode = options.blendMode || "js";
51
+ const asyncRender = typeof createImageBitmap !== "undefined" && ((_a = options.asyncRender) != null ? _a : true);
52
+ const offscreenRender = typeof OffscreenCanvas !== "undefined" && ((_b = options.offscreenRender) != null ? _b : true);
53
+ this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && ((_c = options.onDemandRender) != null ? _c : true);
54
+ this.timeOffset = options.timeOffset || 0;
55
+ this._video = options.video;
56
+ this._videoHeight = 0;
57
+ this._videoWidth = 0;
58
+ this._canvas = options.canvas;
59
+ if (this._video && !this._canvas) {
60
+ this._canvasParent = document.createElement("div");
61
+ this._canvasParent.className = "JASSUB";
62
+ this._canvasParent.style.position = "relative";
63
+ this._canvas = document.createElement("canvas");
64
+ this._canvas.style.display = "block";
65
+ this._canvas.style.position = "absolute";
66
+ this._canvas.style.pointerEvents = "none";
67
+ this._canvasParent.appendChild(this._canvas);
68
+ if (this._video.nextSibling) {
69
+ this._video.parentNode.insertBefore(this._canvasParent, this._video.nextSibling);
70
+ } else {
71
+ this._video.parentNode.appendChild(this._canvasParent);
72
+ }
73
+ } else if (!this._canvas) {
74
+ this.destroy("Don't know where to render: you should give video or canvas in options.");
75
+ }
76
+ this._bufferCanvas = document.createElement("canvas");
77
+ this._bufferCtx = this._bufferCanvas.getContext("2d", { desynchronized: true, willReadFrequently: true });
78
+ this._canvasctrl = offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas;
79
+ this._ctx = !offscreenRender && this._canvasctrl.getContext("2d", { desynchronized: true });
80
+ this._lastRenderTime = 0;
81
+ this.debug = !!options.debug;
82
+ this.prescaleFactor = options.prescaleFactor || 1;
83
+ this.prescaleHeightLimit = options.prescaleHeightLimit || 1080;
84
+ this.maxRenderHeight = options.maxRenderHeight || 0;
85
+ this._worker = new Worker(_JASSUB._supportsWebAssembly ? options.workerUrl || "jassub-worker.js" : options.legacyWorkerUrl || "jassub-worker-legacy.js");
86
+ this._worker.onmessage = (e) => this._onmessage(e);
87
+ this._worker.onerror = (e) => this._error(e);
88
+ this._loaded = new Promise((resolve) => {
89
+ this._init = () => {
90
+ var _a2, _b2;
91
+ if (this._destroyed)
92
+ return;
93
+ this._worker.postMessage({
94
+ target: "init",
95
+ asyncRender,
96
+ onDemandRender: this._onDemandRender,
97
+ width: this._canvasctrl.width,
98
+ height: this._canvasctrl.height,
99
+ preMain: true,
100
+ blendMode,
101
+ subUrl: options.subUrl,
102
+ subContent: options.subContent || null,
103
+ fonts: options.fonts || [],
104
+ availableFonts: options.availableFonts || { "liberation sans": "./default.woff2" },
105
+ fallbackFont: options.fallbackFont || "liberation sans",
106
+ debug: this.debug,
107
+ targetFps: options.targetFps || 24,
108
+ dropAllAnimations: options.dropAllAnimations,
109
+ libassMemoryLimit: options.libassMemoryLimit || 0,
110
+ libassGlyphLimit: options.libassGlyphLimit || 0,
111
+ hasAlphaBug: _JASSUB._hasAlphaBug,
112
+ useLocalFonts: "queryLocalFonts" in self && ((_a2 = options.useLocalFonts) != null ? _a2 : true)
113
+ });
114
+ if (offscreenRender === true)
115
+ this.sendMessage("offscreenCanvas", null, [this._canvasctrl]);
116
+ this._boundResize = this.resize.bind(this);
117
+ this._boundTimeUpdate = this._timeupdate.bind(this);
118
+ this._boundSetRate = this.setRate.bind(this);
119
+ if (this._video)
120
+ this.setVideo(options.video);
121
+ if (this._onDemandRender) {
122
+ this.busy = false;
123
+ this._lastDemandTime = null;
124
+ (_b2 = this._video) == null ? void 0 : _b2.requestVideoFrameCallback(this._handleRVFC.bind(this));
125
+ }
126
+ resolve();
127
+ };
128
+ });
129
+ }
130
+ static _test() {
131
+ if (_JASSUB._supportsWebAssembly !== null)
132
+ return null;
133
+ const canvas1 = document.createElement("canvas");
134
+ const ctx1 = canvas1.getContext("2d", { willReadFrequently: true });
135
+ if (typeof ImageData.prototype.constructor === "function") {
136
+ try {
137
+ new ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1);
138
+ } catch (e) {
139
+ console.log("detected that ImageData is not constructable despite browser saying so");
140
+ self.ImageData = function(data, width, height) {
141
+ const imageData = ctx1.createImageData(width, height);
142
+ if (data)
143
+ imageData.data.set(data);
144
+ return imageData;
145
+ };
146
+ }
147
+ }
148
+ try {
149
+ if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
150
+ const module = new WebAssembly.Module(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0));
151
+ if (module instanceof WebAssembly.Module)
152
+ _JASSUB._supportsWebAssembly = new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
153
+ }
154
+ } catch (e) {
155
+ _JASSUB._supportsWebAssembly = false;
156
+ }
157
+ const canvas2 = document.createElement("canvas");
158
+ const ctx2 = canvas2.getContext("2d", { willReadFrequently: true });
159
+ canvas1.width = canvas2.width = 1;
160
+ canvas1.height = canvas2.height = 1;
161
+ ctx1.clearRect(0, 0, 1, 1);
162
+ ctx2.clearRect(0, 0, 1, 1);
163
+ const prePut = ctx2.getImageData(0, 0, 1, 1).data;
164
+ ctx1.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0);
165
+ ctx2.drawImage(canvas1, 0, 0);
166
+ const postPut = ctx2.getImageData(0, 0, 1, 1).data;
167
+ _JASSUB._hasAlphaBug = prePut[1] !== postPut[1];
168
+ if (_JASSUB._hasAlphaBug)
169
+ console.log("Detected a browser having issue with transparent pixels, applying workaround");
170
+ canvas1.remove();
171
+ canvas2.remove();
172
+ }
173
+ resize(width = 0, height = 0, top = 0, left = 0, force = ((_a) => (_a = this._video) == null ? void 0 : _a.paused)()) {
174
+ if ((!width || !height) && this._video) {
175
+ const videoSize = this._getVideoPosition();
176
+ let renderSize = null;
177
+ if (this._videoWidth) {
178
+ const widthRatio = this._video.videoWidth / this._videoWidth;
179
+ const heightRatio = this._video.videoHeight / this._videoHeight;
180
+ renderSize = this._computeCanvasSize((videoSize.width || 0) / widthRatio, (videoSize.height || 0) / heightRatio);
181
+ } else {
182
+ renderSize = this._computeCanvasSize(videoSize.width || 0, videoSize.height || 0);
183
+ }
184
+ width = renderSize.width;
185
+ height = renderSize.height;
186
+ if (this._canvasParent) {
187
+ top = videoSize.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top);
188
+ left = videoSize.x;
189
+ }
190
+ this._canvas.style.width = videoSize.width + "px";
191
+ this._canvas.style.height = videoSize.height + "px";
192
+ }
193
+ this._canvas.style.top = top + "px";
194
+ this._canvas.style.left = left + "px";
195
+ this.sendMessage("canvas", { width, height, force: force && this.busy === false });
196
+ }
197
+ _getVideoPosition(width = this._video.videoWidth, height = this._video.videoHeight) {
198
+ const videoRatio = width / height;
199
+ const { offsetWidth, offsetHeight } = this._video;
200
+ const elementRatio = offsetWidth / offsetHeight;
201
+ width = offsetWidth;
202
+ height = offsetHeight;
203
+ if (elementRatio > videoRatio) {
204
+ width = Math.floor(offsetHeight * videoRatio);
205
+ } else {
206
+ height = Math.floor(offsetWidth / videoRatio);
207
+ }
208
+ const x = (offsetWidth - width) / 2;
209
+ const y = (offsetHeight - height) / 2;
210
+ return { width, height, x, y };
211
+ }
212
+ _computeCanvasSize(width = 0, height = 0) {
213
+ const scalefactor = this.prescaleFactor <= 0 ? 1 : this.prescaleFactor;
214
+ const ratio = self.devicePixelRatio || 1;
215
+ width = width * ratio;
216
+ height = height * ratio;
217
+ if (height <= 0 || width <= 0) {
218
+ width = 0;
219
+ height = 0;
220
+ } else {
221
+ const sgn = scalefactor < 1 ? -1 : 1;
222
+ let newH = height * ratio;
223
+ if (sgn * newH * scalefactor <= sgn * this.prescaleHeightLimit) {
224
+ newH *= scalefactor;
225
+ } else if (sgn * newH < sgn * this.prescaleHeightLimit) {
226
+ newH = this.prescaleHeightLimit;
227
+ }
228
+ if (this.maxRenderHeight > 0 && newH > this.maxRenderHeight)
229
+ newH = this.maxRenderHeight;
230
+ width *= newH / height;
231
+ height = newH;
232
+ }
233
+ return { width, height };
234
+ }
235
+ _timeupdate({ type }) {
236
+ const eventmap = {
237
+ seeking: true,
238
+ waiting: true,
239
+ playing: false
240
+ };
241
+ const playing = eventmap[type];
242
+ if (playing != null)
243
+ this._playstate = playing;
244
+ this.setCurrentTime(this._video.paused || this._playstate, this._video.currentTime + this.timeOffset);
245
+ }
246
+ setVideo(video) {
247
+ if (video instanceof HTMLVideoElement) {
248
+ this._removeListeners();
249
+ this._video = video;
250
+ if (this._onDemandRender) {
251
+ this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
252
+ } else {
253
+ this._playstate = video.paused;
254
+ video.addEventListener("timeupdate", this._boundTimeUpdate, false);
255
+ video.addEventListener("progress", this._boundTimeUpdate, false);
256
+ video.addEventListener("waiting", this._boundTimeUpdate, false);
257
+ video.addEventListener("seeking", this._boundTimeUpdate, false);
258
+ video.addEventListener("playing", this._boundTimeUpdate, false);
259
+ video.addEventListener("ratechange", this._boundSetRate, false);
260
+ video.addEventListener("resize", this._boundResize);
261
+ }
262
+ if (video.videoWidth > 0)
263
+ this.resize();
264
+ if (typeof ResizeObserver !== "undefined") {
265
+ if (!this._ro)
266
+ this._ro = new ResizeObserver(() => this.resize());
267
+ this._ro.observe(video);
268
+ }
269
+ } else {
270
+ this._error("Video element invalid!");
271
+ }
272
+ }
273
+ runBenchmark() {
274
+ this.sendMessage("runBenchmark");
275
+ }
276
+ setTrackByUrl(url) {
277
+ this.sendMessage("setTrackByUrl", { url });
278
+ }
279
+ setTrack(content) {
280
+ this.sendMessage("setTrack", { content });
281
+ }
282
+ freeTrack() {
283
+ this.sendMessage("freeTrack");
284
+ }
285
+ setIsPaused(isPaused) {
286
+ this.sendMessage("video", { isPaused });
287
+ }
288
+ setRate(rate) {
289
+ this.sendMessage("video", { rate });
290
+ }
291
+ setCurrentTime(isPaused, currentTime, rate) {
292
+ this.sendMessage("video", { isPaused, currentTime, rate });
293
+ }
294
+ createEvent(event) {
295
+ this.sendMessage("createEvent", { event });
296
+ }
297
+ setEvent(event, index) {
298
+ this.sendMessage("setEvent", { event, index });
299
+ }
300
+ removeEvent(index) {
301
+ this.sendMessage("removeEvent", { index });
302
+ }
303
+ getEvents(callback) {
304
+ this._fetchFromWorker({
305
+ target: "getEvents"
306
+ }, (err, { events }) => {
307
+ callback(err, events);
308
+ });
309
+ }
310
+ createStyle(style) {
311
+ this.sendMessage("createStyle", { style });
312
+ }
313
+ setStyle(event, index) {
314
+ this.sendMessage("setStyle", { event, index });
315
+ }
316
+ removeStyle(index) {
317
+ this.sendMessage("removeStyle", { index });
318
+ }
319
+ getStyles(callback) {
320
+ this._fetchFromWorker({
321
+ target: "getStyles"
322
+ }, (err, { styles }) => {
323
+ callback(err, styles);
324
+ });
325
+ }
326
+ addFont(font) {
327
+ this.sendMessage("addFont", { font });
328
+ }
329
+ _sendLocalFont(name) {
330
+ try {
331
+ queryLocalFonts().then((fontData) => {
332
+ const font = fontData == null ? void 0 : fontData.find((obj) => obj.fullName.toLowerCase() === name);
333
+ if (font) {
334
+ font.blob().then((blob) => {
335
+ blob.arrayBuffer().then((buffer) => {
336
+ this.addFont(new Uint8Array(buffer));
337
+ });
338
+ });
339
+ }
340
+ });
341
+ } catch (e) {
342
+ console.warn("Local fonts API:", e);
343
+ }
344
+ }
345
+ _getLocalFont({ font }) {
346
+ var _a;
347
+ try {
348
+ if ((_a = navigator == null ? void 0 : navigator.permissions) == null ? void 0 : _a.query) {
349
+ navigator.permissions.query({ name: "local-fonts" }).then((permission) => {
350
+ if (permission.state === "granted") {
351
+ this._sendLocalFont(font);
352
+ }
353
+ });
354
+ } else {
355
+ this._sendLocalFont(font);
356
+ }
357
+ } catch (e) {
358
+ console.warn("Local fonts API:", e);
359
+ }
360
+ }
361
+ _unbusy() {
362
+ if (this._lastDemandTime) {
363
+ this._demandRender(this._lastDemandTime);
364
+ } else {
365
+ this.busy = false;
366
+ }
367
+ }
368
+ _handleRVFC(now, { mediaTime, width, height }) {
369
+ if (this._destroyed)
370
+ return null;
371
+ if (this.busy) {
372
+ this._lastDemandTime = { mediaTime, width, height };
373
+ } else {
374
+ this.busy = true;
375
+ this._demandRender({ mediaTime, width, height });
376
+ }
377
+ this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
378
+ }
379
+ _demandRender({ mediaTime, width, height }) {
380
+ this._lastDemandTime = null;
381
+ if (width !== this._videoWidth || height !== this._videoHeight) {
382
+ this._videoWidth = width;
383
+ this._videoHeight = height;
384
+ this.resize();
385
+ }
386
+ this.sendMessage("demand", { time: mediaTime + this.timeOffset });
387
+ }
388
+ _render({ images, async, times, width, height }) {
389
+ this._unbusy();
390
+ const drawStartTime = Date.now();
391
+ if (this._canvasctrl.width !== width || this._canvasctrl.height !== height) {
392
+ this._canvasctrl.width = width;
393
+ this._canvasctrl.height = height;
394
+ }
395
+ this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height);
396
+ for (const image of images) {
397
+ if (image.image) {
398
+ if (async) {
399
+ this._ctx.drawImage(image.image, image.x, image.y);
400
+ image.image.close();
401
+ } else {
402
+ this._bufferCanvas.width = image.w;
403
+ this._bufferCanvas.height = image.h;
404
+ this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(image.image)), image.w, image.h), 0, 0);
405
+ this._ctx.drawImage(this._bufferCanvas, image.x, image.y);
406
+ }
407
+ }
408
+ }
409
+ if (this.debug) {
410
+ times.drawTime = Date.now() - drawStartTime;
411
+ let total = 0;
412
+ for (const key in times)
413
+ total += times[key];
414
+ console.log("Bitmaps: " + images.length + " Total: " + Math.round(total) + "ms", times);
415
+ }
416
+ }
417
+ _fixAlpha(uint8) {
418
+ if (_JASSUB._hasAlphaBug) {
419
+ for (let j = 3; j < uint8.length; j += 4) {
420
+ uint8[j] = uint8[j] > 1 ? uint8[j] : 1;
421
+ }
422
+ }
423
+ return uint8;
424
+ }
425
+ _ready() {
426
+ this._init();
427
+ this.dispatchEvent(new CustomEvent("ready"));
428
+ }
429
+ async sendMessage(target, data = {}, transferable) {
430
+ await this._loaded;
431
+ if (transferable) {
432
+ this._worker.postMessage({
433
+ target,
434
+ transferable,
435
+ ...data
436
+ }, [...transferable]);
437
+ } else {
438
+ this._worker.postMessage({
439
+ target,
440
+ ...data
441
+ });
442
+ }
443
+ }
444
+ _fetchFromWorker(workerOptions, callback) {
445
+ try {
446
+ const target = workerOptions.target;
447
+ const timeout = setTimeout(() => {
448
+ reject(new Error("Error: Timeout while try to fetch " + target));
449
+ }, 5e3);
450
+ const resolve = ({ data }) => {
451
+ if (data.target === target) {
452
+ callback(null, data);
453
+ this._worker.removeEventListener("message", resolve);
454
+ this._worker.removeEventListener("error", reject);
455
+ clearTimeout(timeout);
456
+ }
457
+ };
458
+ const reject = (event) => {
459
+ callback(event);
460
+ this._worker.removeEventListener("message", resolve);
461
+ this._worker.removeEventListener("error", reject);
462
+ clearTimeout(timeout);
463
+ };
464
+ this._worker.addEventListener("message", resolve);
465
+ this._worker.addEventListener("error", reject);
466
+ this._worker.postMessage(workerOptions);
467
+ } catch (error) {
468
+ this._error(error);
469
+ }
470
+ }
471
+ _console({ content, command }) {
472
+ console[command].apply(console, JSON.parse(content));
473
+ }
474
+ _onmessage({ data }) {
475
+ if (this["_" + data.target])
476
+ this["_" + data.target](data);
477
+ }
478
+ _error(err) {
479
+ this.dispatchEvent(err instanceof ErrorEvent ? new ErrorEvent(err.type, err) : new ErrorEvent("error", { cause: err instanceof Error ? err.cause : err }));
480
+ if (!(err instanceof Error)) {
481
+ if (err instanceof ErrorEvent) {
482
+ err = err.error;
483
+ } else {
484
+ err = new Error("error", { cause: err });
485
+ }
486
+ }
487
+ console.error(err);
488
+ }
489
+ _removeListeners() {
490
+ if (this._video) {
491
+ if (this._ro)
492
+ this._ro.unobserve(this._video);
493
+ this._video.removeEventListener("timeupdate", this._boundTimeUpdate);
494
+ this._video.removeEventListener("progress", this._boundTimeUpdate);
495
+ this._video.removeEventListener("waiting", this._boundTimeUpdate);
496
+ this._video.removeEventListener("seeking", this._boundTimeUpdate);
497
+ this._video.removeEventListener("playing", this._boundTimeUpdate);
498
+ this._video.removeEventListener("ratechange", this._boundSetRate);
499
+ this._video.removeEventListener("resize", this._boundResize);
500
+ }
501
+ }
502
+ destroy(err) {
503
+ if (err)
504
+ this._error(err);
505
+ if (this._video && this._canvasParent)
506
+ this._video.parentNode.removeChild(this._canvasParent);
507
+ this._destroyed = true;
508
+ this._removeListeners();
509
+ this.sendMessage("destroy");
510
+ this._worker.terminate();
511
+ }
512
+ };
513
+ let JASSUB = _JASSUB;
514
+ __publicField(JASSUB, "_supportsWebAssembly", null);
515
+ __publicField(JASSUB, "_hasAlphaBug", null);
516
+ export { JASSUB as default };