libbitsub 1.9.0 → 1.10.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 +176 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/ts/diagnostics.d.ts +35 -0
- package/dist/ts/diagnostics.d.ts.map +1 -0
- package/dist/ts/diagnostics.js +102 -0
- package/dist/ts/diagnostics.js.map +1 -0
- package/dist/ts/frame-export.d.ts +6 -0
- package/dist/ts/frame-export.d.ts.map +1 -0
- package/dist/ts/frame-export.js +235 -0
- package/dist/ts/frame-export.js.map +1 -0
- package/dist/ts/parsers.d.ts +44 -4
- package/dist/ts/parsers.d.ts.map +1 -1
- package/dist/ts/parsers.js +269 -56
- package/dist/ts/parsers.js.map +1 -1
- package/dist/ts/renderers.d.ts +19 -2
- package/dist/ts/renderers.d.ts.map +1 -1
- package/dist/ts/renderers.js +150 -27
- package/dist/ts/renderers.js.map +1 -1
- package/dist/ts/types.d.ts +110 -1
- package/dist/ts/types.d.ts.map +1 -1
- package/dist/ts/utils.d.ts +1 -1
- package/dist/ts/utils.d.ts.map +1 -1
- package/dist/ts/utils.js +8 -1
- package/dist/ts/utils.js.map +1 -1
- package/dist/ts/worker.d.ts.map +1 -1
- package/dist/ts/worker.js +6 -4
- package/dist/ts/worker.js.map +1 -1
- package/dist/wrapper.d.ts +4 -2
- package/dist/wrapper.d.ts.map +1 -1
- package/dist/wrapper.js +4 -1
- package/dist/wrapper.js.map +1 -1
- package/package.json +1 -1
- package/pkg/README.md +176 -8
- package/pkg/libbitsub.d.ts +15 -0
- package/pkg/libbitsub.js +48 -0
- package/pkg/libbitsub_bg.wasm +0 -0
- package/pkg/libbitsub_bg.wasm.d.ts +3 -0
- package/pkg/package.json +1 -1
- package/src/wrapper.ts +20 -1
package/dist/ts/renderers.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* High-level video-integrated subtitle renderers for libbitsub.
|
|
3
3
|
* Handles canvas overlay, video sync, and subtitle fetching.
|
|
4
4
|
*/
|
|
5
|
-
import type { AutoVideoSubtitleOptions, SubtitleCueMetadata, SubtitleData, SubtitleDisplaySettings, SubtitleParserMetadata, SubtitleRendererBackend, SubtitleRendererEvent, VideoSubtitleOptions, VideoVobSubOptions } from './types';
|
|
5
|
+
import type { AutoVideoSubtitleOptions, SubtitleCacheStats, SubtitleCueMetadata, SubtitleData, SubtitleDiagnosticWarning, SubtitleDisplaySettings, SubtitleLastRenderInfo, SubtitleParserMetadata, SubtitleRendererBackend, SubtitleRendererEvent, VideoSubtitleOptions, VideoVobSubOptions, WorkerRendererState } from './types';
|
|
6
6
|
import { WebGPURenderer } from './webgpu-renderer';
|
|
7
7
|
import { WebGL2Renderer } from './webgl2-renderer';
|
|
8
8
|
interface SubtitleRenderLayout {
|
|
@@ -12,6 +12,11 @@ interface SubtitleRenderLayout {
|
|
|
12
12
|
shiftY: number;
|
|
13
13
|
opacity: number;
|
|
14
14
|
}
|
|
15
|
+
interface RenderFrameOutcome {
|
|
16
|
+
status: 'rendered' | 'cleared' | 'pending' | 'empty' | 'failed';
|
|
17
|
+
data: SubtitleData | null;
|
|
18
|
+
warning: SubtitleDiagnosticWarning | null;
|
|
19
|
+
}
|
|
15
20
|
/** Performance statistics for subtitle renderer */
|
|
16
21
|
export interface SubtitleRendererStats {
|
|
17
22
|
/** Total frames rendered since initialization */
|
|
@@ -70,7 +75,10 @@ declare abstract class BaseVideoSubtitleRenderer {
|
|
|
70
75
|
protected prefetchBefore: number;
|
|
71
76
|
protected prefetchAfter: number;
|
|
72
77
|
protected onEvent?: (event: SubtitleRendererEvent) => void;
|
|
78
|
+
protected onWarning?: (warning: SubtitleDiagnosticWarning) => void;
|
|
73
79
|
protected currentRendererBackend: SubtitleRendererBackend | null;
|
|
80
|
+
protected readonly debug: boolean;
|
|
81
|
+
protected lastRenderInfo: SubtitleLastRenderInfo | null;
|
|
74
82
|
private loadedMetadataHandler;
|
|
75
83
|
private seekedHandler;
|
|
76
84
|
protected webgpuRenderer: WebGPURenderer | null;
|
|
@@ -92,6 +100,10 @@ declare abstract class BaseVideoSubtitleRenderer {
|
|
|
92
100
|
getDisplaySettings(): SubtitleDisplaySettings;
|
|
93
101
|
/** Get performance statistics */
|
|
94
102
|
abstract getStats(): SubtitleRendererStats;
|
|
103
|
+
/** Get cache-related diagnostics for the active renderer session. */
|
|
104
|
+
getCacheStats(): SubtitleCacheStats;
|
|
105
|
+
/** Get the most recent render attempt when debug mode is enabled. */
|
|
106
|
+
getLastRenderInfo(): SubtitleLastRenderInfo | null;
|
|
95
107
|
/** Get parser metadata for the active subtitle track. */
|
|
96
108
|
getMetadata(): SubtitleParserMetadata | null;
|
|
97
109
|
/** Get the most recently displayed cue metadata. */
|
|
@@ -113,11 +125,13 @@ declare abstract class BaseVideoSubtitleRenderer {
|
|
|
113
125
|
/** Create the canvas overlay positioned over the video. */
|
|
114
126
|
protected createCanvas(): void;
|
|
115
127
|
protected emitEvent(event: SubtitleRendererEvent): void;
|
|
128
|
+
protected emitWarning(warning: SubtitleDiagnosticWarning): void;
|
|
116
129
|
protected setParserMetadata(metadata: SubtitleParserMetadata | null): void;
|
|
117
130
|
protected emitWorkerState(enabled: boolean, ready: boolean, sessionId: string | null, fallback?: boolean): void;
|
|
118
131
|
protected emitCacheChange(cachedFrames: number, pendingRenders: number): void;
|
|
119
132
|
protected emitCueChange(cue: SubtitleCueMetadata | null): void;
|
|
120
133
|
protected emitRendererBackend(renderer: SubtitleRendererBackend): void;
|
|
134
|
+
protected recordLastRenderInfo(info: SubtitleLastRenderInfo): void;
|
|
121
135
|
private initWebGPU;
|
|
122
136
|
private initWebGL2;
|
|
123
137
|
/** Initialize Canvas2D renderer. */
|
|
@@ -138,12 +152,13 @@ declare abstract class BaseVideoSubtitleRenderer {
|
|
|
138
152
|
protected abstract findCurrentIndex(time: number): number;
|
|
139
153
|
protected abstract renderAtIndex(index: number): SubtitleData | undefined;
|
|
140
154
|
protected abstract buildCueMetadata(index: number): SubtitleCueMetadata | null;
|
|
155
|
+
protected abstract getWorkerRendererState(): WorkerRendererState;
|
|
141
156
|
/** Check if a render is pending for the given index (async loading in progress) */
|
|
142
157
|
protected abstract isPendingRender(index: number): boolean;
|
|
143
158
|
/** Start the render loop. */
|
|
144
159
|
protected startRenderLoop(): void;
|
|
145
160
|
/** Render a subtitle frame to the canvas. */
|
|
146
|
-
protected renderFrame(time: number, index: number):
|
|
161
|
+
protected renderFrame(time: number, index: number): RenderFrameOutcome;
|
|
147
162
|
protected computeLayout(data: SubtitleData): SubtitleRenderLayout;
|
|
148
163
|
/** Render using WebGPU. */
|
|
149
164
|
private renderFrameWebGPU;
|
|
@@ -167,6 +182,7 @@ export declare class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
167
182
|
constructor(options: VideoSubtitleOptions);
|
|
168
183
|
protected loadSubtitles(): Promise<void>;
|
|
169
184
|
private loadOnMainThread;
|
|
185
|
+
protected getWorkerRendererState(): WorkerRendererState;
|
|
170
186
|
/** Yield to main thread to prevent UI blocking */
|
|
171
187
|
private yieldToMain;
|
|
172
188
|
protected renderAtTime(time: number): SubtitleData | undefined;
|
|
@@ -202,6 +218,7 @@ export declare class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
202
218
|
constructor(options: VideoVobSubOptions);
|
|
203
219
|
protected loadSubtitles(): Promise<void>;
|
|
204
220
|
private loadOnMainThread;
|
|
221
|
+
protected getWorkerRendererState(): WorkerRendererState;
|
|
205
222
|
/** Yield to main thread to prevent UI blocking */
|
|
206
223
|
private yieldToMain;
|
|
207
224
|
protected renderAtTime(time: number): SubtitleData | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderers.d.ts","sourceRoot":"","sources":["../../src/ts/renderers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,YAAY,EACZ,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"renderers.d.ts","sourceRoot":"","sources":["../../src/ts/renderers.ts"],"names":[],"mappings":"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"}
|
package/dist/ts/renderers.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* High-level video-integrated subtitle renderers for libbitsub.
|
|
3
3
|
* Handles canvas overlay, video sync, and subtitle fetching.
|
|
4
4
|
*/
|
|
5
|
+
import { createSubtitleDiagnosticError, createSubtitleWarning, formatSubtitleWarningForConsole, normalizeSubtitleError, warningFromRenderIssue } from './diagnostics';
|
|
5
6
|
import { initWasm } from './wasm';
|
|
6
7
|
import { getOrCreateWorker, sendToWorker } from './worker';
|
|
7
8
|
import { binarySearchTimestamp, convertFrameData, createWorkerSessionId, createWorkerState, detectSubtitleFormat, getSubtitleBounds, isMksSource, setCacheLimit as applyCacheLimit, setCachedFrame } from './utils';
|
|
@@ -59,7 +60,10 @@ class BaseVideoSubtitleRenderer {
|
|
|
59
60
|
prefetchBefore = 0;
|
|
60
61
|
prefetchAfter = 0;
|
|
61
62
|
onEvent;
|
|
63
|
+
onWarning;
|
|
62
64
|
currentRendererBackend = null;
|
|
65
|
+
debug;
|
|
66
|
+
lastRenderInfo = null;
|
|
63
67
|
loadedMetadataHandler = null;
|
|
64
68
|
seekedHandler = null;
|
|
65
69
|
// WebGPU renderer (optional, falls back to WebGL2 then Canvas2D)
|
|
@@ -87,6 +91,8 @@ class BaseVideoSubtitleRenderer {
|
|
|
87
91
|
this.onWebGPUFallback = options.onWebGPUFallback;
|
|
88
92
|
this.onWebGL2Fallback = options.onWebGL2Fallback;
|
|
89
93
|
this.onEvent = options.onEvent;
|
|
94
|
+
this.onWarning = options.onWarning;
|
|
95
|
+
this.debug = Boolean(options.debug);
|
|
90
96
|
this.displaySettings = { ...DEFAULT_DISPLAY_SETTINGS, ...options.displaySettings };
|
|
91
97
|
this.timeOffset = options.timeOffset ?? 0;
|
|
92
98
|
this.cacheLimit = Math.max(0, Math.floor(options.cacheLimit ?? 24));
|
|
@@ -97,6 +103,29 @@ class BaseVideoSubtitleRenderer {
|
|
|
97
103
|
getDisplaySettings() {
|
|
98
104
|
return { ...this.displaySettings };
|
|
99
105
|
}
|
|
106
|
+
/** Get cache-related diagnostics for the active renderer session. */
|
|
107
|
+
getCacheStats() {
|
|
108
|
+
const state = this.getWorkerRendererState();
|
|
109
|
+
return {
|
|
110
|
+
cacheLimit: this.cacheLimit,
|
|
111
|
+
cachedFrames: state.frameCache.size,
|
|
112
|
+
pendingRenders: state.pendingRenders.size,
|
|
113
|
+
totalEntries: state.timestamps.length,
|
|
114
|
+
usingWorker: state.useWorker && state.workerReady,
|
|
115
|
+
workerReady: state.workerReady,
|
|
116
|
+
sessionId: state.sessionId
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** Get the most recent render attempt when debug mode is enabled. */
|
|
120
|
+
getLastRenderInfo() {
|
|
121
|
+
if (!this.lastRenderInfo)
|
|
122
|
+
return null;
|
|
123
|
+
return {
|
|
124
|
+
...this.lastRenderInfo,
|
|
125
|
+
cache: { ...this.lastRenderInfo.cache },
|
|
126
|
+
cue: this.lastRenderInfo.cue ? { ...this.lastRenderInfo.cue } : null
|
|
127
|
+
};
|
|
128
|
+
}
|
|
100
129
|
/** Get parser metadata for the active subtitle track. */
|
|
101
130
|
getMetadata() {
|
|
102
131
|
return this.parserMetadata;
|
|
@@ -165,7 +194,7 @@ class BaseVideoSubtitleRenderer {
|
|
|
165
194
|
/** Start initialization. */
|
|
166
195
|
startInit() {
|
|
167
196
|
this.init().catch((error) => {
|
|
168
|
-
this.emitEvent({ type: 'error', format: this.format, error: error
|
|
197
|
+
this.emitEvent({ type: 'error', format: this.format, error: normalizeSubtitleError(error, { format: this.format }) });
|
|
169
198
|
});
|
|
170
199
|
}
|
|
171
200
|
/** Initialize the renderer. */
|
|
@@ -216,6 +245,13 @@ class BaseVideoSubtitleRenderer {
|
|
|
216
245
|
emitEvent(event) {
|
|
217
246
|
this.onEvent?.(event);
|
|
218
247
|
}
|
|
248
|
+
emitWarning(warning) {
|
|
249
|
+
this.onWarning?.(warning);
|
|
250
|
+
this.emitEvent({ type: 'warning', warning });
|
|
251
|
+
if (this.debug && !this.onWarning) {
|
|
252
|
+
console.warn(formatSubtitleWarningForConsole(warning), warning.details ?? {});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
219
255
|
setParserMetadata(metadata) {
|
|
220
256
|
this.parserMetadata = metadata;
|
|
221
257
|
if (metadata) {
|
|
@@ -243,6 +279,11 @@ class BaseVideoSubtitleRenderer {
|
|
|
243
279
|
this.currentRendererBackend = renderer;
|
|
244
280
|
this.emitEvent({ type: 'renderer-change', renderer });
|
|
245
281
|
}
|
|
282
|
+
recordLastRenderInfo(info) {
|
|
283
|
+
if (!this.debug)
|
|
284
|
+
return;
|
|
285
|
+
this.lastRenderInfo = info;
|
|
286
|
+
}
|
|
246
287
|
/** Initialize WebGPU renderer. */
|
|
247
288
|
async initWebGPU() {
|
|
248
289
|
try {
|
|
@@ -378,8 +419,9 @@ class BaseVideoSubtitleRenderer {
|
|
|
378
419
|
const currentIndex = this.findCurrentIndex(currentTime);
|
|
379
420
|
// Only re-render if index changed
|
|
380
421
|
if (currentIndex !== this.lastRenderedIndex) {
|
|
422
|
+
const cacheHit = currentIndex >= 0 && this.getWorkerRendererState().frameCache.has(currentIndex);
|
|
381
423
|
const startTime = performance.now();
|
|
382
|
-
this.renderFrame(currentTime, currentIndex);
|
|
424
|
+
const outcome = this.renderFrame(currentTime, currentIndex);
|
|
383
425
|
const endTime = performance.now();
|
|
384
426
|
// Track performance
|
|
385
427
|
const renderTime = endTime - startTime;
|
|
@@ -396,9 +438,28 @@ class BaseVideoSubtitleRenderer {
|
|
|
396
438
|
if (renderTime > frameBudget) {
|
|
397
439
|
this.perfStats.framesDropped++;
|
|
398
440
|
}
|
|
441
|
+
if (outcome.warning) {
|
|
442
|
+
this.emitWarning(outcome.warning);
|
|
443
|
+
}
|
|
399
444
|
this.lastRenderedIndex = currentIndex;
|
|
400
445
|
this.lastRenderedTime = currentTime;
|
|
401
|
-
|
|
446
|
+
const cue = currentIndex >= 0 ? this.buildCueMetadata(currentIndex) : null;
|
|
447
|
+
this.emitCueChange(cue);
|
|
448
|
+
this.recordLastRenderInfo({
|
|
449
|
+
time: currentTime,
|
|
450
|
+
index: currentIndex,
|
|
451
|
+
status: outcome.status,
|
|
452
|
+
backend: this.currentRendererBackend,
|
|
453
|
+
usingWorker: this.getCacheStats().usingWorker,
|
|
454
|
+
cacheHit,
|
|
455
|
+
renderDuration: Math.round(renderTime * 100) / 100,
|
|
456
|
+
frameWidth: outcome.data?.width ?? null,
|
|
457
|
+
frameHeight: outcome.data?.height ?? null,
|
|
458
|
+
compositionCount: outcome.data?.compositionData.length ?? 0,
|
|
459
|
+
cue,
|
|
460
|
+
cache: this.getCacheStats(),
|
|
461
|
+
capturedAt: endTime
|
|
462
|
+
});
|
|
402
463
|
this.emitEvent({ type: 'stats', stats: this.getStats() });
|
|
403
464
|
if (currentIndex >= 0 && (this.prefetchBefore > 0 || this.prefetchAfter > 0)) {
|
|
404
465
|
const prefetch = this.prefetchAroundTime;
|
|
@@ -412,8 +473,9 @@ class BaseVideoSubtitleRenderer {
|
|
|
412
473
|
}
|
|
413
474
|
/** Render a subtitle frame to the canvas. */
|
|
414
475
|
renderFrame(time, index) {
|
|
415
|
-
if (!this.canvas)
|
|
416
|
-
return;
|
|
476
|
+
if (!this.canvas) {
|
|
477
|
+
return { status: 'failed', data: null, warning: null };
|
|
478
|
+
}
|
|
417
479
|
// Get the data for this index
|
|
418
480
|
const data = index >= 0 ? this.renderAtIndex(index) : undefined;
|
|
419
481
|
// If data is undefined, it means async loading is in progress
|
|
@@ -424,9 +486,11 @@ class BaseVideoSubtitleRenderer {
|
|
|
424
486
|
// If not pending, it means the render returned no data immediately
|
|
425
487
|
if (this.isPendingRender(index)) {
|
|
426
488
|
// Don't clear - keep showing the last frame while loading
|
|
427
|
-
return;
|
|
489
|
+
return { status: 'pending', data: null, warning: null };
|
|
428
490
|
}
|
|
429
491
|
}
|
|
492
|
+
const renderIssue = index >= 0 ? this.getWorkerRendererState().renderIssues.get(index) ?? null : null;
|
|
493
|
+
const warning = warningFromRenderIssue(renderIssue, { format: this.format, cueIndex: index });
|
|
430
494
|
// Use best available renderer
|
|
431
495
|
if (this.useWebGPU && this.webgpuRenderer) {
|
|
432
496
|
this.renderFrameWebGPU(data, index);
|
|
@@ -437,6 +501,16 @@ class BaseVideoSubtitleRenderer {
|
|
|
437
501
|
else {
|
|
438
502
|
this.renderFrameCanvas2D(data, index);
|
|
439
503
|
}
|
|
504
|
+
if (index < 0) {
|
|
505
|
+
return { status: 'cleared', data: null, warning: null };
|
|
506
|
+
}
|
|
507
|
+
if (warning) {
|
|
508
|
+
return { status: 'failed', data: data ?? null, warning };
|
|
509
|
+
}
|
|
510
|
+
if (!data || data.compositionData.length === 0) {
|
|
511
|
+
return { status: 'empty', data: data ?? null, warning: null };
|
|
512
|
+
}
|
|
513
|
+
return { status: 'rendered', data, warning: null };
|
|
440
514
|
}
|
|
441
515
|
computeLayout(data) {
|
|
442
516
|
if (!this.canvas) {
|
|
@@ -600,6 +674,7 @@ class BaseVideoSubtitleRenderer {
|
|
|
600
674
|
this.lastRenderedData = null;
|
|
601
675
|
this.currentCueMetadata = null;
|
|
602
676
|
this.parserMetadata = null;
|
|
677
|
+
this.lastRenderInfo = null;
|
|
603
678
|
this.useWebGPU = false;
|
|
604
679
|
this.useWebGL2 = false;
|
|
605
680
|
}
|
|
@@ -668,6 +743,10 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
668
743
|
catch (workerError) {
|
|
669
744
|
this.state.useWorker = false;
|
|
670
745
|
this.emitWorkerState(false, false, this.state.sessionId, true);
|
|
746
|
+
this.emitWarning(createSubtitleWarning('WORKER_FALLBACK', 'PGS worker initialization failed, falling back to main-thread rendering.', {
|
|
747
|
+
format: 'pgs',
|
|
748
|
+
details: { reason: workerError instanceof Error ? workerError.message : String(workerError) }
|
|
749
|
+
}));
|
|
671
750
|
if (!this.subContent && this.subUrl && data.byteLength === 0) {
|
|
672
751
|
const response = await fetch(this.subUrl);
|
|
673
752
|
if (!response.ok)
|
|
@@ -681,7 +760,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
681
760
|
this.onLoaded?.();
|
|
682
761
|
}
|
|
683
762
|
catch (error) {
|
|
684
|
-
const resolvedError = error
|
|
763
|
+
const resolvedError = normalizeSubtitleError(error, { format: 'pgs' });
|
|
685
764
|
this.emitEvent({ type: 'error', format: 'pgs', error: resolvedError });
|
|
686
765
|
this.onError?.(resolvedError);
|
|
687
766
|
}
|
|
@@ -689,7 +768,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
689
768
|
async loadOnMainThread(data) {
|
|
690
769
|
// Yield to browser before heavy parsing
|
|
691
770
|
await this.yieldToMain();
|
|
692
|
-
this.pgsParser = new PgsParser();
|
|
771
|
+
this.pgsParser = new PgsParser({ debug: this.debug, onWarning: (warning) => this.emitWarning(warning) });
|
|
693
772
|
// Parse in a microtask to allow UI to update
|
|
694
773
|
await new Promise((resolve) => {
|
|
695
774
|
// Use requestIdleCallback if available, otherwise setTimeout
|
|
@@ -702,10 +781,16 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
702
781
|
this.state.metadata = this.pgsParser.getMetadata();
|
|
703
782
|
this.isLoaded = true;
|
|
704
783
|
this.setParserMetadata(this.state.metadata);
|
|
784
|
+
if (count === 0) {
|
|
785
|
+
this.state.renderIssues.set(-1, 'INVALID_SUBTITLE_DATA');
|
|
786
|
+
}
|
|
705
787
|
resolve();
|
|
706
788
|
});
|
|
707
789
|
});
|
|
708
790
|
}
|
|
791
|
+
getWorkerRendererState() {
|
|
792
|
+
return this.state;
|
|
793
|
+
}
|
|
709
794
|
/** Yield to main thread to prevent UI blocking */
|
|
710
795
|
yieldToMain() {
|
|
711
796
|
// Use scheduler.yield if available (Chrome 115+)
|
|
@@ -732,19 +817,28 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
732
817
|
}
|
|
733
818
|
if (this.state.useWorker && this.state.workerReady) {
|
|
734
819
|
if (!this.state.pendingRenders.has(index)) {
|
|
735
|
-
const
|
|
820
|
+
const renderTask = sendToWorker({
|
|
736
821
|
type: 'renderPgsAtIndex',
|
|
737
822
|
sessionId: this.state.sessionId,
|
|
738
823
|
index
|
|
739
|
-
}).then((response) =>
|
|
824
|
+
}).then((response) => {
|
|
825
|
+
if (response.type === 'pgsFrame') {
|
|
826
|
+
return {
|
|
827
|
+
frame: response.frame ? convertFrameData(response.frame) : null,
|
|
828
|
+
renderIssue: response.renderIssue?.trim() || null
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
return { frame: null, renderIssue: null };
|
|
832
|
+
});
|
|
833
|
+
const renderPromise = renderTask.then(({ frame }) => frame);
|
|
740
834
|
this.state.pendingRenders.set(index, renderPromise);
|
|
741
835
|
this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
|
|
742
|
-
|
|
743
|
-
setCachedFrame(this.state, index,
|
|
836
|
+
renderTask.then(({ frame, renderIssue }) => {
|
|
837
|
+
setCachedFrame(this.state, index, frame, renderIssue);
|
|
744
838
|
this.state.pendingRenders.delete(index);
|
|
745
839
|
this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
|
|
746
840
|
// Force re-render on next frame by resetting lastRenderedIndex
|
|
747
|
-
if (this.findCurrentIndex(this.video.currentTime) === index) {
|
|
841
|
+
if (this.findCurrentIndex(this.video.currentTime + this.timeOffset) === index) {
|
|
748
842
|
this.lastRenderedIndex = -1;
|
|
749
843
|
}
|
|
750
844
|
});
|
|
@@ -753,7 +847,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
753
847
|
return undefined;
|
|
754
848
|
}
|
|
755
849
|
const rendered = this.pgsParser?.renderAtIndex(index) ?? null;
|
|
756
|
-
setCachedFrame(this.state, index, rendered);
|
|
850
|
+
setCachedFrame(this.state, index, rendered, this.pgsParser?.getLastRenderIssue() ?? null);
|
|
757
851
|
this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
|
|
758
852
|
return rendered ?? undefined;
|
|
759
853
|
}
|
|
@@ -784,6 +878,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
784
878
|
}
|
|
785
879
|
onSeek() {
|
|
786
880
|
this.state.frameCache.clear();
|
|
881
|
+
this.state.renderIssues.clear();
|
|
787
882
|
this.state.pendingRenders.clear();
|
|
788
883
|
this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
|
|
789
884
|
if (this.state.useWorker && this.state.workerReady) {
|
|
@@ -797,6 +892,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
797
892
|
}
|
|
798
893
|
clearFrameCache() {
|
|
799
894
|
this.state.frameCache.clear();
|
|
895
|
+
this.state.renderIssues.clear();
|
|
800
896
|
this.state.pendingRenders.clear();
|
|
801
897
|
this.lastRenderedIndex = -1;
|
|
802
898
|
if (this.state.useWorker && this.state.workerReady) {
|
|
@@ -837,6 +933,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
|
|
|
837
933
|
dispose() {
|
|
838
934
|
super.dispose();
|
|
839
935
|
this.state.frameCache.clear();
|
|
936
|
+
this.state.renderIssues.clear();
|
|
840
937
|
this.state.pendingRenders.clear();
|
|
841
938
|
if (this.state.useWorker && this.state.workerReady) {
|
|
842
939
|
sendToWorker({ type: 'disposePgs', sessionId: this.state.sessionId }).catch(() => { });
|
|
@@ -898,8 +995,9 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
898
995
|
// Fetch missing parts
|
|
899
996
|
const promises = [];
|
|
900
997
|
if (!subArrayBuffer) {
|
|
901
|
-
if (!this.subUrl)
|
|
902
|
-
throw
|
|
998
|
+
if (!this.subUrl) {
|
|
999
|
+
throw createSubtitleDiagnosticError('MISSING_INPUT', 'No SUB content or URL provided.', { format: 'vobsub' });
|
|
1000
|
+
}
|
|
903
1001
|
promises.push(fetch(this.subUrl)
|
|
904
1002
|
.then((r) => {
|
|
905
1003
|
if (!r.ok)
|
|
@@ -911,8 +1009,9 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
911
1009
|
}));
|
|
912
1010
|
}
|
|
913
1011
|
if (!useMksSource && !idxData) {
|
|
914
|
-
if (!this.idxUrl)
|
|
915
|
-
throw
|
|
1012
|
+
if (!this.idxUrl) {
|
|
1013
|
+
throw createSubtitleDiagnosticError('MISSING_INPUT', 'No IDX content or URL provided.', { format: 'vobsub' });
|
|
1014
|
+
}
|
|
916
1015
|
promises.push(fetch(this.idxUrl)
|
|
917
1016
|
.then((r) => {
|
|
918
1017
|
if (!r.ok)
|
|
@@ -927,7 +1026,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
927
1026
|
await Promise.all(promises);
|
|
928
1027
|
}
|
|
929
1028
|
if (!subArrayBuffer || (!useMksSource && !idxData)) {
|
|
930
|
-
throw
|
|
1029
|
+
throw createSubtitleDiagnosticError('MISSING_INPUT', 'Failed to load VobSub data.', { format: 'vobsub' });
|
|
931
1030
|
}
|
|
932
1031
|
let subData = new Uint8Array(subArrayBuffer);
|
|
933
1032
|
if (this.state.useWorker) {
|
|
@@ -949,6 +1048,11 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
949
1048
|
subData: transferableSubData
|
|
950
1049
|
});
|
|
951
1050
|
if (loadResponse.type === 'vobSubLoaded') {
|
|
1051
|
+
if (!useMksSource && idxData && loadResponse.count === 0) {
|
|
1052
|
+
throw createSubtitleDiagnosticError('BAD_IDX', 'IDX metadata did not yield any subtitle timestamps.', {
|
|
1053
|
+
format: 'vobsub'
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
952
1056
|
this.state.workerReady = true;
|
|
953
1057
|
this.state.metadata = loadResponse.metadata;
|
|
954
1058
|
this.state.timestamps = loadResponse.timestamps;
|
|
@@ -965,6 +1069,10 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
965
1069
|
catch (workerError) {
|
|
966
1070
|
this.state.useWorker = false;
|
|
967
1071
|
this.emitWorkerState(false, false, this.state.sessionId, true);
|
|
1072
|
+
this.emitWarning(createSubtitleWarning('WORKER_FALLBACK', 'VobSub worker initialization failed, falling back to main-thread rendering.', {
|
|
1073
|
+
format: 'vobsub',
|
|
1074
|
+
details: { reason: workerError instanceof Error ? workerError.message : String(workerError) }
|
|
1075
|
+
}));
|
|
968
1076
|
if (!this.subContent && this.subUrl && subData.byteLength === 0) {
|
|
969
1077
|
const response = await fetch(this.subUrl);
|
|
970
1078
|
if (!response.ok)
|
|
@@ -978,7 +1086,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
978
1086
|
this.onLoaded?.();
|
|
979
1087
|
}
|
|
980
1088
|
catch (error) {
|
|
981
|
-
const resolvedError = error
|
|
1089
|
+
const resolvedError = normalizeSubtitleError(error, { format: 'vobsub' });
|
|
982
1090
|
this.emitEvent({ type: 'error', format: 'vobsub', error: resolvedError });
|
|
983
1091
|
this.onError?.(resolvedError);
|
|
984
1092
|
}
|
|
@@ -986,7 +1094,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
986
1094
|
async loadOnMainThread(subData, idxData, useMksSource = false) {
|
|
987
1095
|
// Yield to browser before heavy parsing
|
|
988
1096
|
await this.yieldToMain();
|
|
989
|
-
this.vobsubParser = new VobSubParserLowLevel();
|
|
1097
|
+
this.vobsubParser = new VobSubParserLowLevel({ debug: this.debug, onWarning: (warning) => this.emitWarning(warning) });
|
|
990
1098
|
// Parse in a microtask to allow UI to update
|
|
991
1099
|
await new Promise((resolve) => {
|
|
992
1100
|
const scheduleTask = typeof requestIdleCallback !== 'undefined'
|
|
@@ -1010,6 +1118,9 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
1010
1118
|
});
|
|
1011
1119
|
});
|
|
1012
1120
|
}
|
|
1121
|
+
getWorkerRendererState() {
|
|
1122
|
+
return this.state;
|
|
1123
|
+
}
|
|
1013
1124
|
/** Yield to main thread to prevent UI blocking */
|
|
1014
1125
|
yieldToMain() {
|
|
1015
1126
|
const globalScheduler = globalThis.scheduler;
|
|
@@ -1063,19 +1174,28 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
1063
1174
|
if (this.state.useWorker && this.state.workerReady) {
|
|
1064
1175
|
// Start async render if not already pending
|
|
1065
1176
|
if (!this.state.pendingRenders.has(index)) {
|
|
1066
|
-
const
|
|
1177
|
+
const renderTask = sendToWorker({
|
|
1067
1178
|
type: 'renderVobSubAtIndex',
|
|
1068
1179
|
sessionId: this.state.sessionId,
|
|
1069
1180
|
index
|
|
1070
|
-
}).then((response) =>
|
|
1181
|
+
}).then((response) => {
|
|
1182
|
+
if (response.type === 'vobSubFrame') {
|
|
1183
|
+
return {
|
|
1184
|
+
frame: response.frame ? convertFrameData(response.frame) : null,
|
|
1185
|
+
renderIssue: response.renderIssue?.trim() || null
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
return { frame: null, renderIssue: null };
|
|
1189
|
+
});
|
|
1190
|
+
const renderPromise = renderTask.then(({ frame }) => frame);
|
|
1071
1191
|
this.state.pendingRenders.set(index, renderPromise);
|
|
1072
1192
|
this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
|
|
1073
|
-
|
|
1074
|
-
setCachedFrame(this.state, index,
|
|
1193
|
+
renderTask.then(({ frame, renderIssue }) => {
|
|
1194
|
+
setCachedFrame(this.state, index, frame, renderIssue);
|
|
1075
1195
|
this.state.pendingRenders.delete(index);
|
|
1076
1196
|
this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
|
|
1077
1197
|
// Force re-render on next frame by resetting lastRenderedIndex
|
|
1078
|
-
if (this.findCurrentIndex(this.video.currentTime) === index) {
|
|
1198
|
+
if (this.findCurrentIndex(this.video.currentTime + this.timeOffset) === index) {
|
|
1079
1199
|
this.lastRenderedIndex = -1;
|
|
1080
1200
|
}
|
|
1081
1201
|
});
|
|
@@ -1084,7 +1204,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
1084
1204
|
return undefined;
|
|
1085
1205
|
}
|
|
1086
1206
|
const rendered = this.vobsubParser?.renderAtIndex(index) ?? null;
|
|
1087
|
-
setCachedFrame(this.state, index, rendered);
|
|
1207
|
+
setCachedFrame(this.state, index, rendered, this.vobsubParser?.getLastRenderIssue() ?? null);
|
|
1088
1208
|
this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
|
|
1089
1209
|
return rendered ?? undefined;
|
|
1090
1210
|
}
|
|
@@ -1117,6 +1237,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
1117
1237
|
}
|
|
1118
1238
|
onSeek() {
|
|
1119
1239
|
this.state.frameCache.clear();
|
|
1240
|
+
this.state.renderIssues.clear();
|
|
1120
1241
|
this.state.pendingRenders.clear();
|
|
1121
1242
|
// Clear cached index lookup on seek
|
|
1122
1243
|
this.cachedIndex = -1;
|
|
@@ -1134,6 +1255,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
1134
1255
|
}
|
|
1135
1256
|
clearFrameCache() {
|
|
1136
1257
|
this.state.frameCache.clear();
|
|
1258
|
+
this.state.renderIssues.clear();
|
|
1137
1259
|
this.state.pendingRenders.clear();
|
|
1138
1260
|
this.cachedIndex = -1;
|
|
1139
1261
|
this.cachedIndexTime = -1;
|
|
@@ -1214,6 +1336,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
|
|
|
1214
1336
|
dispose() {
|
|
1215
1337
|
super.dispose();
|
|
1216
1338
|
this.state.frameCache.clear();
|
|
1339
|
+
this.state.renderIssues.clear();
|
|
1217
1340
|
this.state.pendingRenders.clear();
|
|
1218
1341
|
if (this.state.useWorker && this.state.workerReady) {
|
|
1219
1342
|
sendToWorker({ type: 'disposeVobSub', sessionId: this.state.sessionId }).catch(() => { });
|