jassub 1.7.17 → 1.7.20
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/COPYRIGHT +951 -951
- package/dist/jassub-worker-modern.wasm +0 -0
- package/dist/jassub-worker.js +3 -3
- package/dist/jassub-worker.wasm +0 -0
- package/dist/jassub-worker.wasm.js +1 -1
- package/dist/jassub.es.js +76 -70
- package/dist/jassub.umd.js +1 -1
- package/index.d.ts +5 -2
- package/package.json +1 -1
- package/src/jassub.js +13 -7
package/dist/jassub.es.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
typeof HTMLVideoElement < "u" && !("requestVideoFrameCallback" in HTMLVideoElement.prototype) && "getVideoPlaybackQuality" in HTMLVideoElement.prototype && (HTMLVideoElement.prototype._rvfcpolyfillmap = {}, HTMLVideoElement.prototype.requestVideoFrameCallback = function(c) {
|
|
2
|
-
const e = performance.now(), t = this.getVideoPlaybackQuality(), s = this.mozPresentedFrames || this.mozPaintedFrames || t.totalVideoFrames - t.droppedVideoFrames,
|
|
3
|
-
const i = this.getVideoPlaybackQuality(),
|
|
4
|
-
if (
|
|
5
|
-
const d = this.mozFrameDelay || i.totalFrameDelay - t.totalFrameDelay || 0, l =
|
|
6
|
-
c(
|
|
7
|
-
presentationTime:
|
|
8
|
-
expectedDisplayTime:
|
|
2
|
+
const e = performance.now(), t = this.getVideoPlaybackQuality(), s = this.mozPresentedFrames || this.mozPaintedFrames || t.totalVideoFrames - t.droppedVideoFrames, r = (n, a) => {
|
|
3
|
+
const i = this.getVideoPlaybackQuality(), h = this.mozPresentedFrames || this.mozPaintedFrames || i.totalVideoFrames - i.droppedVideoFrames;
|
|
4
|
+
if (h > s) {
|
|
5
|
+
const d = this.mozFrameDelay || i.totalFrameDelay - t.totalFrameDelay || 0, l = a - n;
|
|
6
|
+
c(a, {
|
|
7
|
+
presentationTime: a + d * 1e3,
|
|
8
|
+
expectedDisplayTime: a + l,
|
|
9
9
|
width: this.videoWidth,
|
|
10
10
|
height: this.videoHeight,
|
|
11
11
|
mediaTime: Math.max(0, this.currentTime || 0) + l / 1e3,
|
|
12
|
-
presentedFrames:
|
|
12
|
+
presentedFrames: h,
|
|
13
13
|
processingDuration: d
|
|
14
14
|
}), delete this._rvfcpolyfillmap[e];
|
|
15
15
|
} else
|
|
16
|
-
this._rvfcpolyfillmap[e] = requestAnimationFrame((d) => a
|
|
16
|
+
this._rvfcpolyfillmap[e] = requestAnimationFrame((d) => r(a, d));
|
|
17
17
|
};
|
|
18
|
-
return this._rvfcpolyfillmap[e] = requestAnimationFrame((n) =>
|
|
18
|
+
return this._rvfcpolyfillmap[e] = requestAnimationFrame((n) => r(e, n)), e;
|
|
19
19
|
}, HTMLVideoElement.prototype.cancelVideoFrameCallback = function(c) {
|
|
20
20
|
cancelAnimationFrame(this._rvfcpolyfillmap[c]), delete this._rvfcpolyfillmap[c];
|
|
21
21
|
});
|
|
@@ -42,7 +42,7 @@ const _ = {
|
|
|
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
|
|
45
|
+
class o 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.
|
|
@@ -81,7 +81,7 @@ class h extends EventTarget {
|
|
|
81
81
|
new Promise((s) => {
|
|
82
82
|
this._init = s;
|
|
83
83
|
});
|
|
84
|
-
const t =
|
|
84
|
+
const t = o._test();
|
|
85
85
|
if (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)
|
|
86
86
|
this._canvasParent = document.createElement("div"), this._canvasParent.className = "JASSUB", this._canvasParent.style.position = "relative", this._canvas = this._createCanvas(), this._video.insertAdjacentElement("afterend", this._canvasParent);
|
|
87
87
|
else if (!this._canvas)
|
|
@@ -91,8 +91,8 @@ class h extends EventTarget {
|
|
|
91
91
|
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 = (s) => this._onmessage(s), this._worker.onerror = (s) => this._error(s), t.then(() => {
|
|
92
92
|
this._worker.postMessage({
|
|
93
93
|
target: "init",
|
|
94
|
-
wasmUrl:
|
|
95
|
-
legacyWasmUrl: e.legacyWasmUrl
|
|
94
|
+
wasmUrl: o._supportsSIMD && e.modernWasmUrl ? e.modernWasmUrl : e.wasmUrl ?? "jassub-worker.wasm",
|
|
95
|
+
legacyWasmUrl: e.legacyWasmUrl ?? "jassub-worker.wasm.js",
|
|
96
96
|
asyncRender: typeof createImageBitmap < "u" && (e.asyncRender ?? !0),
|
|
97
97
|
onDemandRender: this._onDemandRender,
|
|
98
98
|
width: this._canvasctrl.width || 0,
|
|
@@ -111,7 +111,7 @@ class h extends EventTarget {
|
|
|
111
111
|
libassGlyphLimit: e.libassGlyphLimit || 0,
|
|
112
112
|
// @ts-ignore
|
|
113
113
|
useLocalFonts: typeof queryLocalFonts < "u" && (e.useLocalFonts ?? !0),
|
|
114
|
-
hasBitmapBug:
|
|
114
|
+
hasBitmapBug: o._hasBitmapBug
|
|
115
115
|
}), this._offscreenRender === !0 && this.sendMessage("offscreenCanvas", null, [this._canvasctrl]);
|
|
116
116
|
});
|
|
117
117
|
}
|
|
@@ -125,14 +125,17 @@ class h extends EventTarget {
|
|
|
125
125
|
static _hasAlphaBug = null;
|
|
126
126
|
/** @type {boolean|null} */
|
|
127
127
|
static _hasBitmapBug = null;
|
|
128
|
-
static
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
static _testSIMD() {
|
|
129
|
+
if (o._supportsSIMD === null)
|
|
130
|
+
try {
|
|
131
|
+
o._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));
|
|
132
|
+
} catch {
|
|
133
|
+
o._supportsSIMD = !1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
static async _testImageBugs() {
|
|
137
|
+
if (o._hasBitmapBug !== null)
|
|
138
|
+
return;
|
|
136
139
|
const e = document.createElement("canvas"), t = e.getContext("2d", { willReadFrequently: !0 });
|
|
137
140
|
if (!t)
|
|
138
141
|
throw new Error("Canvas rendering not supported");
|
|
@@ -140,32 +143,35 @@ class h extends EventTarget {
|
|
|
140
143
|
try {
|
|
141
144
|
new ImageData(new Uint8ClampedArray([0, 0, 0, 0]), 1, 1);
|
|
142
145
|
} catch {
|
|
143
|
-
console.log("Detected that ImageData is not constructable despite browser saying so"), self.ImageData = function(
|
|
146
|
+
console.log("Detected that ImageData is not constructable despite browser saying so"), self.ImageData = function(h, d, l) {
|
|
144
147
|
const m = t.createImageData(d, l);
|
|
145
|
-
return
|
|
148
|
+
return h && m.data.set(h), m;
|
|
146
149
|
};
|
|
147
150
|
}
|
|
148
|
-
const s = document.createElement("canvas"),
|
|
149
|
-
if (!
|
|
151
|
+
const s = document.createElement("canvas"), r = s.getContext("2d", { willReadFrequently: !0 });
|
|
152
|
+
if (!r)
|
|
150
153
|
throw new Error("Canvas rendering not supported");
|
|
151
|
-
e.width = s.width = 1, e.height = s.height = 1, t.clearRect(0, 0, 1, 1),
|
|
152
|
-
const n =
|
|
153
|
-
t.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0),
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
154
|
+
e.width = s.width = 1, e.height = s.height = 1, t.clearRect(0, 0, 1, 1), r.clearRect(0, 0, 1, 1);
|
|
155
|
+
const n = r.getImageData(0, 0, 1, 1).data;
|
|
156
|
+
t.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0), r.drawImage(e, 0, 0);
|
|
157
|
+
const a = r.getImageData(0, 0, 1, 1).data;
|
|
158
|
+
if (o._hasAlphaBug = n[1] !== a[1], o._hasAlphaBug && console.log("Detected a browser having issue with transparent pixels, applying workaround"), typeof createImageBitmap < "u") {
|
|
156
159
|
const i = new Uint8ClampedArray([255, 0, 255, 0, 255]).subarray(1, 5);
|
|
157
|
-
|
|
158
|
-
const { data:
|
|
159
|
-
|
|
160
|
-
for (const [d, l] of
|
|
160
|
+
r.drawImage(await createImageBitmap(new ImageData(i, 1)), 0, 0);
|
|
161
|
+
const { data: h } = r.getImageData(0, 0, 1, 1);
|
|
162
|
+
o._hasBitmapBug = !1;
|
|
163
|
+
for (const [d, l] of h.entries())
|
|
161
164
|
if (Math.abs(i[d] - l) > 15) {
|
|
162
|
-
|
|
165
|
+
o._hasBitmapBug = !0, console.log("Detected a browser having issue with partial bitmaps, applying workaround");
|
|
163
166
|
break;
|
|
164
167
|
}
|
|
165
168
|
} else
|
|
166
|
-
|
|
169
|
+
o._hasBitmapBug = !1;
|
|
167
170
|
e.remove(), s.remove();
|
|
168
171
|
}
|
|
172
|
+
static async _test() {
|
|
173
|
+
o._testSIMD(), await o._testImageBugs();
|
|
174
|
+
}
|
|
169
175
|
/**
|
|
170
176
|
* Resize the canvas to given parameters. Auto-generated if values are ommited.
|
|
171
177
|
* @param {Number} [width=0]
|
|
@@ -174,33 +180,33 @@ class h extends EventTarget {
|
|
|
174
180
|
* @param {Number} [left=0]
|
|
175
181
|
* @param {Boolean} [force=false]
|
|
176
182
|
*/
|
|
177
|
-
resize(e = 0, t = 0, s = 0,
|
|
183
|
+
resize(e = 0, t = 0, s = 0, r = 0, n = this._video?.paused) {
|
|
178
184
|
if ((!e || !t) && this._video) {
|
|
179
|
-
const
|
|
185
|
+
const a = this._getVideoPosition();
|
|
180
186
|
let i = null;
|
|
181
187
|
if (this._videoWidth) {
|
|
182
|
-
const
|
|
183
|
-
i = this._computeCanvasSize((
|
|
188
|
+
const h = this._video.videoWidth / this._videoWidth, d = this._video.videoHeight / this._videoHeight;
|
|
189
|
+
i = this._computeCanvasSize((a.width || 0) / h, (a.height || 0) / d);
|
|
184
190
|
} else
|
|
185
|
-
i = this._computeCanvasSize(
|
|
186
|
-
e = i.width, t = i.height, this._canvasParent && (s =
|
|
191
|
+
i = this._computeCanvasSize(a.width || 0, a.height || 0);
|
|
192
|
+
e = i.width, t = i.height, this._canvasParent && (s = a.y - (this._canvasParent.getBoundingClientRect().top - this._video.getBoundingClientRect().top), r = a.x), this._canvas.style.width = a.width + "px", this._canvas.style.height = a.height + "px";
|
|
187
193
|
}
|
|
188
|
-
this._canvas.style.top = s + "px", this._canvas.style.left =
|
|
194
|
+
this._canvas.style.top = s + "px", this._canvas.style.left = r + "px", n && this.busy === !1 ? this.busy = !0 : n = !1, this.sendMessage("canvas", { width: e, height: t, force: n });
|
|
189
195
|
}
|
|
190
196
|
_getVideoPosition(e = this._video.videoWidth, t = this._video.videoHeight) {
|
|
191
|
-
const s = e / t, { offsetWidth:
|
|
192
|
-
e =
|
|
193
|
-
const i = (
|
|
194
|
-
return { width: e, height: t, x: i, y:
|
|
197
|
+
const s = e / t, { offsetWidth: r, offsetHeight: n } = this._video, a = r / n;
|
|
198
|
+
e = r, t = n, a > s ? e = Math.floor(n * s) : t = Math.floor(r / s);
|
|
199
|
+
const i = (r - e) / 2, h = (n - t) / 2;
|
|
200
|
+
return { width: e, height: t, x: i, y: h };
|
|
195
201
|
}
|
|
196
202
|
_computeCanvasSize(e = 0, t = 0) {
|
|
197
|
-
const s = this.prescaleFactor <= 0 ? 1 : this.prescaleFactor,
|
|
198
|
-
if (
|
|
203
|
+
const s = this.prescaleFactor <= 0 ? 1 : this.prescaleFactor, r = self.devicePixelRatio || 1;
|
|
204
|
+
if (t <= 0 || e <= 0)
|
|
199
205
|
e = 0, t = 0;
|
|
200
206
|
else {
|
|
201
207
|
const n = s < 1 ? -1 : 1;
|
|
202
|
-
let
|
|
203
|
-
n *
|
|
208
|
+
let a = t * r;
|
|
209
|
+
n * a * s <= n * this.prescaleHeightLimit ? a *= s : n * a < n * this.prescaleHeightLimit && (a = this.prescaleHeightLimit), this.maxRenderHeight > 0 && a > this.maxRenderHeight && (a = this.maxRenderHeight), e *= a / t, t = a;
|
|
204
210
|
}
|
|
205
211
|
return { width: e, height: t };
|
|
206
212
|
}
|
|
@@ -385,9 +391,9 @@ class h extends EventTarget {
|
|
|
385
391
|
_sendLocalFont(e) {
|
|
386
392
|
try {
|
|
387
393
|
queryLocalFonts().then((t) => {
|
|
388
|
-
const s = t?.find((
|
|
389
|
-
s && s.blob().then((
|
|
390
|
-
|
|
394
|
+
const s = t?.find((r) => r.fullName.toLowerCase() === e);
|
|
395
|
+
s && s.blob().then((r) => {
|
|
396
|
+
r.arrayBuffer().then((n) => {
|
|
391
397
|
this.addFont(new Uint8Array(n));
|
|
392
398
|
});
|
|
393
399
|
});
|
|
@@ -408,10 +414,10 @@ class h extends EventTarget {
|
|
|
408
414
|
_unbusy() {
|
|
409
415
|
this._lastDemandTime ? this._demandRender(this._lastDemandTime) : this.busy = !1;
|
|
410
416
|
}
|
|
411
|
-
_handleRVFC(e, { mediaTime: t, width: s, height:
|
|
417
|
+
_handleRVFC(e, { mediaTime: t, width: s, height: r }) {
|
|
412
418
|
if (this._destroyed)
|
|
413
419
|
return null;
|
|
414
|
-
this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height:
|
|
420
|
+
this.busy ? this._lastDemandTime = { mediaTime: t, width: s, height: r } : (this.busy = !0, this._demandRender({ mediaTime: t, width: s, height: r })), this._video.requestVideoFrameCallback(this._handleRVFC.bind(this));
|
|
415
421
|
}
|
|
416
422
|
_demandRender({ mediaTime: e, width: t, height: s }) {
|
|
417
423
|
this._lastDemandTime = null, (t !== this._videoWidth || s !== this._videoHeight) && (this._videoWidth = t, this._videoHeight = s, this.resize()), this.sendMessage("demand", { time: e + this.timeOffset });
|
|
@@ -447,22 +453,22 @@ class h extends EventTarget {
|
|
|
447
453
|
_verifyColorSpace({ subtitleColorSpace: e, videoColorSpace: t = this._videoColorSpace }) {
|
|
448
454
|
!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")`);
|
|
449
455
|
}
|
|
450
|
-
_render({ images: e, asyncRender: t, times: s, width:
|
|
451
|
-
this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !==
|
|
456
|
+
_render({ images: e, asyncRender: t, times: s, width: r, height: n, colorSpace: a }) {
|
|
457
|
+
this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !== r || this._canvasctrl.height !== n) && (this._canvasctrl.width = r, this._canvasctrl.height = n, this._verifyColorSpace({ subtitleColorSpace: a })), this._ctx.clearRect(0, 0, this._canvasctrl.width, this._canvasctrl.height);
|
|
452
458
|
for (const i of e)
|
|
453
459
|
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)));
|
|
454
460
|
if (this.debug) {
|
|
455
461
|
s.JSRenderTime = Date.now() - s.JSRenderTime - s.IPCTime;
|
|
456
462
|
let i = 0;
|
|
457
|
-
const
|
|
463
|
+
const h = s.bitmaps || e.length;
|
|
458
464
|
delete s.bitmaps;
|
|
459
465
|
for (const d in s)
|
|
460
466
|
i += s[d];
|
|
461
|
-
console.log("Bitmaps: " +
|
|
467
|
+
console.log("Bitmaps: " + h + " Total: " + (i | 0) + "ms", s);
|
|
462
468
|
}
|
|
463
469
|
}
|
|
464
470
|
_fixAlpha(e) {
|
|
465
|
-
if (
|
|
471
|
+
if (o._hasAlphaBug)
|
|
466
472
|
for (let t = 3; t < e.length; t += 4)
|
|
467
473
|
e[t] = e[t] > 1 ? e[t] : 1;
|
|
468
474
|
return e;
|
|
@@ -488,14 +494,14 @@ class h extends EventTarget {
|
|
|
488
494
|
}
|
|
489
495
|
_fetchFromWorker(e, t) {
|
|
490
496
|
try {
|
|
491
|
-
const s = e.target,
|
|
492
|
-
|
|
497
|
+
const s = e.target, r = setTimeout(() => {
|
|
498
|
+
a(new Error("Error: Timeout while try to fetch " + s));
|
|
493
499
|
}, 5e3), n = ({ data: i }) => {
|
|
494
|
-
i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error",
|
|
495
|
-
},
|
|
496
|
-
t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error",
|
|
500
|
+
i.target === s && (t(null, i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r));
|
|
501
|
+
}, a = (i) => {
|
|
502
|
+
t(i), this._worker.removeEventListener("message", n), this._worker.removeEventListener("error", a), clearTimeout(r);
|
|
497
503
|
};
|
|
498
|
-
this._worker.addEventListener("message", n), this._worker.addEventListener("error",
|
|
504
|
+
this._worker.addEventListener("message", n), this._worker.addEventListener("error", a), this._worker.postMessage(e);
|
|
499
505
|
} catch (s) {
|
|
500
506
|
this._error(s);
|
|
501
507
|
}
|
|
@@ -522,5 +528,5 @@ class h extends EventTarget {
|
|
|
522
528
|
}
|
|
523
529
|
}
|
|
524
530
|
export {
|
|
525
|
-
|
|
531
|
+
o as default
|
|
526
532
|
};
|
package/dist/jassub.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(c,m){typeof exports=="object"&&typeof module<"u"?module.exports=m():typeof define=="function"&&define.amd?define(m):(c=typeof globalThis<"u"?globalThis:c||self,c.JASSUB=m())})(this,function(){"use strict";typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(_){const e=performance.now(),t=this.getVideoPlaybackQuality(),s=this.mozPresentedFrames||this.mozPaintedFrames||t.totalVideoFrames-t.droppedVideoFrames,a=(n,r)=>{const i=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames;if(h>s){const d=this.mozFrameDelay||i.totalFrameDelay-t.totalFrameDelay||0,l=r-n;_(r,{presentationTime:r+d*1e3,expectedDisplayTime:r+l,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+l/1e3,presentedFrames:h,processingDuration:d}),delete this._rvfcpolyfillmap[e]}else this._rvfcpolyfillmap[e]=requestAnimationFrame(d=>a(r,d))};return this._rvfcpolyfillmap[e]=requestAnimationFrame(n=>a(e,n)),e},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(_){cancelAnimationFrame(this._rvfcpolyfillmap[_]),delete this._rvfcpolyfillmap[_]});const c={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},m={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 o extends EventTarget{constructor(e){if(super(),!globalThis.Worker)throw this.destroy("Worker not supported");if(!e)throw this.destroy("No options provided");this._loaded=new Promise(s=>{this._init=s});const t=o._test();if(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._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");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=s=>this._onmessage(s),this._worker.onerror=s=>this._error(s),t.then(()=>{this._worker.postMessage({target:"init",wasmUrl:o._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),hasBitmapBug:o._hasBitmapBug}),this._offscreenRender===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])})}_createCanvas(){return 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),this._canvas}static _supportsSIMD=null;static _hasAlphaBug=null;static _hasBitmapBug=null;static async _test(){if(o._hasBitmapBug!==null)return null;try{o._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{o._supportsSIMD=!1}const e=document.createElement("canvas"),t=e.getContext("2d",{willReadFrequently:!0});if(!t)throw new Error("Canvas rendering not supported");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(h,d,l){const f=t.createImageData(d,l);return h&&f.data.set(h),f}}const s=document.createElement("canvas"),a=s.getContext("2d",{willReadFrequently:!0});if(!a)throw new Error("Canvas rendering not supported");e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),a.clearRect(0,0,1,1);const n=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 r=a.getImageData(0,0,1,1).data;if(o._hasAlphaBug=n[1]!==r[1],o._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const i=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);a.drawImage(await createImageBitmap(new ImageData(i,1)),0,0);const{data:h}=a.getImageData(0,0,1,1);o._hasBitmapBug=!1;for(const[d,l]of h.entries())if(Math.abs(i[d]-l)>15){o._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else o._hasBitmapBug=!1;e.remove(),s.remove()}resize(e=0,t=0,s=0,a=0,n=this._video?.paused){if((!e||!t)&&this._video){const r=this._getVideoPosition();let i=null;if(this._videoWidth){const h=this._video.videoWidth/this._videoWidth,d=this._video.videoHeight/this._videoHeight;i=this._computeCanvasSize((r.width||0)/h,(r.height||0)/d)}else i=this._computeCanvasSize(r.width||0,r.height||0);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"}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})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:a,offsetHeight:n}=this._video,r=a/n;e=a,t=n,r>s?e=Math.floor(n*s):t=Math.floor(a/s);const i=(a-e)/2,h=(n-t)/2;return{width:e,height:t,x:i,y:h}}_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 n=s<1?-1:1;let r=t*a;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}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",{style: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(n=>{this.addFont(new Uint8Array(n))})})})}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='${m[e][t]} 0 0 0 0 0 1 0'/></filter></svg>#f")`)}_render({images:e,asyncRender:t,times:s,width:a,height:n,colorSpace:r}){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);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 h=s.bitmaps||e.length;delete s.bitmaps;for(const d in s)i+=s[d];console.log("Bitmaps: "+h+" Total: "+(i|0)+"ms",s)}}_fixAlpha(e){if(o._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(()=>{r(new Error("Error: Timeout while try to fetch "+s))},5e3),n=({data:i})=>{i.target===s&&(t(null,i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",r),clearTimeout(a))},r=i=>{t(i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",r),clearTimeout(a)};this._worker.addEventListener("message",n),this._worker.addEventListener("error",r),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});return this.dispatchEvent(s),console.error(t),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){return 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(),e}}return o});
|
|
1
|
+
(function(c,m){typeof exports=="object"&&typeof module<"u"?module.exports=m():typeof define=="function"&&define.amd?define(m):(c=typeof globalThis<"u"?globalThis:c||self,c.JASSUB=m())})(this,function(){"use strict";typeof HTMLVideoElement<"u"&&!("requestVideoFrameCallback"in HTMLVideoElement.prototype)&&"getVideoPlaybackQuality"in HTMLVideoElement.prototype&&(HTMLVideoElement.prototype._rvfcpolyfillmap={},HTMLVideoElement.prototype.requestVideoFrameCallback=function(_){const e=performance.now(),t=this.getVideoPlaybackQuality(),s=this.mozPresentedFrames||this.mozPaintedFrames||t.totalVideoFrames-t.droppedVideoFrames,r=(n,a)=>{const i=this.getVideoPlaybackQuality(),h=this.mozPresentedFrames||this.mozPaintedFrames||i.totalVideoFrames-i.droppedVideoFrames;if(h>s){const d=this.mozFrameDelay||i.totalFrameDelay-t.totalFrameDelay||0,l=a-n;_(a,{presentationTime:a+d*1e3,expectedDisplayTime:a+l,width:this.videoWidth,height:this.videoHeight,mediaTime:Math.max(0,this.currentTime||0)+l/1e3,presentedFrames:h,processingDuration:d}),delete this._rvfcpolyfillmap[e]}else this._rvfcpolyfillmap[e]=requestAnimationFrame(d=>r(a,d))};return this._rvfcpolyfillmap[e]=requestAnimationFrame(n=>r(e,n)),e},HTMLVideoElement.prototype.cancelVideoFrameCallback=function(_){cancelAnimationFrame(this._rvfcpolyfillmap[_]),delete this._rvfcpolyfillmap[_]});const c={bt709:"BT709",bt470bg:"BT601",smpte170m:"BT601"},m={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 o extends EventTarget{constructor(e){if(super(),!globalThis.Worker)throw this.destroy("Worker not supported");if(!e)throw this.destroy("No options provided");this._loaded=new Promise(s=>{this._init=s});const t=o._test();if(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._canvas=this._createCanvas(),this._video.insertAdjacentElement("afterend",this._canvasParent);else if(!this._canvas)throw this.destroy("Don't know where to render: you should give video or canvas in options.");if(this._bufferCanvas=document.createElement("canvas"),this._bufferCtx=this._bufferCanvas.getContext("2d"),!this._bufferCtx)throw this.destroy("Canvas rendering not supported");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=s=>this._onmessage(s),this._worker.onerror=s=>this._error(s),t.then(()=>{this._worker.postMessage({target:"init",wasmUrl:o._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),hasBitmapBug:o._hasBitmapBug}),this._offscreenRender===!0&&this.sendMessage("offscreenCanvas",null,[this._canvasctrl])})}_createCanvas(){return 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),this._canvas}static _supportsSIMD=null;static _hasAlphaBug=null;static _hasBitmapBug=null;static _testSIMD(){if(o._supportsSIMD===null)try{o._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{o._supportsSIMD=!1}}static async _testImageBugs(){if(o._hasBitmapBug!==null)return;const e=document.createElement("canvas"),t=e.getContext("2d",{willReadFrequently:!0});if(!t)throw new Error("Canvas rendering not supported");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(h,d,l){const f=t.createImageData(d,l);return h&&f.data.set(h),f}}const s=document.createElement("canvas"),r=s.getContext("2d",{willReadFrequently:!0});if(!r)throw new Error("Canvas rendering not supported");e.width=s.width=1,e.height=s.height=1,t.clearRect(0,0,1,1),r.clearRect(0,0,1,1);const n=r.getImageData(0,0,1,1).data;t.putImageData(new ImageData(new Uint8ClampedArray([0,255,0,0]),1,1),0,0),r.drawImage(e,0,0);const a=r.getImageData(0,0,1,1).data;if(o._hasAlphaBug=n[1]!==a[1],o._hasAlphaBug&&console.log("Detected a browser having issue with transparent pixels, applying workaround"),typeof createImageBitmap<"u"){const i=new Uint8ClampedArray([255,0,255,0,255]).subarray(1,5);r.drawImage(await createImageBitmap(new ImageData(i,1)),0,0);const{data:h}=r.getImageData(0,0,1,1);o._hasBitmapBug=!1;for(const[d,l]of h.entries())if(Math.abs(i[d]-l)>15){o._hasBitmapBug=!0,console.log("Detected a browser having issue with partial bitmaps, applying workaround");break}}else o._hasBitmapBug=!1;e.remove(),s.remove()}static async _test(){o._testSIMD(),await o._testImageBugs()}resize(e=0,t=0,s=0,r=0,n=this._video?.paused){if((!e||!t)&&this._video){const a=this._getVideoPosition();let i=null;if(this._videoWidth){const h=this._video.videoWidth/this._videoWidth,d=this._video.videoHeight/this._videoHeight;i=this._computeCanvasSize((a.width||0)/h,(a.height||0)/d)}else i=this._computeCanvasSize(a.width||0,a.height||0);e=i.width,t=i.height,this._canvasParent&&(s=a.y-(this._canvasParent.getBoundingClientRect().top-this._video.getBoundingClientRect().top),r=a.x),this._canvas.style.width=a.width+"px",this._canvas.style.height=a.height+"px"}this._canvas.style.top=s+"px",this._canvas.style.left=r+"px",n&&this.busy===!1?this.busy=!0:n=!1,this.sendMessage("canvas",{width:e,height:t,force:n})}_getVideoPosition(e=this._video.videoWidth,t=this._video.videoHeight){const s=e/t,{offsetWidth:r,offsetHeight:n}=this._video,a=r/n;e=r,t=n,a>s?e=Math.floor(n*s):t=Math.floor(r/s);const i=(r-e)/2,h=(n-t)/2;return{width:e,height:t,x:i,y:h}}_computeCanvasSize(e=0,t=0){const s=this.prescaleFactor<=0?1:this.prescaleFactor,r=self.devicePixelRatio||1;if(t<=0||e<=0)e=0,t=0;else{const n=s<1?-1:1;let a=t*r;n*a*s<=n*this.prescaleHeightLimit?a*=s:n*a<n*this.prescaleHeightLimit&&(a=this.prescaleHeightLimit),this.maxRenderHeight>0&&a>this.maxRenderHeight&&(a=this.maxRenderHeight),e*=a/t,t=a}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",{style: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(r=>r.fullName.toLowerCase()===e);s&&s.blob().then(r=>{r.arrayBuffer().then(n=>{this.addFont(new Uint8Array(n))})})})}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:r}){if(this._destroyed)return null;this.busy?this._lastDemandTime={mediaTime:t,width:s,height:r}:(this.busy=!0,this._demandRender({mediaTime:t,width:s,height:r})),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='${m[e][t]} 0 0 0 0 0 1 0'/></filter></svg>#f")`)}_render({images:e,asyncRender:t,times:s,width:r,height:n,colorSpace:a}){this._unbusy(),this.debug&&(s.IPCTime=Date.now()-s.JSRenderTime),(this._canvasctrl.width!==r||this._canvasctrl.height!==n)&&(this._canvasctrl.width=r,this._canvasctrl.height=n,this._verifyColorSpace({subtitleColorSpace:a})),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 h=s.bitmaps||e.length;delete s.bitmaps;for(const d in s)i+=s[d];console.log("Bitmaps: "+h+" Total: "+(i|0)+"ms",s)}}_fixAlpha(e){if(o._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,r=setTimeout(()=>{a(new Error("Error: Timeout while try to fetch "+s))},5e3),n=({data:i})=>{i.target===s&&(t(null,i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",a),clearTimeout(r))},a=i=>{t(i),this._worker.removeEventListener("message",n),this._worker.removeEventListener("error",a),clearTimeout(r)};this._worker.addEventListener("message",n),this._worker.addEventListener("error",a),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});return this.dispatchEvent(s),console.error(t),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){return 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(),e}}return o});
|
package/index.d.ts
CHANGED
|
@@ -79,8 +79,8 @@ interface JassubOptions {
|
|
|
79
79
|
libassGlyphLimit?: number;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
type ASS_EventCallback = (error: Error | null, event: ASS_Event) => void;
|
|
83
|
-
type ASS_StyleCallback = (error: Error | null, event: ASS_Style) => void;
|
|
82
|
+
type ASS_EventCallback = (error: Error | null, event: ASS_Event[]) => void;
|
|
83
|
+
type ASS_StyleCallback = (error: Error | null, event: ASS_Style[]) => void;
|
|
84
84
|
|
|
85
85
|
export default class JASSUB {
|
|
86
86
|
constructor (options: JassubOptions);
|
|
@@ -111,4 +111,7 @@ export default class JASSUB {
|
|
|
111
111
|
|
|
112
112
|
sendMessage (target: string, data?: Record<string, unknown>, transferable?: Transferable[]): void;
|
|
113
113
|
destroy (err?: string): void;
|
|
114
|
+
|
|
115
|
+
protected _ctx: CanvasRenderingContext2D;
|
|
116
|
+
protected _canvas: HTMLCanvasElement;
|
|
114
117
|
}
|
package/package.json
CHANGED
package/src/jassub.js
CHANGED
|
@@ -123,8 +123,8 @@ export default class JASSUB extends EventTarget {
|
|
|
123
123
|
test.then(() => {
|
|
124
124
|
this._worker.postMessage({
|
|
125
125
|
target: 'init',
|
|
126
|
-
wasmUrl: JASSUB._supportsSIMD && options.modernWasmUrl ? options.modernWasmUrl : options.wasmUrl
|
|
127
|
-
legacyWasmUrl: options.legacyWasmUrl
|
|
126
|
+
wasmUrl: JASSUB._supportsSIMD && options.modernWasmUrl ? options.modernWasmUrl : options.wasmUrl ?? 'jassub-worker.wasm',
|
|
127
|
+
legacyWasmUrl: options.legacyWasmUrl ?? 'jassub-worker.wasm.js',
|
|
128
128
|
asyncRender: typeof createImageBitmap !== 'undefined' && (options.asyncRender ?? true),
|
|
129
129
|
onDemandRender: this._onDemandRender,
|
|
130
130
|
width: this._canvasctrl.width || 0,
|
|
@@ -167,15 +167,18 @@ export default class JASSUB extends EventTarget {
|
|
|
167
167
|
/** @type {boolean|null} */
|
|
168
168
|
static _hasBitmapBug = null
|
|
169
169
|
|
|
170
|
-
static
|
|
171
|
-
|
|
172
|
-
if (JASSUB._hasBitmapBug !== null) return null
|
|
170
|
+
static _testSIMD () {
|
|
171
|
+
if (JASSUB._supportsSIMD !== null) return
|
|
173
172
|
|
|
174
173
|
try {
|
|
175
174
|
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))
|
|
176
175
|
} catch (e) {
|
|
177
176
|
JASSUB._supportsSIMD = false
|
|
178
177
|
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static async _testImageBugs () {
|
|
181
|
+
if (JASSUB._hasBitmapBug !== null) return
|
|
179
182
|
|
|
180
183
|
const canvas1 = document.createElement('canvas')
|
|
181
184
|
const ctx1 = canvas1.getContext('2d', { willReadFrequently: true })
|
|
@@ -237,6 +240,11 @@ export default class JASSUB extends EventTarget {
|
|
|
237
240
|
canvas2.remove()
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
static async _test () {
|
|
244
|
+
JASSUB._testSIMD()
|
|
245
|
+
await JASSUB._testImageBugs()
|
|
246
|
+
}
|
|
247
|
+
|
|
240
248
|
/**
|
|
241
249
|
* Resize the canvas to given parameters. Auto-generated if values are ommited.
|
|
242
250
|
* @param {Number} [width=0]
|
|
@@ -299,8 +307,6 @@ export default class JASSUB extends EventTarget {
|
|
|
299
307
|
const scalefactor = this.prescaleFactor <= 0 ? 1.0 : this.prescaleFactor
|
|
300
308
|
const ratio = self.devicePixelRatio || 1
|
|
301
309
|
|
|
302
|
-
width = width * ratio
|
|
303
|
-
height = height * ratio
|
|
304
310
|
if (height <= 0 || width <= 0) {
|
|
305
311
|
width = 0
|
|
306
312
|
height = 0
|