libbitsub 1.8.2 → 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 +187 -9
  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 +272 -67
  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 +180 -40
  21. package/dist/ts/renderers.js.map +1 -1
  22. package/dist/ts/types.d.ts +112 -1
  23. package/dist/ts/types.d.ts.map +1 -1
  24. package/dist/ts/utils.d.ts +2 -2
  25. package/dist/ts/utils.d.ts.map +1 -1
  26. package/dist/ts/utils.js +26 -11
  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 +32 -14
  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 +187 -9
  37. package/pkg/libbitsub.d.ts +15 -0
  38. package/pkg/libbitsub.js +48 -7
  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';
@@ -19,6 +20,15 @@ const DEFAULT_DISPLAY_SETTINGS = {
19
20
  safeArea: 0,
20
21
  opacity: 1.0
21
22
  };
23
+ function createTransferableBuffer(data, preserveSource) {
24
+ if (!preserveSource &&
25
+ data.buffer instanceof ArrayBuffer &&
26
+ data.byteOffset === 0 &&
27
+ data.byteLength === data.buffer.byteLength) {
28
+ return data.buffer;
29
+ }
30
+ return data.slice().buffer;
31
+ }
22
32
  /**
23
33
  * Base class for video-integrated subtitle renderers.
24
34
  * Handles canvas overlay, video sync, and subtitle fetching.
@@ -50,7 +60,10 @@ class BaseVideoSubtitleRenderer {
50
60
  prefetchBefore = 0;
51
61
  prefetchAfter = 0;
52
62
  onEvent;
63
+ onWarning;
53
64
  currentRendererBackend = null;
65
+ debug;
66
+ lastRenderInfo = null;
54
67
  loadedMetadataHandler = null;
55
68
  seekedHandler = null;
56
69
  // WebGPU renderer (optional, falls back to WebGL2 then Canvas2D)
@@ -78,6 +91,8 @@ class BaseVideoSubtitleRenderer {
78
91
  this.onWebGPUFallback = options.onWebGPUFallback;
79
92
  this.onWebGL2Fallback = options.onWebGL2Fallback;
80
93
  this.onEvent = options.onEvent;
94
+ this.onWarning = options.onWarning;
95
+ this.debug = Boolean(options.debug);
81
96
  this.displaySettings = { ...DEFAULT_DISPLAY_SETTINGS, ...options.displaySettings };
82
97
  this.timeOffset = options.timeOffset ?? 0;
83
98
  this.cacheLimit = Math.max(0, Math.floor(options.cacheLimit ?? 24));
@@ -88,6 +103,29 @@ class BaseVideoSubtitleRenderer {
88
103
  getDisplaySettings() {
89
104
  return { ...this.displaySettings };
90
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
+ }
91
129
  /** Get parser metadata for the active subtitle track. */
92
130
  getMetadata() {
93
131
  return this.parserMetadata;
@@ -156,7 +194,7 @@ class BaseVideoSubtitleRenderer {
156
194
  /** Start initialization. */
157
195
  startInit() {
158
196
  this.init().catch((error) => {
159
- 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 }) });
160
198
  });
161
199
  }
162
200
  /** Initialize the renderer. */
