jassub 1.6.4 → 1.7.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,21 +1,21 @@
1
1
  !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype && (HTMLVideoElement.prototype._rvfcpolyfillmap = {}, HTMLVideoElement.prototype.requestVideoFrameCallback = function(_) {
2
- const e = this.getVideoPlaybackQuality(), t = this.mozPresentedFrames || this.mozPaintedFrames || e.totalVideoFrames - e.droppedVideoFrames, s = (n, a) => {
3
- const o = this.getVideoPlaybackQuality(), h = this.mozPresentedFrames || this.mozPaintedFrames || o.totalVideoFrames - o.droppedVideoFrames;
4
- if (h > t) {
5
- const d = this.mozFrameDelay || o.totalFrameDelay - e.totalFrameDelay || 0, c = a - n;
6
- _(a, {
7
- presentationTime: a + d * 1e3,
8
- expectedDisplayTime: a + c,
2
+ const e = this.getVideoPlaybackQuality(), t = this.mozPresentedFrames || this.mozPaintedFrames || e.totalVideoFrames - e.droppedVideoFrames, s = (r, i) => {
3
+ const o = this.getVideoPlaybackQuality(), d = this.mozPresentedFrames || this.mozPaintedFrames || o.totalVideoFrames - o.droppedVideoFrames;
4
+ if (d > t) {
5
+ const l = this.mozFrameDelay || o.totalFrameDelay - e.totalFrameDelay || 0, c = i - r;
6
+ _(i, {
7
+ presentationTime: i + l * 1e3,
8
+ expectedDisplayTime: i + c,
9
9
  width: this.videoWidth,
10
10
  height: this.videoHeight,
11
11
  mediaTime: Math.max(0, this.currentTime || 0) + c / 1e3,
12
- presentedFrames: h,
13
- processingDuration: d
14
- }), delete this._rvfcpolyfillmap[i];
12
+ presentedFrames: d,
13
+ processingDuration: l
14
+ }), delete this._rvfcpolyfillmap[a];
15
15
  } else
16
- this._rvfcpolyfillmap[i] = requestAnimationFrame((d) => s(a, d));
17
- }, i = Date.now(), r = performance.now();
18
- return this._rvfcpolyfillmap[i] = requestAnimationFrame((n) => s(r, n)), i;
16
+ this._rvfcpolyfillmap[a] = requestAnimationFrame((l) => s(i, l));
17
+ }, a = Date.now(), n = performance.now();
18
+ return this._rvfcpolyfillmap[a] = requestAnimationFrame((r) => s(n, r)), a;
19
19
  }, HTMLVideoElement.prototype.cancelVideoFrameCallback = function(_) {
20
20
  cancelAnimationFrame(this._rvfcpolyfillmap[_]), delete this._rvfcpolyfillmap[_];
21
21
  });
@@ -26,7 +26,7 @@ const m = {
26
26
  // alias BT.601 PAL... whats the difference?
27
27
  smpte170m: "BT601"
28
28
  // alias BT.601 NTSC... whats the difference?
29
- }, v = {
29
+ }, f = {
30
30
  BT601: {
31
31
  BT709: "1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418"
32
32
  },
@@ -42,7 +42,7 @@ const m = {
42
42
  BT601: "0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973"
43
43
  }
44
44
  };
45
- class l extends EventTarget {
45
+ class h extends EventTarget {
46
46
  /**
47
47
  * @param {Object} options Settings object.
48
48
  * @param {HTMLVideoElement} options.video Video to use as target for rendering and event listeners. Optional if canvas is specified instead.
@@ -58,8 +58,11 @@ class l extends EventTarget {
58
58
  * @param {Number} [options.prescaleHeightLimit=1080] The height in pixels beyond which the subtitles canvas won't be prescaled.
59
59
  * @param {Number} [options.maxRenderHeight=0] The maximum rendering height in pixels of the subtitles canvas. Beyond this subtitles will be upscaled by the browser.
60
60
  * @param {Boolean} [options.dropAllAnimations=false] Attempt to discard all animated tags. Enabling this may severly mangle complex subtitles and should only be considered as an last ditch effort of uncertain success for hardware otherwise incapable of displaing anything. Will not reliably work with manually edited or allocated events.
61
+ * @param {Boolean} [options.dropAllBlur=false] The holy grail of performance gains. If heavy TS lags a lot, disabling this will make it ~x10 faster. This drops blur from all added subtitle tracks making most text and backgrounds look sharper, this is way less intrusive than dropping all animations, while still offering major performance gains.
61
62
  * @param {String} [options.workerUrl='jassub-worker.js'] The URL of the worker.
62
- * @param {String} [options.legacyWorkerUrl='jassub-worker-legacy.js'] The URL of the legacy worker. Only loaded if the browser doesn't support WASM.
63
+ * @param {String} [options.wasmUrl='jassub-worker.wasm'] The URL of the worker WASM.
64
+ * @param {String} [options.legacyWasmUrl='jassub-worker.wasm.js'] The URL of the worker WASM. Only loaded if the browser doesn't support WASM.
65
+ * @param {String} options.modernWasmUrl The URL of the modern worker WASM. This includes faster ASM instructions, but is only supported by newer browsers, disabled if the URL isn't defined.
63
66
  * @param {String} [options.subUrl=options.subContent] The URL of the subtitle file to play.
64
67
  * @param {String} [options.subContent=options.subUrl] The content of the subtitle file to play.
65
68
  * @param {String[]|Uint8Array[]} [options.fonts] An array of links or Uint8Arrays to the fonts used in the subtitle. If Uint8Array is used the array is copied, not referenced. This forces all the fonts in this array to be loaded by the renderer, regardless of if they are used.
@@ -70,55 +73,61 @@ class l extends EventTarget {
70
73
  * @param {Number} [options.libassGlyphLimit] libass glyph cache memory limit in MiB (approximate).
71
74
  */
72
75
  constructor(e = {}) {
73
- super(), globalThis.Worker || this.destroy("Worker not supported"), l._test(), this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? !0), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? !0), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas ? (this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._createCanvas(), this._video.nextSibling ? this._video.parentNode.insertBefore(this._canvasParent, this._video.nextSibling) : this._video.parentNode.appendChild(this._canvasParent)) : this._canvas || this.destroy("Don't know where to render: you should give video or canvas in options."), this._bufferCanvas = document.createElement("canvas"), this._bufferCtx = this._bufferCanvas.getContext("2d"), this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas, this._ctx = !this._offscreenRender && this._canvasctrl.getContext("2d"), this._lastRenderTime = 0, this.debug = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (t) => this._onmessage(t), this._worker.onerror = (t) => this._error(t), this._loaded = new Promise((t) => {
74
- this._init = () => {
75
- this._destroyed || (this._worker.postMessage({
76
- target: "init",
77
- asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? !0),
78
- onDemandRender: this._onDemandRender,
79
- width: this._canvasctrl.width || 0,
80
- height: this._canvasctrl.height || 0,
81
- blendMode: e.blendMode || "js",
82
- subUrl: e.subUrl,
83
- subContent: e.subContent || null,
84
- fonts: e.fonts || [],
85
- availableFonts: e.availableFonts || { "liberation sans": "./default.woff2" },
86
- fallbackFont: e.fallbackFont || "liberation sans",
87
- debug: this.debug,
88
- targetFps: e.targetFps || 24,
89
- dropAllAnimations: e.dropAllAnimations,
90
- libassMemoryLimit: e.libassMemoryLimit || 0,
91
- libassGlyphLimit: e.libassGlyphLimit || 0,
92
- useLocalFonts: "queryLocalFonts" in self && (e.useLocalFonts ?? !0)
93
- }), this._offscreenRender === !0 && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]), this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = this.setRate.bind(this), this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = !1, this._lastDemandTime = null), t());
94
- };
95
- });
76
+ super(), globalThis.Worker || this.destroy("Worker not supported"), this._loaded = new Promise((t) => {
77
+ this._init = t;
78
+ }), h._test(), this._onDemandRender = "requestVideoFrameCallback" in HTMLVideoElement.prototype && (e.onDemandRender ?? !0), this._offscreenRender = "transferControlToOffscreen" in HTMLCanvasElement.prototype && !e.canvas && (e.offscreenRender ?? !0), this.timeOffset = e.timeOffset || 0, this._video = e.video, this._videoHeight = 0, this._videoWidth = 0, this._videoColorSpace = null, this._canvas = e.canvas, this._video && !this._canvas ? (this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._createCanvas(), this._video.nextSibling ? this._video.parentNode.insertBefore(this._canvasParent, this._video.nextSibling) : this._video.parentNode.appendChild(this._canvasParent)) : this._canvas || this.destroy("Don't know where to render: you should give video or canvas in options."), this._bufferCanvas = document.createElement("canvas"), this._bufferCtx = this._bufferCanvas.getContext("2d"), this._canvasctrl = this._offscreenRender ? this._canvas.transferControlToOffscreen() : this._canvas, this._ctx = !this._offscreenRender && this._canvasctrl.getContext("2d"), this._lastRenderTime = 0, this.debug = !!e.debug, this.prescaleFactor = e.prescaleFactor || 1, this.prescaleHeightLimit = e.prescaleHeightLimit || 1080, this.maxRenderHeight = e.maxRenderHeight || 0, this._boundResize = this.resize.bind(this), this._boundTimeUpdate = this._timeupdate.bind(this), this._boundSetRate = this.setRate.bind(this), this._boundUpdateColorSpace = this._updateColorSpace.bind(this), this._video && this.setVideo(e.video), this._onDemandRender && (this.busy = !1, this._lastDemandTime = null), this._worker = new Worker(e.workerUrl || "jassub-worker.js"), this._worker.onmessage = (t) => this._onmessage(t), this._worker.onerror = (t) => this._error(t), this._worker.postMessage({
79
+ target: "init",
80
+ wasmUrl: h._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl || "jassub-worker.wasm",
81
+ legacyWasmUrl: e.legacyWasmUrl || "jassub-worker.wasm.js",
82
+ asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? !0),
83
+ onDemandRender: this._onDemandRender,
84
+ width: this._canvasctrl.width || 0,
85
+ height: this._canvasctrl.height || 0,
86
+ blendMode: e.blendMode || "js",
87
+ subUrl: e.subUrl,
88
+ subContent: e.subContent || null,
89
+ fonts: e.fonts || [],
90
+ availableFonts: e.availableFonts || { "liberation sans": "./default.woff2" },
91
+ fallbackFont: e.fallbackFont || "liberation sans",
92
+ debug: this.debug,
93
+ targetFps: e.targetFps || 24,
94
+ dropAllAnimations: e.dropAllAnimations,
95
+ dropAllBlur: e.dropAllBlur,
96
+ libassMemoryLimit: e.libassMemoryLimit || 0,
97
+ libassGlyphLimit: e.libassGlyphLimit || 0,
98
+ useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? !0)
99
+ }), this._offscreenRender === !0 && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]);
96
100
  }
