libbitsub 1.10.2 → 1.12.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 +296 -49
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/integrations/hlsjs.d.ts +45 -0
- package/dist/integrations/hlsjs.d.ts.map +1 -0
- package/dist/integrations/hlsjs.js +121 -0
- package/dist/integrations/hlsjs.js.map +1 -0
- package/dist/integrations/index.d.ts +11 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +11 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/react.d.ts +55 -0
- package/dist/integrations/react.d.ts.map +1 -0
- package/dist/integrations/react.js +133 -0
- package/dist/integrations/react.js.map +1 -0
- package/dist/integrations/shaka.d.ts +38 -0
- package/dist/integrations/shaka.d.ts.map +1 -0
- package/dist/integrations/shaka.js +71 -0
- package/dist/integrations/shaka.js.map +1 -0
- package/dist/integrations/shared.d.ts +41 -0
- package/dist/integrations/shared.d.ts.map +1 -0
- package/dist/integrations/shared.js +123 -0
- package/dist/integrations/shared.js.map +1 -0
- package/dist/integrations/videojs.d.ts +85 -0
- package/dist/integrations/videojs.d.ts.map +1 -0
- package/dist/integrations/videojs.js +111 -0
- package/dist/integrations/videojs.js.map +1 -0
- package/dist/ts/capabilities.d.ts +8 -0
- package/dist/ts/capabilities.d.ts.map +1 -0
- package/dist/ts/capabilities.js +105 -0
- package/dist/ts/capabilities.js.map +1 -0
- package/dist/ts/diagnostics.d.ts.map +1 -1
- package/dist/ts/diagnostics.js.map +1 -1
- package/dist/ts/frame-export.d.ts.map +1 -1
- package/dist/ts/frame-export.js +4 -4
- package/dist/ts/frame-export.js.map +1 -1
- package/dist/ts/parsers.d.ts +46 -8
- package/dist/ts/parsers.d.ts.map +1 -1
- package/dist/ts/parsers.js +305 -14
- package/dist/ts/parsers.js.map +1 -1
- package/dist/ts/range-loader.d.ts +31 -0
- package/dist/ts/range-loader.d.ts.map +1 -0
- package/dist/ts/range-loader.js +220 -0
- package/dist/ts/range-loader.js.map +1 -0
- package/dist/ts/renderers.d.ts +140 -8
- package/dist/ts/renderers.d.ts.map +1 -1
- package/dist/ts/renderers.js +1773 -287
- package/dist/ts/renderers.js.map +1 -1
- package/dist/ts/types.d.ts +210 -7
- package/dist/ts/types.d.ts.map +1 -1
- package/dist/ts/utils.d.ts.map +1 -1
- package/dist/ts/utils.js +98 -2
- package/dist/ts/utils.js.map +1 -1
- package/dist/ts/video-frame-scheduler.d.ts +35 -0
- package/dist/ts/video-frame-scheduler.d.ts.map +1 -0
- package/dist/ts/video-frame-scheduler.js +98 -0
- package/dist/ts/video-frame-scheduler.js.map +1 -0
- package/dist/ts/wasm.d.ts +2 -0
- package/dist/ts/wasm.d.ts.map +1 -1
- package/dist/ts/wasm.js +12 -0
- package/dist/ts/wasm.js.map +1 -1
- package/dist/ts/webgl2-renderer.d.ts +9 -0
- package/dist/ts/webgl2-renderer.d.ts.map +1 -1
- package/dist/ts/webgl2-renderer.js +34 -0
- package/dist/ts/webgl2-renderer.js.map +1 -1
- package/dist/ts/webgpu-renderer.d.ts +9 -0
- package/dist/ts/webgpu-renderer.d.ts.map +1 -1
- package/dist/ts/webgpu-renderer.js +70 -2
- package/dist/ts/webgpu-renderer.js.map +1 -1
- package/dist/ts/worker.d.ts +11 -0
- package/dist/ts/worker.d.ts.map +1 -1
- package/dist/ts/worker.js +620 -158
- package/dist/ts/worker.js.map +1 -1
- package/dist/wrapper.d.ts +6 -3
- package/dist/wrapper.d.ts.map +1 -1
- package/dist/wrapper.js +5 -2
- package/dist/wrapper.js.map +1 -1
- package/examples/README.md +38 -0
- package/examples/hlsjs/README.md +53 -0
- package/examples/react/README.md +60 -0
- package/examples/shaka/README.md +61 -0
- package/examples/videojs/README.md +55 -0
- package/package.json +66 -8
- package/pkg/README.md +296 -49
- package/pkg/libbitsub.d.ts +65 -1
- package/pkg/libbitsub.js +228 -3
- package/pkg/libbitsub_bg.wasm +0 -0
- package/pkg/libbitsub_bg.wasm.d.ts +28 -0
- package/pkg/package.json +4 -4
- package/src/integrations/hlsjs.ts +154 -0
- package/src/integrations/index.ts +36 -0
- package/src/integrations/react.tsx +176 -0
- package/src/integrations/shaka.ts +92 -0
- package/src/integrations/shared.ts +171 -0
- package/src/integrations/videojs.ts +200 -0
- package/src/wrapper.ts +34 -2
package/dist/ts/worker.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Web Worker management for libbitsub.
|
|
3
3
|
* Handles off-main-thread subtitle parsing and rendering.
|
|
4
4
|
*/
|
|
5
|
-
import { getWasmUrl } from './wasm';
|
|
5
|
+
import { getWasmGlueUrl, getWasmUrl } from './wasm';
|
|
6
6
|
let sharedWorker = null;
|
|
7
7
|
let workerInitPromise = null;
|
|
8
8
|
let messageId = 0;
|
|
@@ -15,60 +15,10 @@ export function isWorkerAvailable() {
|
|
|
15
15
|
function createWorkerScript() {
|
|
16
16
|
return `
|
|
17
17
|
let wasmModule = null;
|
|
18
|
-
let wasm;
|
|
19
|
-
let cachedUint8Memory = null;
|
|
20
|
-
let WASM_VECTOR_LEN = 0;
|
|
21
|
-
let cachedTextEncoder = new TextEncoder();
|
|
22
|
-
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
23
18
|
const pgsParsers = new Map();
|
|
19
|
+
const dvbParsers = new Map();
|
|
24
20
|
const vobSubParsers = new Map();
|
|
25
|
-
|
|
26
|
-
function getUint8Memory() {
|
|
27
|
-
if (cachedUint8Memory === null || cachedUint8Memory.byteLength === 0) {
|
|
28
|
-
cachedUint8Memory = new Uint8Array(wasm.memory.buffer);
|
|
29
|
-
}
|
|
30
|
-
return cachedUint8Memory;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function passArray8ToWasm(arg) {
|
|
34
|
-
const ptr = wasm.__wbindgen_malloc(arg.length);
|
|
35
|
-
getUint8Memory().set(arg, ptr);
|
|
36
|
-
WASM_VECTOR_LEN = arg.length;
|
|
37
|
-
return ptr;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const encodeString = typeof cachedTextEncoder.encodeInto === 'function'
|
|
41
|
-
? function(arg, view) { return cachedTextEncoder.encodeInto(arg, view); }
|
|
42
|
-
: function(arg, view) {
|
|
43
|
-
const buf = cachedTextEncoder.encode(arg);
|
|
44
|
-
view.set(buf);
|
|
45
|
-
return { read: arg.length, written: buf.length };
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
function passStringToWasm(arg) {
|
|
49
|
-
let len = arg.length;
|
|
50
|
-
let ptr = wasm.__wbindgen_malloc(len);
|
|
51
|
-
const mem = getUint8Memory();
|
|
52
|
-
let offset = 0;
|
|
53
|
-
for (; offset < len; offset++) {
|
|
54
|
-
const code = arg.charCodeAt(offset);
|
|
55
|
-
if (code > 0x7F) break;
|
|
56
|
-
mem[ptr + offset] = code;
|
|
57
|
-
}
|
|
58
|
-
if (offset !== len) {
|
|
59
|
-
if (offset !== 0) arg = arg.slice(offset);
|
|
60
|
-
ptr = wasm.__wbindgen_realloc(ptr, len, len = offset + arg.length * 3);
|
|
61
|
-
const view = getUint8Memory().subarray(ptr + offset, ptr + len);
|
|
62
|
-
const ret = encodeString(arg, view);
|
|
63
|
-
offset += ret.written;
|
|
64
|
-
}
|
|
65
|
-
WASM_VECTOR_LEN = offset;
|
|
66
|
-
return ptr;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function getStringFromWasm(ptr, len) {
|
|
70
|
-
return cachedTextDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
|
|
71
|
-
}
|
|
21
|
+
const offscreenSurfaces = new Map();
|
|
72
22
|
|
|
73
23
|
function buildPgsMetadata(parser) {
|
|
74
24
|
return {
|
|
@@ -79,6 +29,15 @@ function buildPgsMetadata(parser) {
|
|
|
79
29
|
};
|
|
80
30
|
}
|
|
81
31
|
|
|
32
|
+
function buildDvbMetadata(parser) {
|
|
33
|
+
return {
|
|
34
|
+
format: 'dvb',
|
|
35
|
+
cueCount: parser.count,
|
|
36
|
+
screenWidth: parser.screenWidth || 0,
|
|
37
|
+
screenHeight: parser.screenHeight || 0
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
82
41
|
function buildVobSubMetadata(parser) {
|
|
83
42
|
return {
|
|
84
43
|
format: 'vobsub',
|
|
@@ -91,87 +50,162 @@ function buildVobSubMetadata(parser) {
|
|
|
91
50
|
};
|
|
92
51
|
}
|
|
93
52
|
|
|
53
|
+
function detachOffscreenSurface(sessionId) {
|
|
54
|
+
offscreenSurfaces.delete(sessionId);
|
|
55
|
+
}
|
|
56
|
+
|
|
94
57
|
function disposeSession(sessionId) {
|
|
95
58
|
const pgsParser = pgsParsers.get(sessionId);
|
|
96
59
|
if (pgsParser) {
|
|
97
60
|
pgsParser.free();
|
|
98
61
|
pgsParsers.delete(sessionId);
|
|
99
62
|
}
|
|
63
|
+
const dvbParser = dvbParsers.get(sessionId);
|
|
64
|
+
if (dvbParser) {
|
|
65
|
+
dvbParser.free();
|
|
66
|
+
dvbParsers.delete(sessionId);
|
|
67
|
+
}
|
|
100
68
|
const vobSubParser = vobSubParsers.get(sessionId);
|
|
101
69
|
if (vobSubParser) {
|
|
102
70
|
vobSubParser.free();
|
|
103
71
|
vobSubParsers.delete(sessionId);
|
|
104
72
|
}
|
|
73
|
+
detachOffscreenSurface(sessionId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getCompositionBounds(compositions) {
|
|
77
|
+
if (!compositions || compositions.length === 0) return null;
|
|
78
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
79
|
+
for (const comp of compositions) {
|
|
80
|
+
minX = Math.min(minX, comp.x);
|
|
81
|
+
minY = Math.min(minY, comp.y);
|
|
82
|
+
maxX = Math.max(maxX, comp.x + comp.width);
|
|
83
|
+
maxY = Math.max(maxY, comp.y + comp.height);
|
|
84
|
+
}
|
|
85
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY)) return null;
|
|
86
|
+
return { x: minX, y: minY, width: Math.max(0, maxX - minX), height: Math.max(0, maxY - minY) };
|
|
105
87
|
}
|
|
106
88
|
|
|
107
|
-
|
|
108
|
-
|
|
89
|
+
function computeOffscreenLayout(frame, canvasWidth, canvasHeight, settings) {
|
|
90
|
+
const safeDataWidth = frame.width > 0 ? frame.width : canvasWidth;
|
|
91
|
+
const safeDataHeight = frame.height > 0 ? frame.height : canvasHeight;
|
|
92
|
+
const stretchScaleX = canvasWidth / safeDataWidth;
|
|
93
|
+
const stretchScaleY = canvasHeight / safeDataHeight;
|
|
94
|
+
const bounds = getCompositionBounds(frame.compositions) || {
|
|
95
|
+
x: 0, y: 0, width: safeDataWidth, height: safeDataHeight
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const scale = settings.scale;
|
|
99
|
+
const aspectMode = settings.aspectMode;
|
|
100
|
+
const verticalOffset = settings.verticalOffset;
|
|
101
|
+
const horizontalOffset = settings.horizontalOffset;
|
|
102
|
+
const horizontalAlign = settings.horizontalAlign;
|
|
103
|
+
const bottomPadding = settings.bottomPadding;
|
|
104
|
+
const safeArea = settings.safeArea;
|
|
105
|
+
const opacity = settings.opacity;
|
|
109
106
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
let baseScaleX = stretchScaleX;
|
|
108
|
+
let baseScaleY = stretchScaleY;
|
|
109
|
+
let frameShiftX = 0;
|
|
110
|
+
let frameShiftY = 0;
|
|
111
|
+
|
|
112
|
+
if (aspectMode !== 'stretch') {
|
|
113
|
+
const uniformScale = aspectMode === 'cover'
|
|
114
|
+
? Math.max(stretchScaleX, stretchScaleY)
|
|
115
|
+
: Math.min(stretchScaleX, stretchScaleY);
|
|
116
|
+
baseScaleX = uniformScale;
|
|
117
|
+
baseScaleY = uniformScale;
|
|
118
|
+
frameShiftX = (canvasWidth - safeDataWidth * uniformScale) / 2;
|
|
119
|
+
frameShiftY = (canvasHeight - safeDataHeight * uniformScale) / 2;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
get screenWidth() { return wasm.vobsubparser_screenWidth(this.ptr); }
|
|
167
|
-
get screenHeight() { return wasm.vobsubparser_screenHeight(this.ptr); }
|
|
168
|
-
get language() { return wasm.vobsubparser_language(this.ptr); }
|
|
169
|
-
get trackId() { return wasm.vobsubparser_trackId(this.ptr); }
|
|
170
|
-
get hasIdxMetadata() { return !!wasm.vobsubparser_hasIdxMetadata(this.ptr); }
|
|
122
|
+
const anchorX = horizontalAlign === 'left'
|
|
123
|
+
? bounds.x
|
|
124
|
+
: horizontalAlign === 'right'
|
|
125
|
+
? bounds.x + bounds.width
|
|
126
|
+
: bounds.x + bounds.width / 2;
|
|
127
|
+
const anchorY = bounds.y + bounds.height;
|
|
128
|
+
|
|
129
|
+
const scaleX = baseScaleX * scale;
|
|
130
|
+
const scaleY = baseScaleY * scale;
|
|
131
|
+
const anchorShiftX = frameShiftX + anchorX * baseScaleX * (1 - scale);
|
|
132
|
+
const anchorShiftY = frameShiftY + anchorY * baseScaleY * (1 - scale);
|
|
133
|
+
|
|
134
|
+
let shiftX = anchorShiftX + (horizontalOffset / 100) * canvasWidth;
|
|
135
|
+
let shiftY = anchorShiftY + (verticalOffset / 100) * canvasHeight;
|
|
136
|
+
shiftY -= (bottomPadding / 100) * canvasHeight;
|
|
137
|
+
|
|
138
|
+
const safeX = (safeArea / 100) * canvasWidth;
|
|
139
|
+
const safeY = (safeArea / 100) * canvasHeight;
|
|
140
|
+
const finalMinX = bounds.x * scaleX + shiftX;
|
|
141
|
+
const finalMinY = bounds.y * scaleY + shiftY;
|
|
142
|
+
const finalMaxX = (bounds.x + bounds.width) * scaleX + shiftX;
|
|
143
|
+
const finalMaxY = (bounds.y + bounds.height) * scaleY + shiftY;
|
|
144
|
+
|
|
145
|
+
if (finalMinX < safeX) shiftX += safeX - finalMinX;
|
|
146
|
+
if (finalMaxX > canvasWidth - safeX) shiftX -= finalMaxX - (canvasWidth - safeX);
|
|
147
|
+
if (finalMinY < safeY) shiftY += safeY - finalMinY;
|
|
148
|
+
if (finalMaxY > canvasHeight - safeY) shiftY -= finalMaxY - (canvasHeight - safeY);
|
|
149
|
+
|
|
150
|
+
return { scaleX, scaleY, shiftX, shiftY, opacity };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function presentFrameToOffscreen(surface, frame, canvasWidth, canvasHeight, settings) {
|
|
154
|
+
const ctx = surface.ctx;
|
|
155
|
+
const canvas = surface.canvas;
|
|
156
|
+
if (canvas.width !== canvasWidth) canvas.width = canvasWidth;
|
|
157
|
+
if (canvas.height !== canvasHeight) canvas.height = canvasHeight;
|
|
158
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
159
|
+
|
|
160
|
+
if (!frame || !frame.compositions || frame.compositions.length === 0) {
|
|
161
|
+
return { status: frame ? 'empty' : 'cleared', width: frame?.width || 0, height: frame?.height || 0, compositionCount: 0 };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const layout = computeOffscreenLayout(frame, canvas.width, canvas.height, settings);
|
|
165
|
+
ctx.save();
|
|
166
|
+
ctx.globalAlpha = layout.opacity;
|
|
167
|
+
|
|
168
|
+
for (const comp of frame.compositions) {
|
|
169
|
+
if (!comp.width || !comp.height || !comp.rgba) continue;
|
|
170
|
+
if (surface.buffer.width !== comp.width || surface.buffer.height !== comp.height) {
|
|
171
|
+
surface.buffer.width = comp.width;
|
|
172
|
+
surface.buffer.height = comp.height;
|
|
171
173
|
}
|
|
174
|
+
const pixels = comp.rgba instanceof Uint8ClampedArray
|
|
175
|
+
? comp.rgba
|
|
176
|
+
: new Uint8ClampedArray(comp.rgba.buffer, comp.rgba.byteOffset, comp.rgba.byteLength);
|
|
177
|
+
surface.bufferCtx.putImageData(new ImageData(pixels, comp.width, comp.height), 0, 0);
|
|
178
|
+
const scaledWidth = comp.width * layout.scaleX;
|
|
179
|
+
const scaledHeight = comp.height * layout.scaleY;
|
|
180
|
+
const adjustedX = comp.x * layout.scaleX + layout.shiftX;
|
|
181
|
+
const adjustedY = comp.y * layout.scaleY + layout.shiftY;
|
|
182
|
+
ctx.drawImage(surface.buffer, adjustedX, adjustedY, scaledWidth, scaledHeight);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
ctx.restore();
|
|
186
|
+
return {
|
|
187
|
+
status: 'rendered',
|
|
188
|
+
width: frame.width,
|
|
189
|
+
height: frame.height,
|
|
190
|
+
compositionCount: frame.compositions.length,
|
|
191
|
+
bounds: getCompositionBounds(frame.compositions)
|
|
172
192
|
};
|
|
173
193
|
}
|
|
174
194
|
|
|
195
|
+
async function initWasm(wasmUrl, glueUrl) {
|
|
196
|
+
if (wasmModule) return;
|
|
197
|
+
|
|
198
|
+
let jsGlueUrl = glueUrl;
|
|
199
|
+
if (!jsGlueUrl) {
|
|
200
|
+
const derivedUrl = new URL(wasmUrl);
|
|
201
|
+
derivedUrl.pathname = derivedUrl.pathname.replace(/_bg\.wasm$/, '.js');
|
|
202
|
+
jsGlueUrl = derivedUrl.href;
|
|
203
|
+
}
|
|
204
|
+
const mod = await import(jsGlueUrl);
|
|
205
|
+
await mod.default({ module_or_path: wasmUrl });
|
|
206
|
+
wasmModule = mod;
|
|
207
|
+
}
|
|
208
|
+
|
|
175
209
|
function convertFrame(frame, isVobSub) {
|
|
176
210
|
const compositions = [];
|
|
177
211
|
if (isVobSub) {
|
|
@@ -205,7 +239,7 @@ self.onmessage = async function(event) {
|
|
|
205
239
|
try {
|
|
206
240
|
switch (request.type) {
|
|
207
241
|
case 'init': {
|
|
208
|
-
await initWasm(request.wasmUrl);
|
|
242
|
+
await initWasm(request.wasmUrl, request.glueUrl);
|
|
209
243
|
postResponse({ type: 'initComplete', success: true }, [], _id);
|
|
210
244
|
break;
|
|
211
245
|
}
|
|
@@ -222,6 +256,145 @@ self.onmessage = async function(event) {
|
|
|
222
256
|
);
|
|
223
257
|
break;
|
|
224
258
|
}
|
|
259
|
+
case 'beginPgs': {
|
|
260
|
+
disposeSession(request.sessionId);
|
|
261
|
+
const parser = new wasmModule.PgsParser();
|
|
262
|
+
parser.reset();
|
|
263
|
+
pgsParsers.set(request.sessionId, parser);
|
|
264
|
+
const timestamps = parser.getTimestamps();
|
|
265
|
+
postResponse(
|
|
266
|
+
{ type: 'pgsProgress', count: 0, added: 0, partial: true, metadata: buildPgsMetadata(parser), timestamps },
|
|
267
|
+
[timestamps.buffer],
|
|
268
|
+
_id
|
|
269
|
+
);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case 'appendPgs': {
|
|
273
|
+
let parser = pgsParsers.get(request.sessionId);
|
|
274
|
+
if (!parser) {
|
|
275
|
+
parser = new wasmModule.PgsParser();
|
|
276
|
+
parser.reset();
|
|
277
|
+
pgsParsers.set(request.sessionId, parser);
|
|
278
|
+
}
|
|
279
|
+
const added = parser.feed(new Uint8Array(request.data));
|
|
280
|
+
const timestamps = parser.getTimestamps();
|
|
281
|
+
postResponse(
|
|
282
|
+
{ type: 'pgsProgress', count: parser.count, added, partial: true, metadata: buildPgsMetadata(parser), timestamps },
|
|
283
|
+
[timestamps.buffer],
|
|
284
|
+
_id
|
|
285
|
+
);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
case 'finishPgs': {
|
|
289
|
+
const parser = pgsParsers.get(request.sessionId);
|
|
290
|
+
if (!parser) {
|
|
291
|
+
postResponse({ type: 'error', message: 'PGS session not found for finishPgs' }, [], _id);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
const count = parser.finishFeed();
|
|
295
|
+
const timestamps = parser.getTimestamps();
|
|
296
|
+
postResponse(
|
|
297
|
+
{ type: 'pgsProgress', count, added: 0, partial: false, metadata: buildPgsMetadata(parser), timestamps },
|
|
298
|
+
[timestamps.buffer],
|
|
299
|
+
_id
|
|
300
|
+
);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case 'resetPgs': {
|
|
304
|
+
let parser = pgsParsers.get(request.sessionId);
|
|
305
|
+
if (!parser) {
|
|
306
|
+
parser = new wasmModule.PgsParser();
|
|
307
|
+
pgsParsers.set(request.sessionId, parser);
|
|
308
|
+
}
|
|
309
|
+
parser.reset();
|
|
310
|
+
const timestamps = parser.getTimestamps();
|
|
311
|
+
postResponse(
|
|
312
|
+
{ type: 'pgsProgress', count: 0, added: 0, partial: true, metadata: buildPgsMetadata(parser), timestamps },
|
|
313
|
+
[timestamps.buffer],
|
|
314
|
+
_id
|
|
315
|
+
);
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
case 'loadDvb': {
|
|
319
|
+
disposeSession(request.sessionId);
|
|
320
|
+
const parser = new wasmModule.DvbParser();
|
|
321
|
+
const count = parser.parse(new Uint8Array(request.data));
|
|
322
|
+
const timestamps = parser.getTimestamps();
|
|
323
|
+
const endTimestamps = parser.getEndTimestamps();
|
|
324
|
+
dvbParsers.set(request.sessionId, parser);
|
|
325
|
+
postResponse(
|
|
326
|
+
{ type: 'dvbLoaded', count, byteLength: request.data.byteLength, metadata: buildDvbMetadata(parser), timestamps, endTimestamps },
|
|
327
|
+
[timestamps.buffer, endTimestamps.buffer],
|
|
328
|
+
_id
|
|
329
|
+
);
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case 'beginDvb': {
|
|
333
|
+
disposeSession(request.sessionId);
|
|
334
|
+
const parser = new wasmModule.DvbParser();
|
|
335
|
+
parser.reset();
|
|
336
|
+
dvbParsers.set(request.sessionId, parser);
|
|
337
|
+
const timestamps = parser.getTimestamps();
|
|
338
|
+
const endTimestamps = parser.getEndTimestamps();
|
|
339
|
+
postResponse(
|
|
340
|
+
{ type: 'dvbProgress', count: 0, added: 0, partial: true, metadata: buildDvbMetadata(parser), timestamps, endTimestamps },
|
|
341
|
+
[timestamps.buffer, endTimestamps.buffer],
|
|
342
|
+
_id
|
|
343
|
+
);
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case 'appendDvb': {
|
|
347
|
+
let parser = dvbParsers.get(request.sessionId);
|
|
348
|
+
if (!parser) {
|
|
349
|
+
parser = new wasmModule.DvbParser();
|
|
350
|
+
parser.reset();
|
|
351
|
+
dvbParsers.set(request.sessionId, parser);
|
|
352
|
+
}
|
|
353
|
+
const added = parser.feed(new Uint8Array(request.data));
|
|
354
|
+
const timestamps = parser.getTimestamps();
|
|
355
|
+
const endTimestamps = parser.getEndTimestamps();
|
|
356
|
+
postResponse(
|
|
357
|
+
{ type: 'dvbProgress', count: parser.count, added, partial: true, metadata: buildDvbMetadata(parser), timestamps, endTimestamps },
|
|
358
|
+
[timestamps.buffer, endTimestamps.buffer],
|
|
359
|
+
_id
|
|
360
|
+
);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
case 'finishDvb': {
|
|
364
|
+
const parser = dvbParsers.get(request.sessionId);
|
|
365
|
+
if (!parser) {
|
|
366
|
+
postResponse({ type: 'error', message: 'DVB session not found for finishDvb' }, [], _id);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
const previousCount = parser.count;
|
|
370
|
+
parser.finishFeed();
|
|
371
|
+
const count = parser.count;
|
|
372
|
+
const added = count - previousCount;
|
|
373
|
+
const timestamps = parser.getTimestamps();
|
|
374
|
+
const endTimestamps = parser.getEndTimestamps();
|
|
375
|
+
postResponse(
|
|
376
|
+
{ type: 'dvbProgress', count, added, partial: false, metadata: buildDvbMetadata(parser), timestamps, endTimestamps },
|
|
377
|
+
[timestamps.buffer, endTimestamps.buffer],
|
|
378
|
+
_id
|
|
379
|
+
);
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
case 'resetDvb': {
|
|
383
|
+
let parser = dvbParsers.get(request.sessionId);
|
|
384
|
+
if (!parser) {
|
|
385
|
+
parser = new wasmModule.DvbParser();
|
|
386
|
+
dvbParsers.set(request.sessionId, parser);
|
|
387
|
+
}
|
|
388
|
+
parser.reset();
|
|
389
|
+
const timestamps = parser.getTimestamps();
|
|
390
|
+
const endTimestamps = parser.getEndTimestamps();
|
|
391
|
+
postResponse(
|
|
392
|
+
{ type: 'dvbProgress', count: 0, added: 0, partial: true, metadata: buildDvbMetadata(parser), timestamps, endTimestamps },
|
|
393
|
+
[timestamps.buffer, endTimestamps.buffer],
|
|
394
|
+
_id
|
|
395
|
+
);
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
225
398
|
case 'loadVobSub': {
|
|
226
399
|
disposeSession(request.sessionId);
|
|
227
400
|
const parser = new wasmModule.VobSubParser();
|
|
@@ -235,6 +408,48 @@ self.onmessage = async function(event) {
|
|
|
235
408
|
);
|
|
236
409
|
break;
|
|
237
410
|
}
|
|
411
|
+
case 'loadVobSubIdx': {
|
|
412
|
+
disposeSession(request.sessionId);
|
|
413
|
+
const parser = new wasmModule.VobSubParser();
|
|
414
|
+
parser.loadFromIdx(request.idxContent);
|
|
415
|
+
const timestamps = parser.getTimestamps();
|
|
416
|
+
vobSubParsers.set(request.sessionId, parser);
|
|
417
|
+
postResponse(
|
|
418
|
+
{
|
|
419
|
+
type: 'vobSubProgress',
|
|
420
|
+
count: parser.count,
|
|
421
|
+
partial: true,
|
|
422
|
+
hasSubData: !!parser.hasSubData,
|
|
423
|
+
metadata: buildVobSubMetadata(parser),
|
|
424
|
+
timestamps
|
|
425
|
+
},
|
|
426
|
+
[timestamps.buffer],
|
|
427
|
+
_id
|
|
428
|
+
);
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
case 'attachVobSubData': {
|
|
432
|
+
const parser = vobSubParsers.get(request.sessionId);
|
|
433
|
+
if (!parser) {
|
|
434
|
+
postResponse({ type: 'error', message: 'VobSub session not found for attachVobSubData' }, [], _id);
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
parser.attachSubData(new Uint8Array(request.subData));
|
|
438
|
+
const timestamps = parser.getTimestamps();
|
|
439
|
+
postResponse(
|
|
440
|
+
{
|
|
441
|
+
type: 'vobSubProgress',
|
|
442
|
+
count: parser.count,
|
|
443
|
+
partial: false,
|
|
444
|
+
hasSubData: !!parser.hasSubData,
|
|
445
|
+
metadata: buildVobSubMetadata(parser),
|
|
446
|
+
timestamps
|
|
447
|
+
},
|
|
448
|
+
[timestamps.buffer],
|
|
449
|
+
_id
|
|
450
|
+
);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
238
453
|
case 'loadVobSubMks': {
|
|
239
454
|
disposeSession(request.sessionId);
|
|
240
455
|
const parser = new wasmModule.VobSubParser();
|
|
@@ -271,6 +486,16 @@ self.onmessage = async function(event) {
|
|
|
271
486
|
postResponse({ type: 'pgsFrame', frame: frameData, renderIssue }, frameData.compositions.map((c) => c.rgba.buffer), _id);
|
|
272
487
|
break;
|
|
273
488
|
}
|
|
489
|
+
case 'renderDvbAtIndex': {
|
|
490
|
+
const parser = dvbParsers.get(request.sessionId);
|
|
491
|
+
if (!parser) { postResponse({ type: 'dvbFrame', frame: null }, [], _id); break; }
|
|
492
|
+
const frame = parser.renderAtIndex(request.index);
|
|
493
|
+
const renderIssue = parser.lastRenderIssue || '';
|
|
494
|
+
if (!frame) { postResponse({ type: 'dvbFrame', frame: null, renderIssue }, [], _id); break; }
|
|
495
|
+
const frameData = convertFrame(frame, false);
|
|
496
|
+
postResponse({ type: 'dvbFrame', frame: frameData, renderIssue }, frameData.compositions.map((c) => c.rgba.buffer), _id);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
274
499
|
case 'renderVobSubAtIndex': {
|
|
275
500
|
const parser = vobSubParsers.get(request.sessionId);
|
|
276
501
|
if (!parser) { postResponse({ type: 'vobSubFrame', frame: null }, [], _id); break; }
|
|
@@ -286,6 +511,11 @@ self.onmessage = async function(event) {
|
|
|
286
511
|
postResponse({ type: 'pgsIndex', index: parser ? parser.findIndexAtTimestamp(request.timeMs) : -1 }, [], _id);
|
|
287
512
|
break;
|
|
288
513
|
}
|
|
514
|
+
case 'findDvbIndex': {
|
|
515
|
+
const parser = dvbParsers.get(request.sessionId);
|
|
516
|
+
postResponse({ type: 'dvbIndex', index: parser ? parser.findIndexAtTimestamp(request.timeMs) : -1 }, [], _id);
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
289
519
|
case 'findVobSubIndex': {
|
|
290
520
|
const parser = vobSubParsers.get(request.sessionId);
|
|
291
521
|
postResponse({ type: 'vobSubIndex', index: parser ? parser.findIndexAtTimestamp(request.timeMs) : -1 }, [], _id);
|
|
@@ -296,6 +526,11 @@ self.onmessage = async function(event) {
|
|
|
296
526
|
postResponse({ type: 'pgsTimestamps', timestamps: parser ? parser.getTimestamps() : new Float64Array(0) }, [], _id);
|
|
297
527
|
break;
|
|
298
528
|
}
|
|
529
|
+
case 'getDvbTimestamps': {
|
|
530
|
+
const parser = dvbParsers.get(request.sessionId);
|
|
531
|
+
postResponse({ type: 'dvbTimestamps', timestamps: parser ? parser.getTimestamps() : new Float64Array(0) }, [], _id);
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
299
534
|
case 'getVobSubTimestamps': {
|
|
300
535
|
const parser = vobSubParsers.get(request.sessionId);
|
|
301
536
|
postResponse({ type: 'vobSubTimestamps', timestamps: parser ? parser.getTimestamps() : new Float64Array(0) }, [], _id);
|
|
@@ -306,6 +541,11 @@ self.onmessage = async function(event) {
|
|
|
306
541
|
postResponse({ type: 'cleared' }, [], _id);
|
|
307
542
|
break;
|
|
308
543
|
}
|
|
544
|
+
case 'clearDvbCache': {
|
|
545
|
+
dvbParsers.get(request.sessionId)?.clearCache();
|
|
546
|
+
postResponse({ type: 'cleared' }, [], _id);
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
309
549
|
case 'clearVobSubCache': {
|
|
310
550
|
vobSubParsers.get(request.sessionId)?.clearCache();
|
|
311
551
|
postResponse({ type: 'cleared' }, [], _id);
|
|
@@ -320,6 +560,15 @@ self.onmessage = async function(event) {
|
|
|
320
560
|
postResponse({ type: 'disposed' }, [], _id);
|
|
321
561
|
break;
|
|
322
562
|
}
|
|
563
|
+
case 'disposeDvb': {
|
|
564
|
+
const parser = dvbParsers.get(request.sessionId);
|
|
565
|
+
if (parser) {
|
|
566
|
+
parser.free();
|
|
567
|
+
dvbParsers.delete(request.sessionId);
|
|
568
|
+
}
|
|
569
|
+
postResponse({ type: 'disposed' }, [], _id);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
323
572
|
case 'disposeVobSub': {
|
|
324
573
|
const parser = vobSubParsers.get(request.sessionId);
|
|
325
574
|
if (parser) {
|
|
@@ -344,63 +593,237 @@ self.onmessage = async function(event) {
|
|
|
344
593
|
postResponse({ type: 'debandSet' }, [], _id);
|
|
345
594
|
break;
|
|
346
595
|
}
|
|
596
|
+
case 'attachOffscreenCanvas': {
|
|
597
|
+
const canvas = request.canvas;
|
|
598
|
+
if (!canvas || typeof canvas.getContext !== 'function') {
|
|
599
|
+
throw new Error('OffscreenCanvas attach requires a transferable OffscreenCanvas');
|
|
600
|
+
}
|
|
601
|
+
if (offscreenSurfaces.has(request.sessionId)) {
|
|
602
|
+
throw new Error('OffscreenCanvas already attached for this session');
|
|
603
|
+
}
|
|
604
|
+
const ctx = canvas.getContext('2d', { alpha: true, desynchronized: true });
|
|
605
|
+
if (!ctx) {
|
|
606
|
+
throw new Error('OffscreenCanvas 2D context unavailable');
|
|
607
|
+
}
|
|
608
|
+
const buffer = new OffscreenCanvas(1, 1);
|
|
609
|
+
const bufferCtx = buffer.getContext('2d', { alpha: true, desynchronized: true });
|
|
610
|
+
if (!bufferCtx) {
|
|
611
|
+
throw new Error('OffscreenCanvas buffer 2D context unavailable');
|
|
612
|
+
}
|
|
613
|
+
offscreenSurfaces.set(request.sessionId, { canvas, ctx, buffer, bufferCtx });
|
|
614
|
+
postResponse({ type: 'offscreenAttached' }, [], _id);
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
case 'resizeOffscreenCanvas': {
|
|
618
|
+
const surface = offscreenSurfaces.get(request.sessionId);
|
|
619
|
+
if (surface) {
|
|
620
|
+
const width = Math.max(1, request.width | 0);
|
|
621
|
+
const height = Math.max(1, request.height | 0);
|
|
622
|
+
if (surface.canvas.width !== width) surface.canvas.width = width;
|
|
623
|
+
if (surface.canvas.height !== height) surface.canvas.height = height;
|
|
624
|
+
}
|
|
625
|
+
postResponse({ type: 'offscreenResized' }, [], _id);
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
case 'detachOffscreenCanvas': {
|
|
629
|
+
detachOffscreenSurface(request.sessionId);
|
|
630
|
+
postResponse({ type: 'offscreenDetached' }, [], _id);
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
case 'clearOffscreenCanvas': {
|
|
634
|
+
const surface = offscreenSurfaces.get(request.sessionId);
|
|
635
|
+
if (surface) {
|
|
636
|
+
surface.ctx.clearRect(0, 0, surface.canvas.width, surface.canvas.height);
|
|
637
|
+
}
|
|
638
|
+
postResponse({ type: 'offscreenCleared' }, [], _id);
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
case 'presentOffscreen': {
|
|
642
|
+
const surface = offscreenSurfaces.get(request.sessionId);
|
|
643
|
+
if (!surface) {
|
|
644
|
+
postResponse({ type: 'offscreenPresented', status: 'failed', fatal: true, renderIssue: 'OFFSCREEN_SURFACE_MISSING' }, [], _id);
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const canvasWidth = Math.max(1, request.canvasWidth | 0);
|
|
649
|
+
const canvasHeight = Math.max(1, request.canvasHeight | 0);
|
|
650
|
+
const settings = request.displaySettings || {
|
|
651
|
+
scale: 1, aspectMode: 'stretch', verticalOffset: 0, horizontalOffset: 0,
|
|
652
|
+
horizontalAlign: 'center', bottomPadding: 0, safeArea: 0, opacity: 1
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
if (request.index < 0) {
|
|
656
|
+
if (surface.canvas.width !== canvasWidth) surface.canvas.width = canvasWidth;
|
|
657
|
+
if (surface.canvas.height !== canvasHeight) surface.canvas.height = canvasHeight;
|
|
658
|
+
surface.ctx.clearRect(0, 0, surface.canvas.width, surface.canvas.height);
|
|
659
|
+
postResponse({ type: 'offscreenPresented', status: 'cleared', compositionCount: 0 }, [], _id);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
let frame = null;
|
|
664
|
+
let renderIssue = '';
|
|
665
|
+
if (request.format === 'pgs') {
|
|
666
|
+
const parser = pgsParsers.get(request.sessionId);
|
|
667
|
+
if (!parser) {
|
|
668
|
+
postResponse({ type: 'offscreenPresented', status: 'failed', fatal: true, renderIssue: 'PARSER_MISSING' }, [], _id);
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
const rendered = parser.renderAtIndex(request.index);
|
|
672
|
+
renderIssue = parser.lastRenderIssue || '';
|
|
673
|
+
frame = rendered ? convertFrame(rendered, false) : null;
|
|
674
|
+
} else if (request.format === 'dvb') {
|
|
675
|
+
const parser = dvbParsers.get(request.sessionId);
|
|
676
|
+
if (!parser) {
|
|
677
|
+
postResponse({ type: 'offscreenPresented', status: 'failed', fatal: true, renderIssue: 'PARSER_MISSING' }, [], _id);
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
const rendered = parser.renderAtIndex(request.index);
|
|
681
|
+
renderIssue = parser.lastRenderIssue || '';
|
|
682
|
+
frame = rendered ? convertFrame(rendered, false) : null;
|
|
683
|
+
} else {
|
|
684
|
+
const parser = vobSubParsers.get(request.sessionId);
|
|
685
|
+
if (!parser) {
|
|
686
|
+
postResponse({ type: 'offscreenPresented', status: 'failed', fatal: true, renderIssue: 'PARSER_MISSING' }, [], _id);
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
const rendered = parser.renderAtIndex(request.index);
|
|
690
|
+
renderIssue = parser.lastRenderIssue || '';
|
|
691
|
+
frame = rendered ? convertFrame(rendered, true) : null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (!frame) {
|
|
695
|
+
if (surface.canvas.width !== canvasWidth) surface.canvas.width = canvasWidth;
|
|
696
|
+
if (surface.canvas.height !== canvasHeight) surface.canvas.height = canvasHeight;
|
|
697
|
+
surface.ctx.clearRect(0, 0, surface.canvas.width, surface.canvas.height);
|
|
698
|
+
postResponse({
|
|
699
|
+
type: 'offscreenPresented',
|
|
700
|
+
status: renderIssue ? 'failed' : 'empty',
|
|
701
|
+
renderIssue,
|
|
702
|
+
compositionCount: 0
|
|
703
|
+
}, [], _id);
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const presented = presentFrameToOffscreen(surface, frame, canvasWidth, canvasHeight, settings);
|
|
708
|
+
postResponse({
|
|
709
|
+
type: 'offscreenPresented',
|
|
710
|
+
status: presented.status,
|
|
711
|
+
renderIssue,
|
|
712
|
+
width: presented.width,
|
|
713
|
+
height: presented.height,
|
|
714
|
+
compositionCount: presented.compositionCount,
|
|
715
|
+
bounds: presented.bounds || null
|
|
716
|
+
}, [], _id);
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
347
719
|
}
|
|
348
720
|
} catch (error) {
|
|
349
721
|
postResponse({ type: 'error', message: error instanceof Error ? error.message : String(error) }, [], _id);
|
|
350
722
|
}
|
|
351
723
|
};`;
|
|
352
724
|
}
|
|
725
|
+
/** Whether the shared worker has finished WASM initialization. */
|
|
726
|
+
export function isWorkerReady() {
|
|
727
|
+
return sharedWorker !== null;
|
|
728
|
+
}
|
|
353
729
|
/** Create or get the shared worker instance. */
|
|
354
730
|
export function getOrCreateWorker() {
|
|
355
731
|
if (sharedWorker)
|
|
356
732
|
return Promise.resolve(sharedWorker);
|
|
357
733
|
if (workerInitPromise)
|
|
358
734
|
return workerInitPromise;
|
|
359
|
-
|
|
735
|
+
const initPromise = initializeWorker();
|
|
736
|
+
workerInitPromise = initPromise;
|
|
737
|
+
void initPromise.then(() => {
|
|
738
|
+
if (workerInitPromise === initPromise)
|
|
739
|
+
workerInitPromise = null;
|
|
740
|
+
}, () => {
|
|
741
|
+
if (workerInitPromise === initPromise)
|
|
742
|
+
workerInitPromise = null;
|
|
743
|
+
});
|
|
744
|
+
return initPromise;
|
|
745
|
+
}
|
|
746
|
+
/** Pre-initialize the shared subtitle worker. */
|
|
747
|
+
export function warmup() {
|
|
748
|
+
return ready();
|
|
749
|
+
}
|
|
750
|
+
/** Wait until the shared worker is ready for parse/render requests. */
|
|
751
|
+
export async function ready() {
|
|
752
|
+
if (!isWorkerAvailable())
|
|
753
|
+
return;
|
|
754
|
+
if (sharedWorker)
|
|
755
|
+
return;
|
|
756
|
+
await getOrCreateWorker();
|
|
757
|
+
}
|
|
758
|
+
async function initializeWorker() {
|
|
759
|
+
const blob = new Blob([createWorkerScript()], { type: 'application/javascript' });
|
|
760
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
761
|
+
let worker;
|
|
762
|
+
try {
|
|
763
|
+
worker = new Worker(workerUrl, { type: 'module' });
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
URL.revokeObjectURL(workerUrl);
|
|
767
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
768
|
+
}
|
|
769
|
+
worker.onmessage = (event) => {
|
|
770
|
+
const { _id, ...response } = event.data;
|
|
771
|
+
if (_id === undefined)
|
|
772
|
+
return;
|
|
773
|
+
const callback = pendingCallbacks.get(_id);
|
|
774
|
+
if (!callback)
|
|
775
|
+
return;
|
|
776
|
+
pendingCallbacks.delete(_id);
|
|
777
|
+
if (response.type === 'error' && callback.requestType === 'init') {
|
|
778
|
+
callback.reject(new Error(response.message));
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
callback.resolve(response);
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
worker.onerror = (event) => {
|
|
785
|
+
const error = event instanceof ErrorEvent ? new Error(event.message) : new Error(String(event));
|
|
786
|
+
rejectWorkerCallbacks(worker, error);
|
|
787
|
+
if (sharedWorker === worker)
|
|
788
|
+
sharedWorker = null;
|
|
360
789
|
try {
|
|
361
|
-
|
|
362
|
-
const workerUrl = URL.createObjectURL(blob);
|
|
363
|
-
const worker = new Worker(workerUrl, { type: 'module' });
|
|
364
|
-
let initialized = false;
|
|
365
|
-
worker.onmessage = (event) => {
|
|
366
|
-
const { _id, ...response } = event.data;
|
|
367
|
-
if (_id !== undefined) {
|
|
368
|
-
const callback = pendingCallbacks.get(_id);
|
|
369
|
-
if (callback) {
|
|
370
|
-
pendingCallbacks.delete(_id);
|
|
371
|
-
callback.resolve(response);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
worker.onerror = (error) => {
|
|
376
|
-
if (!initialized) {
|
|
377
|
-
URL.revokeObjectURL(workerUrl);
|
|
378
|
-
worker.terminate();
|
|
379
|
-
workerInitPromise = null;
|
|
380
|
-
reject(error instanceof ErrorEvent ? new Error(error.message) : new Error(String(error)));
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
|
-
sendToWorkerInstance(worker, { type: 'init', wasmUrl: getWasmUrl() })
|
|
384
|
-
.then(() => {
|
|
385
|
-
URL.revokeObjectURL(workerUrl);
|
|
386
|
-
initialized = true;
|
|
387
|
-
sharedWorker = worker;
|
|
388
|
-
workerInitPromise = null;
|
|
389
|
-
resolve(worker);
|
|
390
|
-
})
|
|
391
|
-
.catch((err) => {
|
|
392
|
-
URL.revokeObjectURL(workerUrl);
|
|
393
|
-
worker.terminate();
|
|
394
|
-
workerInitPromise = null;
|
|
395
|
-
reject(err);
|
|
396
|
-
});
|
|
790
|
+
worker.terminate();
|
|
397
791
|
}
|
|
398
|
-
catch
|
|
399
|
-
|
|
400
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
792
|
+
catch {
|
|
793
|
+
/* ignore */
|
|
401
794
|
}
|
|
402
|
-
}
|
|
403
|
-
|
|
795
|
+
};
|
|
796
|
+
try {
|
|
797
|
+
const initResponse = await sendToWorkerInstance(worker, {
|
|
798
|
+
type: 'init',
|
|
799
|
+
wasmUrl: getWasmUrl(),
|
|
800
|
+
glueUrl: getWasmGlueUrl()
|
|
801
|
+
});
|
|
802
|
+
if (initResponse.type === 'error') {
|
|
803
|
+
throw new Error(initResponse.message);
|
|
804
|
+
}
|
|
805
|
+
if (initResponse.type !== 'initComplete' || !initResponse.success) {
|
|
806
|
+
throw new Error('Worker WASM initialization failed');
|
|
807
|
+
}
|
|
808
|
+
sharedWorker = worker;
|
|
809
|
+
return worker;
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
const workerError = error instanceof Error ? error : new Error(String(error));
|
|
813
|
+
rejectWorkerCallbacks(worker, workerError);
|
|
814
|
+
if (sharedWorker === worker)
|
|
815
|
+
sharedWorker = null;
|
|
816
|
+
try {
|
|
817
|
+
worker.terminate();
|
|
818
|
+
}
|
|
819
|
+
catch {
|
|
820
|
+
/* ignore */
|
|
821
|
+
}
|
|
822
|
+
throw workerError;
|
|
823
|
+
}
|
|
824
|
+
finally {
|
|
825
|
+
URL.revokeObjectURL(workerUrl);
|
|
826
|
+
}
|
|
404
827
|
}
|
|
405
828
|
/** Default timeout for worker operations (30 seconds for large files) */
|
|
406
829
|
const WORKER_TIMEOUT = 30000;
|
|
@@ -419,6 +842,8 @@ function sendToWorkerInstance(worker, request, timeout = WORKER_TIMEOUT) {
|
|
|
419
842
|
reject(new Error(`Worker operation timed out after ${timeout}ms`));
|
|
420
843
|
}, timeout);
|
|
421
844
|
pendingCallbacks.set(id, {
|
|
845
|
+
worker,
|
|
846
|
+
requestType: request.type,
|
|
422
847
|
resolve: (response) => {
|
|
423
848
|
clearTimeout(timeoutId);
|
|
424
849
|
resolve(response);
|
|
@@ -433,7 +858,44 @@ function sendToWorkerInstance(worker, request, timeout = WORKER_TIMEOUT) {
|
|
|
433
858
|
transfers.push(request.data);
|
|
434
859
|
if ('subData' in request && request.subData instanceof ArrayBuffer)
|
|
435
860
|
transfers.push(request.subData);
|
|
436
|
-
|
|
861
|
+
if ('canvas' in request && request.canvas)
|
|
862
|
+
transfers.push(request.canvas);
|
|
863
|
+
try {
|
|
864
|
+
worker.postMessage({ ...request, _id: id }, transfers);
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
pendingCallbacks.delete(id);
|
|
868
|
+
clearTimeout(timeoutId);
|
|
869
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
870
|
+
}
|
|
437
871
|
});
|
|
438
872
|
}
|
|
873
|
+
function rejectWorkerCallbacks(worker, error) {
|
|
874
|
+
for (const [id, callback] of pendingCallbacks) {
|
|
875
|
+
if (callback.worker !== worker)
|
|
876
|
+
continue;
|
|
877
|
+
pendingCallbacks.delete(id);
|
|
878
|
+
callback.reject(error);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Test-only helper: tear down shared worker state between Bun test files.
|
|
883
|
+
* Not part of the public runtime API.
|
|
884
|
+
*/
|
|
885
|
+
export function resetWorkerForTests() {
|
|
886
|
+
if (sharedWorker) {
|
|
887
|
+
try {
|
|
888
|
+
sharedWorker.terminate();
|
|
889
|
+
}
|
|
890
|
+
catch {
|
|
891
|
+
/* ignore */
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
sharedWorker = null;
|
|
895
|
+
workerInitPromise = null;
|
|
896
|
+
for (const [, callback] of pendingCallbacks) {
|
|
897
|
+
callback.reject(new Error('Worker reset for tests'));
|
|
898
|
+
}
|
|
899
|
+
pendingCallbacks.clear();
|
|
900
|
+
}
|
|
439
901
|
//# sourceMappingURL=worker.js.map
|