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.
Files changed (42) hide show
  1. package/README.md +176 -8
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/ts/diagnostics.d.ts +35 -0
  7. package/dist/ts/diagnostics.d.ts.map +1 -0
  8. package/dist/ts/diagnostics.js +102 -0
  9. package/dist/ts/diagnostics.js.map +1 -0
  10. package/dist/ts/frame-export.d.ts +6 -0
  11. package/dist/ts/frame-export.d.ts.map +1 -0
  12. package/dist/ts/frame-export.js +235 -0
  13. package/dist/ts/frame-export.js.map +1 -0
  14. package/dist/ts/parsers.d.ts +44 -4
  15. package/dist/ts/parsers.d.ts.map +1 -1
  16. package/dist/ts/parsers.js +269 -56
  17. package/dist/ts/parsers.js.map +1 -1
  18. package/dist/ts/renderers.d.ts +19 -2
  19. package/dist/ts/renderers.d.ts.map +1 -1
  20. package/dist/ts/renderers.js +150 -27
  21. package/dist/ts/renderers.js.map +1 -1
  22. package/dist/ts/types.d.ts +110 -1
  23. package/dist/ts/types.d.ts.map +1 -1
  24. package/dist/ts/utils.d.ts +1 -1
  25. package/dist/ts/utils.d.ts.map +1 -1
  26. package/dist/ts/utils.js +8 -1
  27. package/dist/ts/utils.js.map +1 -1
  28. package/dist/ts/worker.d.ts.map +1 -1
  29. package/dist/ts/worker.js +6 -4
  30. package/dist/ts/worker.js.map +1 -1
  31. package/dist/wrapper.d.ts +4 -2
  32. package/dist/wrapper.d.ts.map +1 -1
  33. package/dist/wrapper.js +4 -1
  34. package/dist/wrapper.js.map +1 -1
  35. package/package.json +1 -1
  36. package/pkg/README.md +176 -8
  37. package/pkg/libbitsub.d.ts +15 -0
  38. package/pkg/libbitsub.js +48 -0
  39. package/pkg/libbitsub_bg.wasm +0 -0
  40. package/pkg/libbitsub_bg.wasm.d.ts +3 -0
  41. package/pkg/package.json +1 -1
  42. package/src/wrapper.ts +20 -1
@@ -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): void;
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":"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"}
1
+ {"version":3,"file":"renderers.d.ts","sourceRoot":"","sources":["../../src/ts/renderers.ts"],"names":[],"mappings":"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"}
@@ -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 instanceof Error ? error : new Error(String(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
- this.emitCueChange(currentIndex >= 0 ? this.buildCueMetadata(currentIndex) : null);
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 instanceof Error ? error : new Error(String(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 renderPromise = sendToWorker({
820
+ const renderTask = sendToWorker({
736
821
  type: 'renderPgsAtIndex',
737
822
  sessionId: this.state.sessionId,
738
823
  index
739
- }).then((response) => (response.type === 'pgsFrame' && response.frame ? convertFrameData(response.frame) : null));
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
- renderPromise.then((result) => {
743
- setCachedFrame(this.state, index, result);
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 new Error('No SUB content or URL provided');
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 new Error('No IDX content or URL provided');
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 new Error('Failed to load VobSub data');
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 instanceof Error ? error : new Error(String(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 renderPromise = sendToWorker({
1177
+ const renderTask = sendToWorker({
1067
1178
  type: 'renderVobSubAtIndex',
1068
1179
  sessionId: this.state.sessionId,
1069
1180
  index
1070
- }).then((response) => (response.type === 'vobSubFrame' && response.frame ? convertFrameData(response.frame) : null));
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
- renderPromise.then((result) => {
1074
- setCachedFrame(this.state, index, result);
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(() => { });