97
101
  _createCanvas() {
98
102
  this._canvas = document.createElement("canvas"), this._canvas.style.display = "block", this._canvas.style.position = "absolute", this._canvas.style.pointerEvents = "none", this._canvasParent.appendChild(this._canvas);
99
103
  }
100
104
  // test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page
101
- static _supportsWebAssembly = null;
105
+ static _supportsSIMD = null;
102
106
  static _hasAlphaBug = null;
103
107
  static _test() {
104
- if (l._supportsWebAssembly !== null)
108
+ if (h._supportsSIMD !== null)
105
109
  return null;
110
+ try {
111
+ h._supportsSIMD = WebAssembly.validate(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11));
112
+ } catch {
113
+ h._supportsSIMD = !1;
114
+ }
106
115
  const e = document.createElement("canvas"), t = e.getContext("2d", { willReadFrequently: !0 });
107
116
  if (typeof ImageData.prototype.constructor == "function")
108
117
  try {
109
118
  new ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1);
110
119
  } catch {
111
- console.log("Detected that ImageData is not constructable despite browser saying so"), self.ImageData = function(o, h, d) {
112
- const c = t.createImageData(h, d);
120
+ console.log("Detected that ImageData is not constructable despite browser saying so"), self.ImageData = function(o, d, l) {
121
+ const c = t.createImageData(d, l);
113
122
  return o && c.data.set(o), c;
114
123
  };
115
124
  }
116
- const s = document.createElement("canvas"), i = s.getContext("2d", { willReadFrequently: !0 });
117
- e.width = s.width = 1, e.height = s.height = 1, t.clearRect(0, 0, 1, 1), i.clearRect(0, 0, 1, 1);
118
- const r = i.getImageData(0, 0, 1, 1).data;
119
- t.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0), i.drawImage(e, 0, 0);
120
- const n = i.getImageData(0, 0, 1, 1).data;
121
- l._hasAlphaBug = r[1] !== n[1], l._hasAlphaBug && console.log("Detected a browser having issue with transparent pixels, applying workaround"), e.remove(), s.remove();
125
+ const s = document.createElement("canvas"), a = s.getContext("2d", { willReadFrequently: !0 });
126
+ e.width = s.width = 1, e.height = s.height = 1, t.clearRect(0, 0, 1, 1), a.clearRect(0, 0, 1, 1);
127
+ const n = a.getImageData(0, 0, 1, 1).data;
128
+ t.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0), a.drawImage(e, 0, 0);
129
+ const r = a.getImageData(0, 0, 1, 1).data;
130
+ h._hasAlphaBug = n[1] !== r[1], h._hasAlphaBug && console.log("Detected a browser having issue with transparent pixels, applying workaround"), e.remove(), s.remove();
122
131
  }
123
132
  /**
124
133
  * Resize the canvas to given parameters. Auto-generated if values are ommited.
@@ -128,33 +137,33 @@ class l extends EventTarget {
128
137
  * @param {Number} [left=0]
129
138
  * @param {Boolean} [force=false]
130
139
  */