@@ -207,6 +245,13 @@ class BaseVideoSubtitleRenderer {
207
245
  emitEvent(event) {
208
246
  this.onEvent?.(event);
209
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
+ }
210
255
  setParserMetadata(metadata) {
211
256
  this.parserMetadata = metadata;
212
257
  if (metadata) {
@@ -234,6 +279,11 @@ class BaseVideoSubtitleRenderer {
234
279
  this.currentRendererBackend = renderer;
235
280
  this.emitEvent({ type: 'renderer-change', renderer });
236
281
  }
282
+ recordLastRenderInfo(info) {
283
+ if (!this.debug)
284
+ return;
285
+ this.lastRenderInfo = info;
286
+ }
237
287
  /** Initialize WebGPU renderer. */
238
288
  async initWebGPU() {
239
289
  try {
@@ -369,8 +419,9 @@ class BaseVideoSubtitleRenderer {
369
419
  const currentIndex = this.findCurrentIndex(currentTime);
370
420
  // Only re-render if index changed
371
421
  if (currentIndex !== this.lastRenderedIndex) {
422
+ const cacheHit = currentIndex >= 0 && this.getWorkerRendererState().frameCache.has(currentIndex);
372
423
  const startTime = performance.now();
373
- this.renderFrame(currentTime, currentIndex);
424
+ const outcome = this.renderFrame(currentTime, currentIndex);
374
425
  const endTime = performance.now();
375
426
  // Track performance
376
427
  const renderTime = endTime - startTime;
@@ -387,9 +438,28 @@ class BaseVideoSubtitleRenderer {
387
438
  if (renderTime > frameBudget) {
388
439
  this.perfStats.framesDropped++;
389
440
  }
441
+ if (outcome.warning) {
442
+ this.emitWarning(outcome.warning);
443
+ }
390
444
  this.lastRenderedIndex = currentIndex;
391
445
  this.lastRenderedTime = currentTime;
392
- 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
+ });
393
463
  this.emitEvent({ type: 'stats', stats: this.getStats() });
394
464
  if (currentIndex >= 0 && (this.prefetchBefore > 0 || this.prefetchAfter > 0)) {
395
465
  const prefetch = this.prefetchAroundTime;
@@ -403,8 +473,9 @@ class BaseVideoSubtitleRenderer {
403
473
  }
404
474
  /** Render a subtitle frame to the canvas. */
405
475
  renderFrame(time, index) {
406
- if (!this.canvas)
407
- return;
476
+ if (!this.canvas) {
477
+ return { status: 'failed', data: null, warning: null };
478
+ }
408
479
  // Get the data for this index
409
480
  const data = index >= 0 ? this.renderAtIndex(index) : undefined;
410
481
  // If data is undefined, it means async loading is in progress
@@ -415,9 +486,11 @@ class BaseVideoSubtitleRenderer {
415
486
  // If not pending, it means the render returned no data immediately
416
487
  if (this.isPendingRender(index)) {
417
488
  // Don't clear - keep showing the last frame while loading
418
- return;
489
+ return { status: 'pending', data: null, warning: null };
419
490
  }
420
491
  }
492
+ const renderIssue = index >= 0 ? this.getWorkerRendererState().renderIssues.get(index) ?? null : null;
493
+ const warning = warningFromRenderIssue(renderIssue, { format: this.format, cueIndex: index });
421
494
  // Use best available renderer
422
495
  if (this.useWebGPU && this.webgpuRenderer) {
423
496
  this.renderFrameWebGPU(data, index);
@@ -428,6 +501,16 @@ class BaseVideoSubtitleRenderer {
428
501
  else {
429
502
  this.renderFrameCanvas2D(data, index);
430
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 };
431
514
  }
432
515
  computeLayout(data) {
433
516
  if (!this.canvas) {
@@ -591,6 +674,7 @@ class BaseVideoSubtitleRenderer {
591
674
  this.lastRenderedData = null;
592
675
  this.currentCueMetadata = null;
593
676
  this.parserMetadata = null;
677
+ this.lastRenderInfo = null;
594
678
  this.useWebGPU = false;
595
679
  this.useWebGL2 = false;
596
680
  }
@@ -630,24 +714,22 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
630
714
  else {
631
715
  throw new Error('No subtitle content or URL provided');
632
716
  }
633
- const data = new Uint8Array(arrayBuffer);
717
+ let data = new Uint8Array(arrayBuffer);
634
718
  if (this.state.useWorker) {
635
719
  try {
636
720
  this.state.sessionId = createWorkerSessionId();
637
721
  await getOrCreateWorker();
638
722
  this.emitWorkerState(true, false, this.state.sessionId);
723
+ const transferableData = createTransferableBuffer(data, Boolean(this.subContent));
639
724
  const loadResponse = await sendToWorker({
640
725
  type: 'loadPgs',
641
726
  sessionId: this.state.sessionId,
642
- data: data.buffer.slice(0)
727
+ data: transferableData
643
728
  });
644
729
  if (loadResponse.type === 'pgsLoaded') {
645
730
  this.state.workerReady = true;
646
731
  this.state.metadata = loadResponse.metadata;
647
- const tsResponse = await sendToWorker({ type: 'getPgsTimestamps', sessionId: this.state.sessionId });
648
- if (tsResponse.type === 'pgsTimestamps') {
649
- this.state.timestamps = tsResponse.timestamps;
650
- }
732
+ this.state.timestamps = loadResponse.timestamps;
651
733
  this.isLoaded = true;
652
734
  this.setParserMetadata(loadResponse.metadata);
653
735
  this.emitWorkerState(true, true, this.state.sessionId);
@@ -661,6 +743,16 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
661
743
  catch (workerError) {
662
744
  this.state.useWorker = false;
663
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
+ }));
750
+ if (!this.subContent && this.subUrl && data.byteLength === 0) {
751
+ const response = await fetch(this.subUrl);
752
+ if (!response.ok)
753
+ throw new Error(`Failed to fetch subtitle: ${response.status}`);
754
+ data = new Uint8Array(await response.arrayBuffer());
755
+ }
664
756
  }
665
757
  }
666
758
  // Main thread fallback - use idle callback to avoid blocking UI
@@ -668,7 +760,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
668
760
  this.onLoaded?.();
669
761
  }
670
762
  catch (error) {
671
- const resolvedError = error instanceof Error ? error : new Error(String(error));
763
+ const resolvedError = normalizeSubtitleError(error, { format: 'pgs' });
672
764
  this.emitEvent({ type: 'error', format: 'pgs', error: resolvedError });
673
765
  this.onError?.(resolvedError);
674
766
  }
@@ -676,7 +768,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
676
768
  async loadOnMainThread(data) {
677
769
  // Yield to browser before heavy parsing
678
770
  await this.yieldToMain();
679
- this.pgsParser = new PgsParser();
771
+ this.pgsParser = new PgsParser({ debug: this.debug, onWarning: (warning) => this.emitWarning(warning) });
680
772
  // Parse in a microtask to allow UI to update
681
773
  await new Promise((resolve) => {
682
774
  // Use requestIdleCallback if available, otherwise setTimeout
@@ -689,10 +781,16 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
689
781
  this.state.metadata = this.pgsParser.getMetadata();
690
782
  this.isLoaded = true;
691
783
  this.setParserMetadata(this.state.metadata);
784
+ if (count === 0) {
785
+ this.state.renderIssues.set(-1, 'INVALID_SUBTITLE_DATA');
786
+ }
692
787
  resolve();
693
788
  });
694
789
  });
695
790
  }
791
+ getWorkerRendererState() {
792
+ return this.state;
793
+ }
696
794
  /** Yield to main thread to prevent UI blocking */
697
795
  yieldToMain() {
698
796
  // Use scheduler.yield if available (Chrome 115+)
@@ -719,19 +817,28 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
719
817
  }
720
818
  if (this.state.useWorker && this.state.workerReady) {
721
819
  if (!this.state.pendingRenders.has(index)) {
722
- const renderPromise = sendToWorker({
820
+ const renderTask = sendToWorker({
723
821
  type: 'renderPgsAtIndex',
724
822
  sessionId: this.state.sessionId,
725
823
  index
726
- }).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);
727
834
  this.state.pendingRenders.set(index, renderPromise);
728
835
  this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
729
- renderPromise.then((result) => {
730
- setCachedFrame(this.state, index, result);
836
+ renderTask.then(({ frame, renderIssue }) => {
837
+ setCachedFrame(this.state, index, frame, renderIssue);
731
838
  this.state.pendingRenders.delete(index);
732
839
  this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
733
840
  // Force re-render on next frame by resetting lastRenderedIndex
734
- if (this.findCurrentIndex(this.video.currentTime) === index) {
841
+ if (this.findCurrentIndex(this.video.currentTime + this.timeOffset) === index) {
735
842
  this.lastRenderedIndex = -1;
736
843
  }
737
844
  });
@@ -740,7 +847,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
740
847
  return undefined;
741
848
  }
742
849
  const rendered = this.pgsParser?.renderAtIndex(index) ?? null;
743
- setCachedFrame(this.state, index, rendered);
850
+ setCachedFrame(this.state, index, rendered, this.pgsParser?.getLastRenderIssue() ?? null);
744
851
  this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
745
852
  return rendered ?? undefined;
746
853
  }
@@ -771,6 +878,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
771
878
  }
772
879
  onSeek() {
773
880
  this.state.frameCache.clear();
881
+ this.state.renderIssues.clear();
774
882
  this.state.pendingRenders.clear();
775
883
  this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
776
884
  if (this.state.useWorker && this.state.workerReady) {
@@ -784,6 +892,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
784
892
  }
785
893
  clearFrameCache() {
786
894
  this.state.frameCache.clear();
895
+ this.state.renderIssues.clear();
787
896
  this.state.pendingRenders.clear();
788
897
  this.lastRenderedIndex = -1;
789
898
  if (this.state.useWorker && this.state.workerReady) {
@@ -824,6 +933,7 @@ export class PgsRenderer extends BaseVideoSubtitleRenderer {
824
933
  dispose() {
825
934
  super.dispose();
826
935
  this.state.frameCache.clear();
936
+ this.state.renderIssues.clear();
827
937
  this.state.pendingRenders.clear();
828
938
  if (this.state.useWorker && this.state.workerReady) {
829
939
  sendToWorker({ type: 'disposePgs', sessionId: this.state.sessionId }).catch(() => { });
@@ -885,8 +995,9 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
885
995
  // Fetch missing parts
886
996
  const promises = [];
887
997
  if (!subArrayBuffer) {
888
- if (!this.subUrl)
889
- 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
+ }
890
1001
  promises.push(fetch(this.subUrl)
891
1002
  .then((r) => {
892
1003
  if (!r.ok)
@@ -898,8 +1009,9 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
898
1009
  }));
899
1010
  }
900
1011
  if (!useMksSource && !idxData) {
901
- if (!this.idxUrl)
902
- 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
+ }
903
1015
  promises.push(fetch(this.idxUrl)
904
1016
  .then((r) => {
905
1017
  if (!r.ok)
@@ -914,33 +1026,36 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
914
1026
  await Promise.all(promises);
915
1027
  }
916
1028
  if (!subArrayBuffer || (!useMksSource && !idxData)) {
917
- throw new Error('Failed to load VobSub data');
1029
+ throw createSubtitleDiagnosticError('MISSING_INPUT', 'Failed to load VobSub data.', { format: 'vobsub' });
918
1030
  }
919
- const subData = new Uint8Array(subArrayBuffer);
1031
+ let subData = new Uint8Array(subArrayBuffer);
920
1032
  if (this.state.useWorker) {
921
1033
  try {
922
1034
  this.state.sessionId = createWorkerSessionId();
923
1035
  await getOrCreateWorker();
924
1036
  this.emitWorkerState(true, false, this.state.sessionId);
1037
+ const transferableSubData = createTransferableBuffer(subData, Boolean(this.subContent));
925
1038
  const loadResponse = await sendToWorker(useMksSource
926
1039
  ? {
927
1040
  type: 'loadVobSubMks',
928
1041
  sessionId: this.state.sessionId,
929
- subData: subData.buffer.slice(0)
1042
+ subData: transferableSubData
930
1043
  }
931
1044
  : {
932
1045
  type: 'loadVobSub',
933
1046
  sessionId: this.state.sessionId,
934
1047
  idxContent: idxData,
935
- subData: subData.buffer.slice(0)
1048
+ subData: transferableSubData
936
1049
  });
937
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
+ }
938
1056
  this.state.workerReady = true;
939
1057
  this.state.metadata = loadResponse.metadata;
940
- const tsResponse = await sendToWorker({ type: 'getVobSubTimestamps', sessionId: this.state.sessionId });
941
- if (tsResponse.type === 'vobSubTimestamps') {
942
- this.state.timestamps = tsResponse.timestamps;
943
- }
1058
+ this.state.timestamps = loadResponse.timestamps;
944
1059
  this.isLoaded = true;
945
1060
  this.setParserMetadata(loadResponse.metadata);
946
1061
  this.emitWorkerState(true, true, this.state.sessionId);
@@ -954,6 +1069,16 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
954
1069
  catch (workerError) {
955
1070
  this.state.useWorker = false;
956
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
+ }));
1076
+ if (!this.subContent && this.subUrl && subData.byteLength === 0) {
1077
+ const response = await fetch(this.subUrl);
1078
+ if (!response.ok)
1079
+ throw new Error(`Failed to fetch .sub file: ${response.status}`);
1080
+ subData = new Uint8Array(await response.arrayBuffer());
1081
+ }
957
1082
  }
958
1083
  }
959
1084
  // Main thread fallback
@@ -961,7 +1086,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
961
1086
  this.onLoaded?.();
962
1087
  }
963
1088
  catch (error) {
964
- const resolvedError = error instanceof Error ? error : new Error(String(error));
1089
+ const resolvedError = normalizeSubtitleError(error, { format: 'vobsub' });
965
1090
  this.emitEvent({ type: 'error', format: 'vobsub', error: resolvedError });
966
1091
  this.onError?.(resolvedError);
967
1092
  }
@@ -969,7 +1094,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
969
1094
  async loadOnMainThread(subData, idxData, useMksSource = false) {
970
1095
  // Yield to browser before heavy parsing
971
1096
  await this.yieldToMain();
972
- this.vobsubParser = new VobSubParserLowLevel();
1097
+ this.vobsubParser = new VobSubParserLowLevel({ debug: this.debug, onWarning: (warning) => this.emitWarning(warning) });
973
1098
  // Parse in a microtask to allow UI to update
974
1099
  await new Promise((resolve) => {
975
1100
  const scheduleTask = typeof requestIdleCallback !== 'undefined'
@@ -993,6 +1118,9 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
993
1118
  });
994
1119
  });
995
1120
  }
1121
+ getWorkerRendererState() {
1122
+ return this.state;
1123
+ }
996
1124
  /** Yield to main thread to prevent UI blocking */
997
1125
  yieldToMain() {
998
1126
  const globalScheduler = globalThis.scheduler;
@@ -1046,19 +1174,28 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
1046
1174
  if (this.state.useWorker && this.state.workerReady) {
1047
1175
  // Start async render if not already pending
1048
1176
  if (!this.state.pendingRenders.has(index)) {
1049
- const renderPromise = sendToWorker({
1177
+ const renderTask = sendToWorker({
1050
1178
  type: 'renderVobSubAtIndex',
1051
1179
  sessionId: this.state.sessionId,
1052
1180
  index
1053
- }).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);
1054
1191
  this.state.pendingRenders.set(index, renderPromise);
1055
1192
  this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
1056
- renderPromise.then((result) => {
1057
- setCachedFrame(this.state, index, result);
1193
+ renderTask.then(({ frame, renderIssue }) => {
1194
+ setCachedFrame(this.state, index, frame, renderIssue);
1058
1195
  this.state.pendingRenders.delete(index);
1059
1196
  this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
1060
1197
  // Force re-render on next frame by resetting lastRenderedIndex
1061
- if (this.findCurrentIndex(this.video.currentTime) === index) {
1198
+ if (this.findCurrentIndex(this.video.currentTime + this.timeOffset) === index) {
1062
1199
  this.lastRenderedIndex = -1;
1063
1200
  }
1064
1201
  });
@@ -1067,7 +1204,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
1067
1204
  return undefined;
1068
1205
  }
1069
1206
  const rendered = this.vobsubParser?.renderAtIndex(index) ?? null;
1070
- setCachedFrame(this.state, index, rendered);
1207
+ setCachedFrame(this.state, index, rendered, this.vobsubParser?.getLastRenderIssue() ?? null);
1071
1208
  this.emitCacheChange(this.state.frameCache.size, this.state.pendingRenders.size);
1072
1209
  return rendered ?? undefined;
1073
1210
  }
@@ -1100,6 +1237,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
1100
1237
  }
1101
1238
  onSeek() {
1102
1239
  this.state.frameCache.clear();
1240
+ this.state.renderIssues.clear();
1103
1241
  this.state.pendingRenders.clear();
1104
1242
  // Clear cached index lookup on seek
1105
1243
  this.cachedIndex = -1;
@@ -1117,6 +1255,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
1117
1255
  }
1118
1256
  clearFrameCache() {
1119
1257
  this.state.frameCache.clear();
1258
+ this.state.renderIssues.clear();
1120
1259
  this.state.pendingRenders.clear();
1121
1260
  this.cachedIndex = -1;
1122
1261
  this.cachedIndexTime = -1;
@@ -1197,6 +1336,7 @@ export class VobSubRenderer extends BaseVideoSubtitleRenderer {
1197
1336
  dispose() {
1198
1337
  super.dispose();
1199
1338
  this.state.frameCache.clear();
1339
+ this.state.renderIssues.clear();
1200
1340
  this.state.pendingRenders.clear();
1201
1341
  if (this.state.useWorker && this.state.workerReady) {
1202
1342
  sendToWorker({ type: 'disposeVobSub', sessionId: this.state.sessionId }).catch(() => { });