jassub 1.6.4 → 1.7.0
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/README.md +9 -4
- package/dist/jassub-worker-modern.wasm +0 -0
- package/dist/jassub-worker.js +16 -15
- package/dist/jassub-worker.wasm +0 -0
- package/dist/jassub-worker.wasm.js +1 -1
- package/dist/jassub.es.js +99 -90
- package/dist/jassub.umd.js +1 -1
- package/index.d.ts +4 -1
- package/package.json +1 -1
- package/src/jassub.js +50 -40
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 = (
|
|
3
|
-
const o = this.getVideoPlaybackQuality(),
|
|
4
|
-
if (
|
|
5
|
-
const
|
|
6
|
-
_(
|
|
7
|
-
presentationTime:
|
|
8
|
-
expectedDisplayTime:
|
|
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:
|
|
13
|
-
processingDuration:
|
|
14
|
-
}), delete this._rvfcpolyfillmap[
|
|
12
|
+
presentedFrames: d,
|
|
13
|
+
processingDuration: l
|
|
14
|
+
}), delete this._rvfcpolyfillmap[a];
|
|
15
15
|
} else
|
|
16
|
-
this._rvfcpolyfillmap[
|
|
17
|
-
},
|
|
18
|
-
return this._rvfcpolyfillmap[
|
|
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
|
-
},
|
|
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
|
|
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.
|
|
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"),
|
|
74
|
-
this._init =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
105
|
+
static _supportsSIMD = null;
|
|
102
106
|
static _hasAlphaBug = null;
|
|
103
107
|
static _test() {
|
|
104
|
-
if (
|
|
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,
|
|
112
|
-
const c = t.createImageData(
|
|
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"),
|
|
117
|
-
e.width = s.width = 1, e.height = s.height = 1, t.clearRect(0, 0, 1, 1),
|
|
118
|
-
const
|
|
119
|
-
t.putImageData(new ImageData(new Uint8ClampedArray([0, 255, 0, 0]), 1, 1), 0, 0),
|
|
120
|
-
const
|
|
121
|
-
|
|
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,
|
|
140
|
+
resize(e = 0, t = 0, s = 0, a = 0, n = this._video?.paused) {
|
|
132
141
|
if ((!e || !t) && this._video) {
|
|
133
|
-
const
|
|
134
|
-
let
|
|
142
|
+
const r = this._getVideoPosition();
|
|
143
|
+
let i = null;
|
|
135
144
|
if (this._videoWidth) {
|
|
136
|
-
const o = this._video.videoWidth / this._videoWidth,
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
e =
|
|
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 =
|
|
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:
|
|
146
|
-
e =
|
|
147
|
-
const
|
|
148
|
-
return { width: e, height: t, x:
|
|
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,
|
|
152
|
-
if (e = e *
|
|
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
|
|
156
|
-
let
|
|
157
|
-
|
|
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((
|
|
343
|
-
s && s.blob().then((
|
|
344
|
-
|
|
345
|
-
this.addFont(new Uint8Array(
|
|
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:
|
|
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:
|
|
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='${
|
|
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:
|
|
404
|
-
this._unbusy(), this.debug && (s.IPCTime = Date.now() - s.JSRenderTime), (this._canvasctrl.width !==
|
|
405
|
-
for (const
|
|
406
|
-
|
|
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
|
|
418
|
+
let i = 0;
|
|
410
419
|
const o = s.bitmaps || e.length;
|
|
411
420
|
delete s.bitmaps;
|
|
412
|
-
for (const
|
|
413
|
-
|
|
414
|
-
console.log("Bitmaps: " + o + " Total: " + (
|
|
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 (
|
|
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,
|
|
445
|
-
|
|
446
|
-
}, 5e3),
|
|
447
|
-
|
|
448
|
-
},
|
|
449
|
-
t(
|
|
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",
|
|
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
|
-
|
|
487
|
+
h as default
|
|
479
488
|
};
|
package/dist/jassub.umd.js
CHANGED
|
@@ -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
|
-
|
|
66
|
+
wasmUrl?: string;
|
|
67
|
+
legacyWasmUrl?: string;
|
|
68
|
+
modernWasmUrl?: string;
|
|
66
69
|
|
|
67
70
|
subUrl?: string;
|
|
68
71
|
subContent?: string;
|
package/package.json
CHANGED
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.
|
|
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.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
161
|
+
static _supportsSIMD = null
|
|
158
162
|
static _hasAlphaBug = null
|
|
159
163
|
|
|
160
164
|
static _test () {
|
|
161
165
|
// check if ran previously
|
|
162
|
-
if (JASSUB.
|
|
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 })
|