131
- resize(e = 0, t = 0, s = 0, i = 0, r = this._video?.paused) {
140
+ resize(e = 0, t = 0, s = 0, a = 0, n = this._video?.paused) {
132
141
  if ((!e || !t) && this._video) {
133
- const n = this._getVideoPosition();
134
- let a = null;
142
+ const r = this._getVideoPosition();
143
+ let i = null;
135
144
  if (this._videoWidth) {
136
- const o = this._video.videoWidth / this._videoWidth, h = this._video.videoHeight / this._videoHeight;
137
- a = this._computeCanvasSize((n.width || 0) / o, (n.height || 0) / h);
145
+ const o = this._video.videoWidth / this._videoWidth, d = this._video.videoHeight / this._videoHeight;
146
+ i = this._computeCanvasSize((r.width || 0) / o, (r.height || 0) / d);
138
147
  } else
139
- a = this._computeCanvasSize(n.width || 0, n.height || 0);
140
- e = a.width, t = a.height, this._canvasParent && (s = n.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), i = n.x), this._canvas.style.width = n.width + "px", this._canvas.style.height = n.height + "px";
148
+ i = this._computeCanvasSize(r.width || 0, r.height || 0);
149
+ e = i.width, t = i.height, this._canvasParent && (s = r.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), a = r.x), this._canvas.style.width = r.width + "px", this._canvas.style.height = r.height + "px";
141
150
  }
142
- this._canvas.style.top = s + "px", this._canvas.style.left = i + "px", r && this.busy === !1 ? this.busy = !0 : r = !1, this.sendMessage("canvas", { width: e, height: t, force: r });
151
+ this._canvas.style.top = s + "px", this._canvas.style.left = a + "px", n && this.busy === !1 ? this.busy = !0 : n = !1, this.sendMessage("canvas", { width: e, height: t, force: n });
143
152
  }
144
153
  _getVideoPosition(e = this._video.videoWidth, t = this._video.videoHeight) {
145
- const s = e / t, { offsetWidth: i, offsetHeight: r } = this._video, n = i / r;
146
- e = i, t = r, n > s ? e = Math.floor(r * s) : t = Math.floor(i / s);
147
- const a = (i - e) / 2, o = (r - t) / 2;
148
- return { width: e, height: t, x: a, y: o };
154
+ const s = e / t, { offsetWidth: a, offsetHeight: n } = this._video, r = a / n;
155
+ e = a, t = n, r > s ? e = Math.floor(n * s) : t = Math.floor(a / s);
156
+ const i = (a - e) / 2, o = (n - t) / 2;
157
+ return { width: e, height: t, x: i, y: o };
149
158
  }
150
159
  _computeCanvasSize(e = 0, t = 0) {
151
- const s = this.prescaleFactor <= 0 ? 1 : this.prescaleFactor, i = self.devicePixelRatio || 1;
152
- if (e = e * i, t = t * i, t <= 0 || e <= 0)
160
+ const s = this.prescaleFactor <= 0 ? 1 : this.prescaleFactor, a = self.devicePixelRatio || 1;
161
+ if (e = e * a, t = t * a, t <= 0 || e <= 0)
153
162
  e = 0, t = 0;
154
163
  else {
155
- const r = s < 1 ? -1 : 1;
156
- let n = t * i;
157
- r * n * s <= r * this.prescaleHeightLimit ? n *= s : r * n < r * this.prescaleHeightLimit && (n = this.prescaleHeightLimit), this.maxRenderHeight > 0 && n > this.maxRenderHeight && (n = this.maxRenderHeight), e *= n / t, t = n;
164
+ const n = s < 1 ? -1 : 1;
165
+ let r = t * a;
166
+ n * r * s <= n * this.prescaleHeightLimit ? r *= s : n * r < n * this.prescaleHeightLimit && (r = this.prescaleHeightLimit), this.maxRenderHeight > 0 && r > this.maxRenderHeight && (r = this.maxRenderHeight), e *= r / t, t = r;
158
167
  }
159
168
  return { width: e, height: t };
160
169
  }
@@ -339,10 +348,10 @@ class l extends EventTarget {
339
348
  _sendLocalFont(e) {
340
349
  try {
341
350
  queryLocalFonts().then((t) => {
342
- const s = t?.find((i) => i.fullName.toLowerCase() === e);
343
- s && s.blob().then((i) => {
344
- i.arrayBuffer().then((r) => {
345
- this.addFont(new Uint8Array(r));
351
+ const s = t?.find((a) => a.fullName.toLowerCase() === e);
352
+ s && s.blob().then((a) => {
353
+ a.arrayBuffer().then((n) => {
354
+ this.addFont(new Uint8Array(n));
346
355
  });
347
356
  });
348
357
  });
@@ -362,10 +371,10 @@ class l extends EventTarget {
362
371
  _unbusy() {
363
372
  this._lastDemandTime ? this._demandRender(this._lastDemandTime) : this.busy = !1;
364
373
  }
365
- _handleRVFC(e, { mediaTime: t, width: s, height: i }) {
374
+ _handleRVFC(e, { mediaTime: t, width: s, height: a }) {
366
375
  if (this._destroyed)
367
376
  return null;
368
- this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: i } : (this.busy = !0, this._demandRender({ mediaTime: t, width: s, height: i })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
377
+ this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: a } : (this.busy = !0, this._demandRender({ mediaTime: t, width: s, height: a })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
369
378
  }
370
379
  _demandRender({ mediaTime: e, width: t, height: s }) {
371
380
  this._lastDemandTime = null, (t !== this._videoWidth || s !== this._videoHeight) && (this._videoWidth = t, this._videoHeight = s, this.resize()), this.sendMessage("demand", { time: e + this.timeOffset });
@@ -398,24 +407,24 @@ class l extends EventTarget {
398
407
  * @param {String} videoColorSpace Video color space. One of: BT601 BT709
399
408
  */
400
409
  _verifyColorSpace({ subtitleColorSpace: e, videoColorSpace: t = this._videoColorSpace }) {
401
- !e || !t || e !== t && (this._detachOffscreen(), this._ctx.filter = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='${v[e][t]} 0 0 0 0 0 1 0'/></filter></svg>#f")`);
410
+ !e || !t || e !== t && (this._detachOffscreen(), this._ctx.filter = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='${f[e][t]} 0 0 0 0 0 1 0'/></filter></svg>#f")`);
402
411
  }
403
- _render({ images: e, asyncRender: t, times: s, width: i, height: r, colorSpace: n }) {
404
- this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== i || this._canvasctrl.height !== r) && (this._canvasctrl.width = i, this._canvasctrl.height = r, this._verifyColorSpace({ subtitleColorSpace: n })), this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height);
405
- for (const a of e)
406
- a.image && (t ? (this._ctx.drawImage(a.image, a.x, a.y), a.image.close()) : (this._bufferCanvas.width = a.w, this._bufferCanvas.height = a.h, this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(a.image)), a.w, a.h), 0, 0), this._ctx.drawImage(this._bufferCanvas, a.x, a.y)));
412
+ _render({ images: e, asyncRender: t, times: s, width: a, height: n, colorSpace: r }) {
413
+ this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== a || this._canvasctrl.height !== n) && (this._canvasctrl.width = a, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: r })), this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height);
414
+ for (const i of e)
415
+ i.image && (t ? (this._ctx.drawImage(i.image, i.x, i.y), i.image.close()) : (this._bufferCanvas.width = i.w, this._bufferCanvas.height = i.h, this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(i.image)), i.w, i.h), 0, 0), this._ctx.drawImage(this._bufferCanvas, i.x, i.y)));
407
416
  if (this.debug) {
408
417
  s.JSRenderTime = Date.now() - s.JSRenderTime - s.IPCTime;
409
- let a = 0;
418
+ let i = 0;
410
419
  const o = s.bitmaps || e.length;
411
420
  delete s.bitmaps;
412
- for (const h in s)
413
- a += s[h];
414
- console.log("Bitmaps: " + o + " Total: " + (a | 0) + "ms", s);
421
+ for (const d in s)
422
+ i += s[d];
423
+ console.log("Bitmaps: " + o + " Total: " + (i | 0) + "ms", s);
415
424
  }
416
425
  }
417
426
  _fixAlpha(e) {
418
- if (l._hasAlphaBug)
427
+ if (h._hasAlphaBug)
419
428
  for (let t = 3; t < e.length; t += 4)
420
429
  e[t] = e[t] > 1 ? e[t] : 1;
421
430
  return e;
@@ -441,14 +450,14 @@ class l extends EventTarget {
441
450
  }
442
451
  _fetchFromWorker(e, t) {
443
452
  try {
444
- const s = e.target, i = setTimeout(() => {
445
- n(new Error("Error: Timeout while try to fetch " + s));
446
- }, 5e3), r = ({ data: a }) => {
447
- a.target === s && (t(null, a), this._worker.removeEventListener("message", r), this._worker.removeEventListener("error", n), clearTimeout(i));
448
- }, n = (a) => {
449
- t(a), this._worker.removeEventListener("message", r), this._worker.removeEventListener("error", n), clearTimeout(i);
453
+ const s = e.target, a = setTimeout(() => {
454
+ r(new Error("Error: Timeout while try to fetch " + s));
455
+ }, 5e3), n = ({ data: i }) => {
456
+ i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", r), clearTimeout(a));
457
+ }, r = (i) => {
458
+ t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", r), clearTimeout(a);
450
459
  };
451
- this._worker.addEventListener("message", r), this._worker.addEventListener("error", n), this._worker.postMessage(e);
460
+ this._worker.addEventListener("message", n), this._worker.addEventListener("error", r), this._worker.postMessage(e);
452
461
  } catch (s) {
453
462
  this._error(s);
454
463
  }
@@ -475,5 +484,5 @@ class l extends EventTarget {
475
484
  }
476
485
  }
477
486
  export {
478
- l as default
487
+ h as default
479
488
  };
@@ -1 +1 @@
1
- (function(c,_){typeof exports=="object"&&typeof module<"u"?module.exports=_():typeof define=="function"&&define.amd?define(_):(c=typeof globalThis<"u"?globalThis:c||self,c.JASSUB=_())})(this,function(){"use strict";!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(m){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(n,a)=>{const o=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(h>t){const l=this.mozFrameDelay||o.totalFrameDelay-e.totalFrameDelay||0,f=a-n;m(a,{presentationTime:a+l*1e3,expectedDisplayTime:a+f,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+f/1e3,presentedFrames:h,processingDuration:l}),delete this._rvfcpolyfillmap[i]}else this._rvfcpolyfillmap[i]=requestAnimationFrame(l=>s(a,l))},i=Date.now(),r=performance.now();return this._rvfcpolyfillmap[i]=requestAnimationFrame(n=>s(r,n)),i},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(m){cancelAnimationFrame(this._rvfcpolyfillmap[m]),delete this._rvfcpolyfillmap[m]});const c={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},_={BT601:{BT709:"1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418"},BT709:{BT601:"0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582"},FCC:{BT709:"1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251 1.0378",BT601:"1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996"},SMPTE240M:{BT709:"0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148",BT601:"0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973"}};class d extends EventTarget{constructor(e={}){super(),globalThis.Worker||this.destroy("Worker not supported"),d._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(e.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!e.canvas&&(e.offscreenRender??!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=e.canvas,this._video&&!this._canvas?(this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._createCanvas(),this._video.nextSibling?this._video.parentNode.insertBefore(this._canvasParent,this._video.nextSibling):this._video.parentNode.appendChild(this._canvasParent)):this._canvas||this.destroy("Don't know where to render: you should give video or canvas in options."),this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!e.debug,this.prescaleFactor=e.prescaleFactor||1,this.prescaleHeightLimit=e.prescaleHeightLimit||1080,this.maxRenderHeight=e.maxRenderHeight||0,this._worker=new Worker(e.workerUrl||"jassub-worker.js"),this._worker.onmessage=t=>this._onmessage(t),this._worker.onerror=t=>this._error(t),this._loaded=new Promise(t=>{this._init=()=>{this._destroyed||(this._worker.postMessage({target:"init",asyncRender:typeof createImageBitmap<"u"&&(e.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:e.blendMode||"js",subUrl:e.subUrl,subContent:e.subContent||null,fonts:e.fonts||[],availableFonts:e.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:e.fallbackFont||"liberation sans",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,useLocalFonts:"queryLocalFonts"in self&&(e.useLocalFonts??!0)}),this._offscreenRender===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),t())}})}_createCanvas(){this._canvas=document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas)}static _supportsWebAssembly=null;static _hasAlphaBug=null;static _test(){if(d._supportsWebAssembly!==null)return null;const e=document.createElement("canvas"),t=e.getContext("2d",{willReadFrequently:!0});if(typeof ImageData.prototype.constructor=="function")try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch{console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(o,h,l){const f=t.createImageData(h,l);return o&&f.data.set(o),f}}const s=document.createElement("canvas"),i=s.getContext("2d",{willReadFrequently:!0});e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),i.clearRect(0,0,1,1);const r=i.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),i.drawImage(e,0,0);const n=i.getImageData(0,0,1,1).data;d._hasAlphaBug=r[1]!==n[1],d._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),e.remove(),s.remove()}resize(e=0,t=0,s=0,i=0,r=this._video?.paused){if((!e||!t)&&this._video){const n=this._getVideoPosition();let a=null;if(this._videoWidth){const o=this._video.videoWidth/this._videoWidth,h=this._video.videoHeight/this._videoHeight;a=this._computeCanvasSize((n.width||0)/o,(n.height||0)/h)}else a=this._computeCanvasSize(n.width||0,n.height||0);e=a.width,t=a.height,this._canvasParent&&(s=n.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),i=n.x),this._canvas.style.width=n.width+"px",this._canvas.style.height=n.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=i+"px",r&&this.busy===!1?this.busy=!0:r=!1,this.sendMessage("canvas",{width:e,height:t,force:r})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:i,offsetHeight:r}=this._video,n=i/r;e=i,t=r,n>s?e=Math.floor(r*s):t=Math.floor(i/s);const a=(i-e)/2,o=(r-t)/2;return{width:e,height:t,x:a,y:o}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,i=self.devicePixelRatio||1;if(e=e*i,t=t*i,t<=0||e<=0)e=0,t=0;else{const r=s<1?-1:1;let n=t*i;r*n*s<=r*this.prescaleHeightLimit?n*=s:r*n<r*this.prescaleHeightLimit&&(n=this.prescaleHeightLimit),this.maxRenderHeight>0&&n>this.maxRenderHeight&&(n=this.maxRenderHeight),e*=n/t,t=n}return{width:e,height:t}}_timeupdate({type:e}){const s={seeking:!0,waiting:!0,playing:!1}[e];s!=null&&(this._playstate=s),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}setTrack(e){this.sendMessage("setTrack",{content:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s,colorSpace:this._videoColorSpace})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{event:e,index:t})}removeStyle(e){this.sendMessage("removeStyle",{index:e})}getStyles(e){this._fetchFromWorker({target:"getStyles"},(t,{styles:s})=>{e(t,s)})}addFont(e){this.sendMessage("addFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t?.find(i=>i.fullName.toLowerCase()===e);s&&s.blob().then(i=>{i.arrayBuffer().then(r=>{this.addFont(new Uint8Array(r))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){try{navigator?.permissions?.query?navigator.permissions.query({name:"local-fonts"}).then(t=>{t.state==="granted"&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(t){console.warn("Local fonts API:",t)}}_unbusy(){this._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:i}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:i}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:i})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_detachOffscreen(){if(!this._offscreenRender||this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this.sendMessage("detachOffscreen"),this.busy=!1,this.resize(0,0,0,0,!0)}_reAttachOffscreen(){if(!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{try{const e=new VideoFrame(this._video);this._videoColorSpace=c[e.colorSpace.matrix],e.close(),this.sendMessage("getColorSpace")}catch(e){console.warn(e)}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='${_[e][t]} 0 0 0 0 0 1 0'/></filter></svg>#f")`)}_render({images:e,asyncRender:t,times:s,width:i,height:r,colorSpace:n}){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==i||this._canvasctrl.height!==r)&&(this._canvasctrl.width=i,this._canvasctrl.height=r,this._verifyColorSpace({subtitleColorSpace:n})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const a of e)a.image&&(t?(this._ctx.drawImage(a.image,a.x,a.y),a.image.close()):(this._bufferCanvas.width=a.w,this._bufferCanvas.height=a.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(a.image)),a.w,a.h),0,0),this._ctx.drawImage(this._bufferCanvas,a.x,a.y)));if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let a=0;const o=s.bitmaps||e.length;delete s.bitmaps;for(const h in s)a+=s[h];console.log("Bitmaps: "+o+" Total: "+(a|0)+"ms",s)}}_fixAlpha(e){if(d._hasAlphaBug)for(let t=3;t<e.length;t+=4)e[t]=e[t]>1?e[t]:1;return e}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,i=setTimeout(()=>{n(new Error("Error: Timeout while try to fetch "+s))},5e3),r=({data:a})=>{a.target===s&&(t(null,a),this._worker.removeEventListener("message",r),this._worker.removeEventListener("error",n),clearTimeout(i))},n=a=>{t(a),this._worker.removeEventListener("message",r),this._worker.removeEventListener("error",n),clearTimeout(i)};this._worker.addEventListener("message",r),this._worker.addEventListener("error",n),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});this.dispatchEvent(s),console.error(t)}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx&&(this._ctx.filter="none"),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){e&&this._error(e),this._video&&this._canvasParent&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}}return d});
1
+ (function(c,_){typeof exports=="object"&&typeof module<"u"?module.exports=_():typeof define=="function"&&define.amd?define(_):(c=typeof globalThis<"u"?globalThis:c||self,c.JASSUB=_())})(this,function(){"use strict";!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(f){const e=this.getVideoPlaybackQuality(),t=this.mozPresentedFrames||this.mozPaintedFrames||e.totalVideoFrames-e.droppedVideoFrames,s=(n,i)=>{const o=this.getVideoPlaybackQuality(),d=this.mozPresentedFrames||this.mozPaintedFrames||o.totalVideoFrames-o.droppedVideoFrames;if(d>t){const l=this.mozFrameDelay||o.totalFrameDelay-e.totalFrameDelay||0,m=i-n;f(i,{presentationTime:i+l*1e3,expectedDisplayTime:i+m,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+m/1e3,presentedFrames:d,processingDuration:l}),delete this._rvfcpolyfillmap[a]}else this._rvfcpolyfillmap[a]=requestAnimationFrame(l=>s(i,l))},a=Date.now(),r=performance.now();return this._rvfcpolyfillmap[a]=requestAnimationFrame(n=>s(r,n)),a},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(f){cancelAnimationFrame(this._rvfcpolyfillmap[f]),delete this._rvfcpolyfillmap[f]});const c={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},_={BT601:{BT709:"1.0863 -0.0723 -0.014 0 0 0.0965 0.8451 0.0584 0 0 -0.0141 -0.0277 1.0418"},BT709:{BT601:"0.9137 0.0784 0.0079 0 0 -0.1049 1.1722 -0.0671 0 0 0.0096 0.0322 0.9582"},FCC:{BT709:"1.0873 -0.0736 -0.0137 0 0 0.0974 0.8494 0.0531 0 0 -0.0127 -0.0251 1.0378",BT601:"1.001 -0.0008 -0.0002 0 0 0.0009 1.005 -0.006 0 0 0.0013 0.0027 0.996"},SMPTE240M:{BT709:"0.9993 0.0006 0.0001 0 0 -0.0004 0.9812 0.0192 0 0 -0.0034 -0.0114 1.0148",BT601:"0.913 0.0774 0.0096 0 0 -0.1051 1.1508 -0.0456 0 0 0.0063 0.0207 0.973"}};class h extends EventTarget{constructor(e={}){super(),globalThis.Worker||this.destroy("Worker not supported"),this._loaded=new Promise(t=>{this._init=t}),h._test(),this._onDemandRender="requestVideoFrameCallback"in HTMLVideoElement.prototype&&(e.onDemandRender??!0),this._offscreenRender="transferControlToOffscreen"in HTMLCanvasElement.prototype&&!e.canvas&&(e.offscreenRender??!0),this.timeOffset=e.timeOffset||0,this._video=e.video,this._videoHeight=0,this._videoWidth=0,this._videoColorSpace=null,this._canvas=e.canvas,this._video&&!this._canvas?(this._canvasParent=document.createElement("div"),this._canvasParent.className="JASSUB",this._canvasParent.style.position="relative",this._createCanvas(),this._video.nextSibling?this._video.parentNode.insertBefore(this._canvasParent,this._video.nextSibling):this._video.parentNode.appendChild(this._canvasParent)):this._canvas||this.destroy("Don't know where to render: you should give video or canvas in options."),this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),this._canvasctrl=this._offscreenRender?this._canvas.transferControlToOffscreen():this._canvas,this._ctx=!this._offscreenRender&&this._canvasctrl.getContext("2d"),this._lastRenderTime=0,this.debug=!!e.debug,this.prescaleFactor=e.prescaleFactor||1,this.prescaleHeightLimit=e.prescaleHeightLimit||1080,this.maxRenderHeight=e.maxRenderHeight||0,this._boundResize=this.resize.bind(this),this._boundTimeUpdate=this._timeupdate.bind(this),this._boundSetRate=this.setRate.bind(this),this._boundUpdateColorSpace=this._updateColorSpace.bind(this),this._video&&this.setVideo(e.video),this._onDemandRender&&(this.busy=!1,this._lastDemandTime=null),this._worker=new Worker(e.workerUrl||"jassub-worker.js"),this._worker.onmessage=t=>this._onmessage(t),this._worker.onerror=t=>this._error(t),this._worker.postMessage({target:"init",wasmUrl:h._supportsSIMD&&e.modernWasmUrl?e.modernWasmUrl:e.wasmUrl||"jassub-worker.wasm",legacyWasmUrl:e.legacyWasmUrl||"jassub-worker.wasm.js",asyncRender:typeof createImageBitmap<"u"&&(e.asyncRender??!0),onDemandRender:this._onDemandRender,width:this._canvasctrl.width||0,height:this._canvasctrl.height||0,blendMode:e.blendMode||"js",subUrl:e.subUrl,subContent:e.subContent||null,fonts:e.fonts||[],availableFonts:e.availableFonts||{"liberation sans":"./default.woff2"},fallbackFont:e.fallbackFont||"liberation sans",debug:this.debug,targetFps:e.targetFps||24,dropAllAnimations:e.dropAllAnimations,dropAllBlur:e.dropAllBlur,libassMemoryLimit:e.libassMemoryLimit||0,libassGlyphLimit:e.libassGlyphLimit||0,useLocalFonts:typeof queryLocalFonts<"u"&&(e.useLocalFonts??!0)}),this._offscreenRender===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])}_createCanvas(){this._canvas=document.createElement("canvas"),this._canvas.style.display="block",this._canvas.style.position="absolute",this._canvas.style.pointerEvents="none",this._canvasParent.appendChild(this._canvas)}static _supportsSIMD=null;static _hasAlphaBug=null;static _test(){if(h._supportsSIMD!==null)return null;try{h._supportsSIMD=WebAssembly.validate(Uint8Array.of(0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11))}catch{h._supportsSIMD=!1}const e=document.createElement("canvas"),t=e.getContext("2d",{willReadFrequently:!0});if(typeof ImageData.prototype.constructor=="function")try{new ImageData(new Uint8ClampedArray([0,0,0,0]),1,1)}catch{console.log("Detected that ImageData is not constructable despite browser saying so"),self.ImageData=function(o,d,l){const m=t.createImageData(d,l);return o&&m.data.set(o),m}}const s=document.createElement("canvas"),a=s.getContext("2d",{willReadFrequently:!0});e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const r=a.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),a.drawImage(e,0,0);const n=a.getImageData(0,0,1,1).data;h._hasAlphaBug=r[1]!==n[1],h._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),e.remove(),s.remove()}resize(e=0,t=0,s=0,a=0,r=this._video?.paused){if((!e||!t)&&this._video){const n=this._getVideoPosition();let i=null;if(this._videoWidth){const o=this._video.videoWidth/this._videoWidth,d=this._video.videoHeight/this._videoHeight;i=this._computeCanvasSize((n.width||0)/o,(n.height||0)/d)}else i=this._computeCanvasSize(n.width||0,n.height||0);e=i.width,t=i.height,this._canvasParent&&(s=n.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),a=n.x),this._canvas.style.width=n.width+"px",this._canvas.style.height=n.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=a+"px",r&&this.busy===!1?this.busy=!0:r=!1,this.sendMessage("canvas",{width:e,height:t,force:r})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:a,offsetHeight:r}=this._video,n=a/r;e=a,t=r,n>s?e=Math.floor(r*s):t=Math.floor(a/s);const i=(a-e)/2,o=(r-t)/2;return{width:e,height:t,x:i,y:o}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,a=self.devicePixelRatio||1;if(e=e*a,t=t*a,t<=0||e<=0)e=0,t=0;else{const r=s<1?-1:1;let n=t*a;r*n*s<=r*this.prescaleHeightLimit?n*=s:r*n<r*this.prescaleHeightLimit&&(n=this.prescaleHeightLimit),this.maxRenderHeight>0&&n>this.maxRenderHeight&&(n=this.maxRenderHeight),e*=n/t,t=n}return{width:e,height:t}}_timeupdate({type:e}){const s={seeking:!0,waiting:!0,playing:!1}[e];s!=null&&(this._playstate=s),this.setCurrentTime(this._video.paused||this._playstate,this._video.currentTime+this.timeOffset)}setVideo(e){e instanceof HTMLVideoElement?(this._removeListeners(),this._video=e,this._onDemandRender?this._video.requestVideoFrameCallback(this._handleRVFC.bind(this)):(this._playstate=e.paused,e.addEventListener("timeupdate",this._boundTimeUpdate,!1),e.addEventListener("progress",this._boundTimeUpdate,!1),e.addEventListener("waiting",this._boundTimeUpdate,!1),e.addEventListener("seeking",this._boundTimeUpdate,!1),e.addEventListener("playing",this._boundTimeUpdate,!1),e.addEventListener("ratechange",this._boundSetRate,!1),e.addEventListener("resize",this._boundResize,!1)),"VideoFrame"in window&&(e.addEventListener("loadedmetadata",this._boundUpdateColorSpace,!1),e.readyState>2&&this._updateColorSpace()),e.videoWidth>0&&this.resize(),typeof ResizeObserver<"u"&&(this._ro||(this._ro=new ResizeObserver(()=>this.resize())),this._ro.observe(e))):this._error("Video element invalid!")}runBenchmark(){this.sendMessage("runBenchmark")}setTrackByUrl(e){this.sendMessage("setTrackByUrl",{url:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}setTrack(e){this.sendMessage("setTrack",{content:e}),this._reAttachOffscreen(),this._ctx&&(this._ctx.filter="none")}freeTrack(){this.sendMessage("freeTrack")}setIsPaused(e){this.sendMessage("video",{isPaused:e})}setRate(e){this.sendMessage("video",{rate:e})}setCurrentTime(e,t,s){this.sendMessage("video",{isPaused:e,currentTime:t,rate:s,colorSpace:this._videoColorSpace})}createEvent(e){this.sendMessage("createEvent",{event:e})}setEvent(e,t){this.sendMessage("setEvent",{event:e,index:t})}removeEvent(e){this.sendMessage("removeEvent",{index:e})}getEvents(e){this._fetchFromWorker({target:"getEvents"},(t,{events:s})=>{e(t,s)})}createStyle(e){this.sendMessage("createStyle",{style:e})}setStyle(e,t){this.sendMessage("setStyle",{event:e,index:t})}removeStyle(e){this.sendMessage("removeStyle",{index:e})}getStyles(e){this._fetchFromWorker({target:"getStyles"},(t,{styles:s})=>{e(t,s)})}addFont(e){this.sendMessage("addFont",{font:e})}_sendLocalFont(e){try{queryLocalFonts().then(t=>{const s=t?.find(a=>a.fullName.toLowerCase()===e);s&&s.blob().then(a=>{a.arrayBuffer().then(r=>{this.addFont(new Uint8Array(r))})})})}catch(t){console.warn("Local fonts API:",t)}}_getLocalFont({font:e}){try{navigator?.permissions?.query?navigator.permissions.query({name:"local-fonts"}).then(t=>{t.state==="granted"&&this._sendLocalFont(e)}):this._sendLocalFont(e)}catch(t){console.warn("Local fonts API:",t)}}_unbusy(){this._lastDemandTime?this._demandRender(this._lastDemandTime):this.busy=!1}_handleRVFC(e,{mediaTime:t,width:s,height:a}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:a}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:a})),this._video.requestVideoFrameCallback(this._handleRVFC.bind(this))}_demandRender({mediaTime:e,width:t,height:s}){this._lastDemandTime=null,(t!==this._videoWidth||s!==this._videoHeight)&&(this._videoWidth=t,this._videoHeight=s,this.resize()),this.sendMessage("demand",{time:e+this.timeOffset})}_detachOffscreen(){if(!this._offscreenRender||this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas,this._ctx=this._canvasctrl.getContext("2d"),this.sendMessage("detachOffscreen"),this.busy=!1,this.resize(0,0,0,0,!0)}_reAttachOffscreen(){if(!this._offscreenRender||!this._ctx)return null;this._canvas.remove(),this._createCanvas(),this._canvasctrl=this._canvas.transferControlToOffscreen(),this._ctx=!1,this.sendMessage("offscreenCanvas",null,[this._canvasctrl]),this.resize(0,0,0,0,!0)}_updateColorSpace(){this._video.requestVideoFrameCallback(()=>{try{const e=new VideoFrame(this._video);this._videoColorSpace=c[e.colorSpace.matrix],e.close(),this.sendMessage("getColorSpace")}catch(e){console.warn(e)}})}_verifyColorSpace({subtitleColorSpace:e,videoColorSpace:t=this._videoColorSpace}){!e||!t||e!==t&&(this._detachOffscreen(),this._ctx.filter=`url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='f'><feColorMatrix type='matrix' values='${_[e][t]} 0 0 0 0 0 1 0'/></filter></svg>#f")`)}_render({images:e,asyncRender:t,times:s,width:a,height:r,colorSpace:n}){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==a||this._canvasctrl.height!==r)&&(this._canvasctrl.width=a,this._canvasctrl.height=r,this._verifyColorSpace({subtitleColorSpace:n})),this._ctx.clearRect(0,0,this._canvasctrl.width,this._canvasctrl.height);for(const i of e)i.image&&(t?(this._ctx.drawImage(i.image,i.x,i.y),i.image.close()):(this._bufferCanvas.width=i.w,this._bufferCanvas.height=i.h,this._bufferCtx.putImageData(new ImageData(this._fixAlpha(new Uint8ClampedArray(i.image)),i.w,i.h),0,0),this._ctx.drawImage(this._bufferCanvas,i.x,i.y)));if(this.debug){s.JSRenderTime=Date.now()-s.JSRenderTime-s.IPCTime;let i=0;const o=s.bitmaps||e.length;delete s.bitmaps;for(const d in s)i+=s[d];console.log("Bitmaps: "+o+" Total: "+(i|0)+"ms",s)}}_fixAlpha(e){if(h._hasAlphaBug)for(let t=3;t<e.length;t+=4)e[t]=e[t]>1?e[t]:1;return e}_ready(){this._init(),this.dispatchEvent(new CustomEvent("ready"))}async sendMessage(e,t={},s){await this._loaded,s?this._worker.postMessage({target:e,transferable:s,...t},[...s]):this._worker.postMessage({target:e,...t})}_fetchFromWorker(e,t){try{const s=e.target,a=setTimeout(()=>{n(new Error("Error: Timeout while try to fetch "+s))},5e3),r=({data:i})=>{i.target===s&&(t(null,i),this._worker.removeEventListener("message",r),this._worker.removeEventListener("error",n),clearTimeout(a))},n=i=>{t(i),this._worker.removeEventListener("message",r),this._worker.removeEventListener("error",n),clearTimeout(a)};this._worker.addEventListener("message",r),this._worker.addEventListener("error",n),this._worker.postMessage(e)}catch(s){this._error(s)}}_console({content:e,command:t}){console[t].apply(console,JSON.parse(e))}_onmessage({data:e}){this["_"+e.target]&&this["_"+e.target](e)}_error(e){const t=e instanceof Error?e:e instanceof ErrorEvent?e.error:new Error(e),s=e instanceof Event?new ErrorEvent(e.type,e):new ErrorEvent("error",{error:t});this.dispatchEvent(s),console.error(t)}_removeListeners(){this._video&&(this._ro&&this._ro.unobserve(this._video),this._ctx&&(this._ctx.filter="none"),this._video.removeEventListener("timeupdate",this._boundTimeUpdate),this._video.removeEventListener("progress",this._boundTimeUpdate),this._video.removeEventListener("waiting",this._boundTimeUpdate),this._video.removeEventListener("seeking",this._boundTimeUpdate),this._video.removeEventListener("playing",this._boundTimeUpdate),this._video.removeEventListener("ratechange",this._boundSetRate),this._video.removeEventListener("resize",this._boundResize),this._video.removeEventListener("loadedmetadata",this._boundUpdateColorSpace))}destroy(e){e&&this._error(e),this._video&&this._canvasParent&&this._video.parentNode.removeChild(this._canvasParent),this._destroyed=!0,this._removeListeners(),this.sendMessage("destroy"),this._worker.terminate()}}return h});
package/index.d.ts CHANGED
@@ -60,9 +60,12 @@ interface JassubOptions {
60
60
  prescaleHeightLimit?: number;
61
61
  maxRenderHeight?: number;
62
62
  dropAllAnimations?: boolean;
63
+ dropAllBlur?: boolean
63
64
 
64
65
  workerUrl?: string;
65
- legacyWorkerUrl?: string;
66
+ wasmUrl?: string;
67
+ legacyWasmUrl?: string;
68
+ modernWasmUrl?: string;
66
69
 
67
70
  subUrl?: string;
68
71
  subContent?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jassub",
3
- "version": "1.6.4",
3
+ "version": "1.7.1",
4
4
  "description": "libass Subtitle Renderer and Parser library for browsers",
5
5
  "main": "src/jassub.js",
6
6
  "type": "module",
package/src/jassub.js CHANGED
@@ -44,8 +44,11 @@ export default class JASSUB extends EventTarget {
44
44
  * @param {Number} [options.prescaleHeightLimit=1080] The height in pixels beyond which the subtitles canvas won't be prescaled.
45
45
  * @param {Number} [options.maxRenderHeight=0] The maximum rendering height in pixels of the subtitles canvas. Beyond this subtitles will be upscaled by the browser.
46
46
  * @param {Boolean} [options.dropAllAnimations=false] Attempt to discard all animated tags. Enabling this may severly mangle complex subtitles and should only be considered as an last ditch effort of uncertain success for hardware otherwise incapable of displaing anything. Will not reliably work with manually edited or allocated events.
47
+ * @param {Boolean} [options.dropAllBlur=false] The holy grail of performance gains. If heavy TS lags a lot, disabling this will make it ~x10 faster. This drops blur from all added subtitle tracks making most text and backgrounds look sharper, this is way less intrusive than dropping all animations, while still offering major performance gains.
47
48
  * @param {String} [options.workerUrl='jassub-worker.js'] The URL of the worker.
48
- * @param {String} [options.legacyWorkerUrl='jassub-worker-legacy.js'] The URL of the legacy worker. Only loaded if the browser doesn't support WASM.
49
+ * @param {String} [options.wasmUrl='jassub-worker.wasm'] The URL of the worker WASM.
50
+ * @param {String} [options.legacyWasmUrl='jassub-worker.wasm.js'] The URL of the worker WASM. Only loaded if the browser doesn't support WASM.
51
+ * @param {String} options.modernWasmUrl The URL of the modern worker WASM. This includes faster ASM instructions, but is only supported by newer browsers, disabled if the URL isn't defined.
49
52
  * @param {String} [options.subUrl=options.subContent] The URL of the subtitle file to play.
50
53
  * @param {String} [options.subContent=options.subUrl] The content of the subtitle file to play.
51
54
  * @param {String[]|Uint8Array[]} [options.fonts] An array of links or Uint8Arrays to the fonts used in the subtitle. If Uint8Array is used the array is copied, not referenced. This forces all the fonts in this array to be loaded by the renderer, regardless of if they are used.
@@ -60,6 +63,11 @@ export default class JASSUB extends EventTarget {
60
63
  if (!globalThis.Worker) {
61
64
  this.destroy('Worker not supported')
62
65
  }
66
+
67
+ this._loaded = new Promise(resolve => {
68
+ this._init = resolve
69
+ })
70
+
63
71
  JASSUB._test()
64
72
  this._onDemandRender = 'requestVideoFrameCallback' in HTMLVideoElement.prototype && (options.onDemandRender ?? true)
65
73
 
@@ -101,48 +109,44 @@ export default class JASSUB extends EventTarget {
101
109
  this.prescaleHeightLimit = options.prescaleHeightLimit || 1080
102
110
  this.maxRenderHeight = options.maxRenderHeight || 0 // 0 - no limit.
103
111
 
112
+ this._boundResize = this.resize.bind(this)
113
+ this._boundTimeUpdate = this._timeupdate.bind(this)
114
+ this._boundSetRate = this.setRate.bind(this)
115
+ this._boundUpdateColorSpace = this._updateColorSpace.bind(this)
116
+ if (this._video) this.setVideo(options.video)
117
+
118
+ if (this._onDemandRender) {
119
+ this.busy = false
120
+ this._lastDemandTime = null
121
+ }
122
+
104
123
  this._worker = new Worker(options.workerUrl || 'jassub-worker.js')
105
124
  this._worker.onmessage = e => this._onmessage(e)
106
125
  this._worker.onerror = e => this._error(e)
107
126
 
108
- this._loaded = new Promise(resolve => {
109
- this._init = () => {
110
- if (this._destroyed) return
111
- this._worker.postMessage({
112
- target: 'init',
113
- asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
114
- onDemandRender: this._onDemandRender,
115
- width: this._canvasctrl.width || 0,
116
- height: this._canvasctrl.height || 0,
117
- blendMode: options.blendMode || 'js',
118
- subUrl: options.subUrl,
119
- subContent: options.subContent || null,
120
- fonts: options.fonts || [],
121
- availableFonts: options.availableFonts || { 'liberation sans': './default.woff2' },
122
- fallbackFont: options.fallbackFont || 'liberation sans',
123
- debug: this.debug,
124
- targetFps: options.targetFps || 24,
125
- dropAllAnimations: options.dropAllAnimations,
126
- dropAllBlur: options.dropAllBlur,
127
- libassMemoryLimit: options.libassMemoryLimit || 0,
128
- libassGlyphLimit: options.libassGlyphLimit || 0,
129
- useLocalFonts: ('queryLocalFonts' in self) && (options.useLocalFonts ?? true)
130
- })
131
- if (this._offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
132
-
133
- this._boundResize = this.resize.bind(this)
134
- this._boundTimeUpdate = this._timeupdate.bind(this)
135
- this._boundSetRate = this.setRate.bind(this)
136
- this._boundUpdateColorSpace = this._updateColorSpace.bind(this)
137
- if (this._video) this.setVideo(options.video)
138
-
139
- if (this._onDemandRender) {
140
- this.busy = false
141
- this._lastDemandTime = null
142
- }
143
- resolve()
144
- }
127
+ this._worker.postMessage({
128
+ target: 'init',
129
+ wasmUrl: JASSUB._supportsSIMD && options.modernWasmUrl ? options.modernWasmUrl : options.wasmUrl || 'jassub-worker.wasm',
130
+ legacyWasmUrl: options.legacyWasmUrl || 'jassub-worker.wasm.js',
131
+ asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
132
+ onDemandRender: this._onDemandRender,
133
+ width: this._canvasctrl.width || 0,
134
+ height: this._canvasctrl.height || 0,
135
+ blendMode: options.blendMode || 'js',
136
+ subUrl: options.subUrl,
137
+ subContent: options.subContent || null,
138
+ fonts: options.fonts || [],
139
+ availableFonts: options.availableFonts || { 'liberation sans': './default.woff2' },
140
+ fallbackFont: options.fallbackFont || 'liberation sans',
141
+ debug: this.debug,
142
+ targetFps: options.targetFps || 24,
143
+ dropAllAnimations: options.dropAllAnimations,
144
+ dropAllBlur: options.dropAllBlur,
145
+ libassMemoryLimit: options.libassMemoryLimit || 0,
146
+ libassGlyphLimit: options.libassGlyphLimit || 0,
147
+ useLocalFonts: typeof queryLocalFonts !== 'undefined' && (options.useLocalFonts ?? true)
145
148
  })
149
+ if (this._offscreenRender === true) this.sendMessage('offscreenCanvas', null, [this._canvasctrl])
146
150
  }
147
151
 
148
152
  _createCanvas () {
@@ -154,12 +158,18 @@ export default class JASSUB extends EventTarget {
154
158
  }
155
159
 
156
160
  // test support for WASM, ImageData, alphaBug, but only once, on init so it doesn't run when first running the page
157
- static _supportsWebAssembly = null
161
+ static _supportsSIMD = null
158
162
  static _hasAlphaBug = null
159
163
 
160
164
  static _test () {
161
165
  // check if ran previously
162
- if (JASSUB._supportsWebAssembly !== null) return null
166
+ if (JASSUB._supportsSIMD !== null) return null
167
+
168
+ try {
169
+ JASSUB._supportsSIMD = WebAssembly.validate(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11))
170
+ } catch (e) {
171
+ JASSUB._supportsSIMD = false
172
+ }
163
173
 
164
174
  const canvas1 = document.createElement('canvas')
165
175
  const ctx1 = canvas1.getContext('2d', { willReadFrequently: true })