libbitsub 1.10.1 → 1.11.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 (91) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +186 -36
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/integrations/hlsjs.d.ts +45 -0
  8. package/dist/integrations/hlsjs.d.ts.map +1 -0
  9. package/dist/integrations/hlsjs.js +121 -0
  10. package/dist/integrations/hlsjs.js.map +1 -0
  11. package/dist/integrations/index.d.ts +11 -0
  12. package/dist/integrations/index.d.ts.map +1 -0
  13. package/dist/integrations/index.js +11 -0
  14. package/dist/integrations/index.js.map +1 -0
  15. package/dist/integrations/react.d.ts +55 -0
  16. package/dist/integrations/react.d.ts.map +1 -0
  17. package/dist/integrations/react.js +127 -0
  18. package/dist/integrations/react.js.map +1 -0
  19. package/dist/integrations/shaka.d.ts +38 -0
  20. package/dist/integrations/shaka.d.ts.map +1 -0
  21. package/dist/integrations/shaka.js +71 -0
  22. package/dist/integrations/shaka.js.map +1 -0
  23. package/dist/integrations/shared.d.ts +41 -0
  24. package/dist/integrations/shared.d.ts.map +1 -0
  25. package/dist/integrations/shared.js +123 -0
  26. package/dist/integrations/shared.js.map +1 -0
  27. package/dist/integrations/videojs.d.ts +85 -0
  28. package/dist/integrations/videojs.d.ts.map +1 -0
  29. package/dist/integrations/videojs.js +111 -0
  30. package/dist/integrations/videojs.js.map +1 -0
  31. package/dist/ts/diagnostics.d.ts.map +1 -1
  32. package/dist/ts/diagnostics.js.map +1 -1
  33. package/dist/ts/frame-export.d.ts.map +1 -1
  34. package/dist/ts/frame-export.js +4 -4
  35. package/dist/ts/frame-export.js.map +1 -1
  36. package/dist/ts/parsers.d.ts +7 -6
  37. package/dist/ts/parsers.d.ts.map +1 -1
  38. package/dist/ts/parsers.js +76 -13
  39. package/dist/ts/parsers.js.map +1 -1
  40. package/dist/ts/range-loader.d.ts +31 -0
  41. package/dist/ts/range-loader.d.ts.map +1 -0
  42. package/dist/ts/range-loader.js +220 -0
  43. package/dist/ts/range-loader.js.map +1 -0
  44. package/dist/ts/renderers.d.ts +14 -0
  45. package/dist/ts/renderers.d.ts.map +1 -1
  46. package/dist/ts/renderers.js +445 -162
  47. package/dist/ts/renderers.js.map +1 -1
  48. package/dist/ts/types.d.ts +51 -1
  49. package/dist/ts/types.d.ts.map +1 -1
  50. package/dist/ts/utils.d.ts.map +1 -1
  51. package/dist/ts/utils.js.map +1 -1
  52. package/dist/ts/wasm.d.ts +2 -0
  53. package/dist/ts/wasm.d.ts.map +1 -1
  54. package/dist/ts/wasm.js +12 -0
  55. package/dist/ts/wasm.js.map +1 -1
  56. package/dist/ts/webgl2-renderer.d.ts +9 -0
  57. package/dist/ts/webgl2-renderer.d.ts.map +1 -1
  58. package/dist/ts/webgl2-renderer.js +34 -0
  59. package/dist/ts/webgl2-renderer.js.map +1 -1
  60. package/dist/ts/webgpu-renderer.d.ts +9 -0
  61. package/dist/ts/webgpu-renderer.d.ts.map +1 -1
  62. package/dist/ts/webgpu-renderer.js +70 -2
  63. package/dist/ts/webgpu-renderer.js.map +1 -1
  64. package/dist/ts/worker.d.ts +11 -0
  65. package/dist/ts/worker.d.ts.map +1 -1
  66. package/dist/ts/worker.js +235 -160
  67. package/dist/ts/worker.js.map +1 -1
  68. package/dist/wrapper.d.ts +3 -1
  69. package/dist/wrapper.d.ts.map +1 -1
  70. package/dist/wrapper.js +2 -0
  71. package/dist/wrapper.js.map +1 -1
  72. package/examples/README.md +38 -0
  73. package/examples/hlsjs/README.md +53 -0
  74. package/examples/react/README.md +60 -0
  75. package/examples/shaka/README.md +61 -0
  76. package/examples/videojs/README.md +55 -0
  77. package/package.json +68 -12
  78. package/pkg/LICENSE +1 -0
  79. package/pkg/README.md +186 -36
  80. package/pkg/libbitsub.d.ts +14 -0
  81. package/pkg/libbitsub.js +50 -0
  82. package/pkg/libbitsub_bg.wasm +0 -0
  83. package/pkg/libbitsub_bg.wasm.d.ts +7 -0
  84. package/pkg/package.json +1 -1
  85. package/src/integrations/hlsjs.ts +154 -0
  86. package/src/integrations/index.ts +36 -0
  87. package/src/integrations/react.tsx +170 -0
  88. package/src/integrations/shaka.ts +92 -0
  89. package/src/integrations/shared.ts +170 -0
  90. package/src/integrations/videojs.ts +200 -0
  91. package/src/wrapper.ts +12 -0
@@ -8,7 +8,9 @@ export class PgsParser {
8
8
  free(): void;
9
9
  [Symbol.dispose](): void;
10
10
  clearCache(): void;
11
+ feed(data: Uint8Array): number;
11
12
  findIndexAtTimestamp(time_ms: number): number;
13
+ finishFeed(): number;
12
14
  getCueCompositionCount(index: number): number;
13
15
  getCueCompositionState(index: number): number;
14
16
  getCueEndTime(index: number): number;
@@ -18,8 +20,10 @@ export class PgsParser {
18
20
  constructor();
19
21
  parse(data: Uint8Array): number;
20
22
  renderAtIndex(index: number): SubtitleFrame | undefined;
23
+ reset(): void;
21
24
  readonly count: number;
22
25
  readonly lastRenderIssue: string;
26
+ readonly pendingLen: number;
23
27
  readonly screenHeight: number;
24
28
  readonly screenWidth: number;
25
29
  }
@@ -128,6 +132,7 @@ export class VobSubFrame {
128
132
  export class VobSubParser {
129
133
  free(): void;
130
134
  [Symbol.dispose](): void;
135
+ attachSubData(sub_data: Uint8Array): void;
131
136
  clearCache(): void;
132
137
  dispose(): void;
133
138
  findIndexAtTimestamp(time_ms: number): number;
@@ -137,6 +142,7 @@ export class VobSubParser {
137
142
  getCueStartTime(index: number): number;
138
143
  getTimestamps(): Float64Array;
139
144
  loadFromData(idx_content: string, sub_data: Uint8Array): void;
145
+ loadFromIdx(idx_content: string): void;
140
146
  loadFromMks(mks_data: Uint8Array): void;
141
147
  loadFromSubOnly(sub_data: Uint8Array): void;
142
148
  constructor();
@@ -147,6 +153,7 @@ export class VobSubParser {
147
153
  readonly count: number;
148
154
  readonly debandEnabled: boolean;
149
155
  readonly hasIdxMetadata: boolean;
156
+ readonly hasSubData: boolean;
150
157
  readonly language: string;
151
158
  readonly lastRenderIssue: string;
152
159
  readonly screenHeight: number;
@@ -173,7 +180,9 @@ export interface InitOutput {
173
180
  readonly init: () => void;
174
181
  readonly pgsparser_clearCache: (a: number) => void;
175
182
  readonly pgsparser_count: (a: number) => number;
183
+ readonly pgsparser_feed: (a: number, b: number, c: number) => number;
176
184
  readonly pgsparser_findIndexAtTimestamp: (a: number, b: number) => number;
185
+ readonly pgsparser_finishFeed: (a: number) => number;
177
186
  readonly pgsparser_getCueCompositionCount: (a: number, b: number) => number;
178
187
  readonly pgsparser_getCueCompositionState: (a: number, b: number) => number;
179
188
  readonly pgsparser_getCueEndTime: (a: number, b: number) => number;
@@ -183,7 +192,9 @@ export interface InitOutput {
183
192
  readonly pgsparser_lastRenderIssue: (a: number) => [number, number];
184
193
  readonly pgsparser_new: () => number;
185
194
  readonly pgsparser_parse: (a: number, b: number, c: number) => number;
195
+ readonly pgsparser_pendingLen: (a: number) => number;
186
196
  readonly pgsparser_renderAtIndex: (a: number, b: number) => number;
197
+ readonly pgsparser_reset: (a: number) => void;
187
198
  readonly pgsparser_screenHeight: (a: number) => number;
188
199
  readonly pgsparser_screenWidth: (a: number) => number;
189
200
  readonly renderresult_compositionCount: (a: number) => number;
@@ -232,6 +243,7 @@ export interface InitOutput {
232
243
  readonly vobsubframe_width: (a: number) => number;
233
244
  readonly vobsubframe_x: (a: number) => number;
234
245
  readonly vobsubframe_y: (a: number) => number;
246
+ readonly vobsubparser_attachSubData: (a: number, b: number, c: number) => void;
235
247
  readonly vobsubparser_clearCache: (a: number) => void;
236
248
  readonly vobsubparser_count: (a: number) => number;
237
249
  readonly vobsubparser_debandEnabled: (a: number) => number;
@@ -243,9 +255,11 @@ export interface InitOutput {
243
255
  readonly vobsubparser_getCueStartTime: (a: number, b: number) => number;
244
256
  readonly vobsubparser_getTimestamps: (a: number) => any;
245
257
  readonly vobsubparser_hasIdxMetadata: (a: number) => number;
258
+ readonly vobsubparser_hasSubData: (a: number) => number;
246
259
  readonly vobsubparser_language: (a: number) => [number, number];
247
260
  readonly vobsubparser_lastRenderIssue: (a: number) => [number, number];
248
261
  readonly vobsubparser_loadFromData: (a: number, b: number, c: number, d: number, e: number) => void;
262
+ readonly vobsubparser_loadFromIdx: (a: number, b: number, c: number) => void;
249
263
  readonly vobsubparser_loadFromMks: (a: number, b: number, c: number) => [number, number];
250
264
  readonly vobsubparser_loadFromSubOnly: (a: number, b: number, c: number) => void;
251
265
  readonly vobsubparser_new: () => number;
package/pkg/libbitsub.js CHANGED
@@ -24,6 +24,16 @@ export class PgsParser {
24
24
  const ret = wasm.pgsparser_count(this.__wbg_ptr);
25
25
  return ret >>> 0;
26
26
  }
27
+ /**
28
+ * @param {Uint8Array} data
29
+ * @returns {number}
30
+ */
31
+ feed(data) {
32
+ const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
33
+ const len0 = WASM_VECTOR_LEN;
34
+ const ret = wasm.pgsparser_feed(this.__wbg_ptr, ptr0, len0);
35
+ return ret >>> 0;
36
+ }
27
37
  /**
28
38
  * @param {number} time_ms
29
39
  * @returns {number}
@@ -32,6 +42,13 @@ export class PgsParser {
32
42
  const ret = wasm.pgsparser_findIndexAtTimestamp(this.__wbg_ptr, time_ms);
33
43
  return ret;
34
44
  }
45
+ /**
46
+ * @returns {number}
47
+ */
48
+ finishFeed() {
49
+ const ret = wasm.pgsparser_finishFeed(this.__wbg_ptr);
50
+ return ret >>> 0;
51
+ }
35
52
  /**
36
53
  * @param {number} index
37
54
  * @returns {number}
@@ -110,6 +127,13 @@ export class PgsParser {
110
127
  const ret = wasm.pgsparser_parse(this.__wbg_ptr, ptr0, len0);
111
128
  return ret >>> 0;
112
129
  }
130
+ /**
131
+ * @returns {number}
132
+ */
133
+ get pendingLen() {
134
+ const ret = wasm.pgsparser_pendingLen(this.__wbg_ptr);
135
+ return ret >>> 0;
136
+ }
113
137
  /**
114
138
  * @param {number} index
115
139
  * @returns {SubtitleFrame | undefined}
@@ -118,6 +142,9 @@ export class PgsParser {
118
142
  const ret = wasm.pgsparser_renderAtIndex(this.__wbg_ptr, index);
119
143
  return ret === 0 ? undefined : SubtitleFrame.__wrap(ret);
120
144
  }
145
+ reset() {
146
+ wasm.pgsparser_reset(this.__wbg_ptr);
147
+ }
121
148
  /**
122
149
  * @returns {number}
123
150
  */
@@ -628,6 +655,14 @@ export class VobSubParser {
628
655
  const ptr = this.__destroy_into_raw();
629
656
  wasm.__wbg_vobsubparser_free(ptr, 0);
630
657
  }
658
+ /**
659
+ * @param {Uint8Array} sub_data
660
+ */
661
+ attachSubData(sub_data) {
662
+ const ptr0 = passArray8ToWasm0(sub_data, wasm.__wbindgen_malloc);
663
+ const len0 = WASM_VECTOR_LEN;
664
+ wasm.vobsubparser_attachSubData(this.__wbg_ptr, ptr0, len0);
665
+ }
631
666
  clearCache() {
632
667
  wasm.vobsubparser_clearCache(this.__wbg_ptr);
633
668
  }
@@ -702,6 +737,13 @@ export class VobSubParser {
702
737
  const ret = wasm.vobsubparser_hasIdxMetadata(this.__wbg_ptr);
703
738
  return ret !== 0;
704
739
  }
740
+ /**
741
+ * @returns {boolean}
742
+ */
743
+ get hasSubData() {
744
+ const ret = wasm.vobsubparser_hasSubData(this.__wbg_ptr);
745
+ return ret !== 0;
746
+ }
705
747
  /**
706
748
  * @returns {string}
707
749
  */
@@ -743,6 +785,14 @@ export class VobSubParser {
743
785
  const len1 = WASM_VECTOR_LEN;
744
786
  wasm.vobsubparser_loadFromData(this.__wbg_ptr, ptr0, len0, ptr1, len1);
745
787
  }
788
+ /**
789
+ * @param {string} idx_content
790
+ */
791
+ loadFromIdx(idx_content) {
792
+ const ptr0 = passStringToWasm0(idx_content, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
793
+ const len0 = WASM_VECTOR_LEN;
794
+ wasm.vobsubparser_loadFromIdx(this.__wbg_ptr, ptr0, len0);
795
+ }
746
796
  /**
747
797
  * @param {Uint8Array} mks_data
748
798
  */
Binary file
@@ -11,7 +11,9 @@ export const __wbg_vobsubparser_free: (a: number, b: number) => void;
11
11
  export const init: () => void;
12
12
  export const pgsparser_clearCache: (a: number) => void;
13
13
  export const pgsparser_count: (a: number) => number;
14
+ export const pgsparser_feed: (a: number, b: number, c: number) => number;
14
15
  export const pgsparser_findIndexAtTimestamp: (a: number, b: number) => number;
16
+ export const pgsparser_finishFeed: (a: number) => number;
15
17
  export const pgsparser_getCueCompositionCount: (a: number, b: number) => number;
16
18
  export const pgsparser_getCueCompositionState: (a: number, b: number) => number;
17
19
  export const pgsparser_getCueEndTime: (a: number, b: number) => number;
@@ -21,7 +23,9 @@ export const pgsparser_getTimestamps: (a: number) => any;
21
23
  export const pgsparser_lastRenderIssue: (a: number) => [number, number];
22
24
  export const pgsparser_new: () => number;
23
25
  export const pgsparser_parse: (a: number, b: number, c: number) => number;
26
+ export const pgsparser_pendingLen: (a: number) => number;
24
27
  export const pgsparser_renderAtIndex: (a: number, b: number) => number;
28
+ export const pgsparser_reset: (a: number) => void;
25
29
  export const pgsparser_screenHeight: (a: number) => number;
26
30
  export const pgsparser_screenWidth: (a: number) => number;
27
31
  export const renderresult_compositionCount: (a: number) => number;
@@ -70,6 +74,7 @@ export const vobsubframe_screenWidth: (a: number) => number;
70
74
  export const vobsubframe_width: (a: number) => number;
71
75
  export const vobsubframe_x: (a: number) => number;
72
76
  export const vobsubframe_y: (a: number) => number;
77
+ export const vobsubparser_attachSubData: (a: number, b: number, c: number) => void;
73
78
  export const vobsubparser_clearCache: (a: number) => void;
74
79
  export const vobsubparser_count: (a: number) => number;
75
80
  export const vobsubparser_debandEnabled: (a: number) => number;
@@ -81,9 +86,11 @@ export const vobsubparser_getCueFilePosition: (a: number, b: number) => number;
81
86
  export const vobsubparser_getCueStartTime: (a: number, b: number) => number;
82
87
  export const vobsubparser_getTimestamps: (a: number) => any;
83
88
  export const vobsubparser_hasIdxMetadata: (a: number) => number;
89
+ export const vobsubparser_hasSubData: (a: number) => number;
84
90
  export const vobsubparser_language: (a: number) => [number, number];
85
91
  export const vobsubparser_lastRenderIssue: (a: number) => [number, number];
86
92
  export const vobsubparser_loadFromData: (a: number, b: number, c: number, d: number, e: number) => void;
93
+ export const vobsubparser_loadFromIdx: (a: number, b: number, c: number) => void;
87
94
  export const vobsubparser_loadFromMks: (a: number, b: number, c: number) => [number, number];
88
95
  export const vobsubparser_loadFromSubOnly: (a: number, b: number, c: number) => void;
89
96
  export const vobsubparser_new: () => number;
package/pkg/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "altqx"
6
6
  ],
7
7
  "description": "High-performance WASM renderer for graphical subtitles (PGS and VobSub)",
8
- "version": "1.10.1",
8
+ "version": "1.11.0",
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Optional hls.js adapter for libbitsub bitmap subtitles.
3
+ *
4
+ * hls.js handles HLS media and WebVTT/IMSC text tracks; use this helper for
5
+ * external PGS/VobSub/MKS bitmap overlays on the same media element.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import Hls from 'hls.js'
10
+ * import { attachBitSubToHls } from 'libbitsub/hlsjs'
11
+ *
12
+ * const hls = new Hls()
13
+ * hls.loadSource(playlistUrl)
14
+ * hls.attachMedia(video)
15
+ * const bitsub = attachBitSubToHls(hls, { subUrl: '/subs/movie.sup' })
16
+ * // later
17
+ * bitsub.dispose()
18
+ * ```
19
+ */
20
+
21
+ import { attachBitSub, type AttachBitSubOptions, type BitSubController, type BitSubSourceOptions } from './shared'
22
+
23
+ /** Minimal hls.js surface used by the adapter. */
24
+ export interface HlsLike {
25
+ media: HTMLMediaElement | null
26
+ on?(event: string, listener: (...args: unknown[]) => void): void
27
+ off?(event: string, listener: (...args: unknown[]) => void): void
28
+ /** hls.js Events map — optional so callers can pass event name strings. */
29
+ Events?: {
30
+ MEDIA_ATTACHED?: string
31
+ MEDIA_DETACHED?: string
32
+ DESTROYING?: string
33
+ }
34
+ }
35
+
36
+ export type BitSubHlsOptions = AttachBitSubOptions
37
+
38
+ export interface BitSubHlsHandle extends BitSubController {
39
+ /** Underlying hls.js instance. */
40
+ readonly hls: HlsLike
41
+ }
42
+
43
+ function eventName(hls: HlsLike, key: 'MEDIA_ATTACHED' | 'MEDIA_DETACHED' | 'DESTROYING', fallback: string): string {
44
+ return hls.Events?.[key] ?? fallback
45
+ }
46
+
47
+ function hasSubtitleSource(source: BitSubSourceOptions): boolean {
48
+ return Boolean(source.subUrl || source.subContent || source.idxUrl || source.idxContent || source.fileName)
49
+ }
50
+
51
+ /**
52
+ * Attach libbitsub to an hls.js instance.
53
+ * Binds to `hls.media` and re-attaches on MEDIA_ATTACHED when event APIs exist.
54
+ */
55
+ export function attachBitSubToHls(hls: HlsLike, options: BitSubHlsOptions = {}): BitSubHlsHandle {
56
+ const initialMedia = hls.media
57
+ if (initialMedia && !(initialMedia instanceof HTMLVideoElement)) {
58
+ throw new Error('libbitsub hls.js adapter requires hls.media to be an HTMLVideoElement')
59
+ }
60
+
61
+ // Allow constructing before attachMedia(); load waits until a video is available.
62
+ const placeholderVideo = initialMedia instanceof HTMLVideoElement ? initialMedia : document.createElement('video')
63
+ const { autoLoad: initialAutoLoad = true, ...initialSource } = options
64
+ let lastSource: BitSubSourceOptions = { ...initialSource }
65
+ let autoLoad = initialAutoLoad
66
+
67
+ const controller = attachBitSub(placeholderVideo, {
68
+ ...initialSource,
69
+ autoLoad: initialMedia instanceof HTMLVideoElement ? initialAutoLoad : false
70
+ })
71
+
72
+ const bindMedia = (media: HTMLMediaElement | null): void => {
73
+ if (controller.disposed) return
74
+ if (media && !(media instanceof HTMLVideoElement)) {
75
+ throw new Error('libbitsub hls.js adapter requires hls.media to be an HTMLVideoElement')
76
+ }
77
+
78
+ if (!media) {
79
+ controller.setVideo(null)
80
+ return
81
+ }
82
+
83
+ // setVideo reloads the last source when present; otherwise load explicitly if autoLoad.
84
+ const needsExplicitLoad = autoLoad && hasSubtitleSource(lastSource) && controller.renderer === null
85
+ controller.setVideo(media)
86
+ if (needsExplicitLoad && controller.renderer === null && hasSubtitleSource(lastSource)) {
87
+ controller.load(lastSource)
88
+ }
89
+ }
90
+
91
+ const onMediaAttached = (): void => bindMedia(hls.media)
92
+ const onMediaDetached = (): void => {
93
+ if (!controller.disposed) controller.clear()
94
+ }
95
+ const onDestroying = (): void => dispose()
96
+
97
+ const mediaAttached = eventName(hls, 'MEDIA_ATTACHED', 'hlsMediaAttached')
98
+ const mediaDetached = eventName(hls, 'MEDIA_DETACHED', 'hlsMediaDetached')
99
+ const destroying = eventName(hls, 'DESTROYING', 'hlsDestroying')
100
+
101
+ hls.on?.(mediaAttached, onMediaAttached)
102
+ hls.on?.(mediaDetached, onMediaDetached)
103
+ hls.on?.(destroying, onDestroying)
104
+
105
+ if (initialMedia instanceof HTMLVideoElement) {
106
+ bindMedia(initialMedia)
107
+ }
108
+
109
+ const dispose = (): void => {
110
+ hls.off?.(mediaAttached, onMediaAttached)
111
+ hls.off?.(mediaDetached, onMediaDetached)
112
+ hls.off?.(destroying, onDestroying)
113
+ controller.dispose()
114
+ }
115
+
116
+ return {
117
+ hls,
118
+ get renderer() {
119
+ return controller.renderer
120
+ },
121
+ get video() {
122
+ return controller.video
123
+ },
124
+ get disposed() {
125
+ return controller.disposed
126
+ },
127
+ load: (source) => {
128
+ lastSource = { ...source }
129
+ autoLoad = true
130
+ if (!(controller.video instanceof HTMLVideoElement) || controller.video === placeholderVideo) {
131
+ const media = hls.media
132
+ if (media instanceof HTMLVideoElement) {
133
+ controller.setVideo(media)
134
+ } else {
135
+ throw new Error('libbitsub hls.js adapter: call hls.attachMedia(video) before load()')
136
+ }
137
+ }
138
+ return controller.load(source)
139
+ },
140
+ clear: () => {
141
+ autoLoad = false
142
+ controller.clear()
143
+ },
144
+ dispose,
145
+ getDisplaySettings: () => controller.getDisplaySettings(),
146
+ setDisplaySettings: (settings) => controller.setDisplaySettings(settings),
147
+ resetDisplaySettings: () => controller.resetDisplaySettings(),
148
+ getStats: () => controller.getStats(),
149
+ setVideo: (video) => controller.setVideo(video)
150
+ }
151
+ }
152
+
153
+ export type { AttachBitSubOptions, BitSubController, BitSubSourceOptions, BitSubRenderer } from './shared'
154
+ export { attachBitSub, createBitSubRenderer } from './shared'
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Optional player integrations for libbitsub.
3
+ * Prefer deep imports (`libbitsub/videojs`, `libbitsub/shaka`, `libbitsub/hlsjs`, `libbitsub/react`)
4
+ * so unused peer dependencies stay out of your bundle.
5
+ */
6
+
7
+ export {
8
+ attachBitSub,
9
+ createBitSubRenderer,
10
+ type AttachBitSubOptions,
11
+ type BitSubController,
12
+ type BitSubRenderer,
13
+ type BitSubSourceOptions
14
+ } from './shared'
15
+
16
+ export {
17
+ registerBitSubPlugin,
18
+ type BitSubVideoJsApi,
19
+ type BitSubVideoJsOptions,
20
+ type VideoJsBitSubPlayer,
21
+ type VideoJsLike,
22
+ type VideoJsPlayerLike
23
+ } from './videojs'
24
+
25
+ export { attachBitSubToShaka, type BitSubShakaHandle, type BitSubShakaOptions, type ShakaPlayerLike } from './shaka'
26
+
27
+ export { attachBitSubToHls, type BitSubHlsHandle, type BitSubHlsOptions, type HlsLike } from './hlsjs'
28
+
29
+ export {
30
+ BitSubOverlay,
31
+ useBitSub,
32
+ type BitSubOverlayProps,
33
+ type BitSubReactSource,
34
+ type UseBitSubOptions,
35
+ type UseBitSubResult
36
+ } from './react'
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Optional React bindings for libbitsub bitmap subtitles.
3
+ *
4
+ * Peer dependency: react >= 18
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import { useRef } from 'react'
9
+ * import { useBitSub } from 'libbitsub/react'
10
+ *
11
+ * function Player({ src, subUrl }: { src: string; subUrl: string }) {
12
+ * const videoRef = useRef<HTMLVideoElement>(null)
13
+ * useBitSub(videoRef, { subUrl })
14
+ * return (
15
+ * <div style={{ position: 'relative' }}>
16
+ * <video ref={videoRef} src={src} controls playsInline />
17
+ * </div>
18
+ * )
19
+ * }
20
+ * ```
21
+ *
22
+ * Prefer either `useBitSub` *or* `BitSubOverlay` for a given video — not both.
23
+ */
24
+
25
+ import { useEffect, useRef, useState, type CSSProperties, type ReactElement, type RefObject } from 'react'
26
+ import { attachBitSub, type AttachBitSubOptions, type BitSubController, type BitSubSourceOptions } from './shared'
27
+ import type { SubtitleDisplaySettings, SubtitleDiagnosticWarning, SubtitleRendererEvent } from '../wrapper'
28
+
29
+ export type BitSubReactSource = BitSubSourceOptions
30
+
31
+ export interface UseBitSubOptions extends BitSubReactSource {
32
+ /** When false, do not load even if source fields are set. Default true. */
33
+ enabled?: boolean
34
+ }
35
+
36
+ export interface UseBitSubResult {
37
+ controller: BitSubController | null
38
+ error: Error | null
39
+ }
40
+
41
+ function sourceKey(source: BitSubReactSource | null | undefined): string {
42
+ if (!source) return ''
43
+ return JSON.stringify({
44
+ subUrl: source.subUrl ?? null,
45
+ idxUrl: source.idxUrl ?? null,
46
+ fileName: source.fileName ?? null,
47
+ hasSubContent: Boolean(source.subContent),
48
+ hasIdxContent: Boolean(source.idxContent),
49
+ cacheLimit: source.cacheLimit ?? null,
50
+ timeOffset: source.timeOffset ?? null,
51
+ streamingLoad: source.streamingLoad ?? null,
52
+ rangeRequests: source.rangeRequests ?? null,
53
+ debug: source.debug ?? null,
54
+ displaySettings: source.displaySettings ?? null,
55
+ prefetchWindow: source.prefetchWindow ?? null
56
+ })
57
+ }
58
+
59
+ /**
60
+ * Bind a libbitsub renderer to a video element ref for the lifetime of the source.
61
+ * Cleans up automatically on unmount or when the source identity changes.
62
+ */
63
+ export function useBitSub(
64
+ videoRef: RefObject<HTMLVideoElement | null>,
65
+ options: UseBitSubOptions | null
66
+ ): UseBitSubResult {
67
+ const controllerRef = useRef<BitSubController | null>(null)
68
+ const [controller, setController] = useState<BitSubController | null>(null)
69
+ const [error, setError] = useState<Error | null>(null)
70
+
71
+ const onEventRef = useRef<((event: SubtitleRendererEvent) => void) | undefined>(undefined)
72
+ const onWarningRef = useRef<((warning: SubtitleDiagnosticWarning) => void) | undefined>(undefined)
73
+ const onErrorRef = useRef<((error: Error) => void) | undefined>(undefined)
74
+ const onLoadingRef = useRef<(() => void) | undefined>(undefined)
75
+ const onLoadedRef = useRef<(() => void) | undefined>(undefined)
76
+
77
+ onEventRef.current = options?.onEvent
78
+ onWarningRef.current = options?.onWarning
79
+ onErrorRef.current = options?.onError
80
+ onLoadingRef.current = options?.onLoading
81
+ onLoadedRef.current = options?.onLoaded
82
+
83
+ const enabled = options?.enabled !== false
84
+ const key = sourceKey(options)
85
+ const displaySettings = options?.displaySettings
86
+
87
+ useEffect(() => {
88
+ setError(null)
89
+ const video = videoRef.current
90
+ if (!video || !options || !enabled) {
91
+ controllerRef.current?.dispose()
92
+ controllerRef.current = null
93
+ setController(null)
94
+ return
95
+ }
96
+
97
+ const {
98
+ enabled: _enabled,
99
+ onEvent: _onEvent,
100
+ onWarning: _onWarning,
101
+ onError: _onError,
102
+ onLoading: _onLoading,
103
+ onLoaded: _onLoaded,
104
+ ...source
105
+ } = options
106
+
107
+ const attachOptions: AttachBitSubOptions = {
108
+ ...source,
109
+ onEvent: (event) => onEventRef.current?.(event),
110
+ onWarning: (warning) => onWarningRef.current?.(warning),
111
+ onError: (err) => {
112
+ setError(err)
113
+ onErrorRef.current?.(err)
114
+ },
115
+ onLoading: () => onLoadingRef.current?.(),
116
+ onLoaded: () => onLoadedRef.current?.()
117
+ }
118
+
119
+ try {
120
+ const next = attachBitSub(video, attachOptions)
121
+ controllerRef.current = next
122
+ setController(next)
123
+ return () => {
124
+ next.dispose()
125
+ if (controllerRef.current === next) {
126
+ controllerRef.current = null
127
+ setController(null)
128
+ }
129
+ }
130
+ } catch (err) {
131
+ const resolved = err instanceof Error ? err : new Error(String(err))
132
+ setError(resolved)
133
+ onErrorRef.current?.(resolved)
134
+ controllerRef.current = null
135
+ setController(null)
136
+ }
137
+ // key captures source identity; callback props stay fresh via refs
138
+ }, [videoRef, key, enabled])
139
+
140
+ useEffect(() => {
141
+ if (displaySettings) {
142
+ controllerRef.current?.setDisplaySettings(displaySettings)
143
+ }
144
+ }, [displaySettings])
145
+
146
+ return { controller, error }
147
+ }
148
+
149
+ export interface BitSubOverlayProps extends UseBitSubOptions {
150
+ videoRef: RefObject<HTMLVideoElement | null>
151
+ /** Optional class name applied to a zero-size host span for debugging. */
152
+ className?: string
153
+ style?: CSSProperties
154
+ }
155
+
156
+ /**
157
+ * Declarative overlay helper. Renders nothing visible itself — libbitsub attaches
158
+ * its canvas next to the video element. Useful when you want JSX-configured tracks.
159
+ */
160
+ export function BitSubOverlay(props: BitSubOverlayProps): ReactElement | null {
161
+ const { videoRef, className, style, ...options } = props
162
+ useBitSub(videoRef, options)
163
+
164
+ if (!className && !style) return null
165
+ return <span className={className} style={{ display: 'none', ...style }} aria-hidden='true' />
166
+ }
167
+
168
+ export type { AttachBitSubOptions, BitSubController, BitSubSourceOptions, BitSubRenderer } from './shared'
169
+ export { attachBitSub, createBitSubRenderer } from './shared'
170
+ export type { SubtitleDisplaySettings }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Optional Shaka Player adapter for libbitsub bitmap subtitles.
3
+ *
4
+ * Bitmap PGS/VobSub tracks are not native Shaka text tracks — this helper
5
+ * overlays libbitsub on the player media element.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import shaka from 'shaka-player'
10
+ * import { attachBitSubToShaka } from 'libbitsub/shaka'
11
+ *
12
+ * const player = new shaka.Player(video)
13
+ * await player.load(manifestUri)
14
+ * const bitsub = attachBitSubToShaka(player, { subUrl: '/subs/movie.sup' })
15
+ * // later
16
+ * bitsub.dispose()
17
+ * ```
18
+ */
19
+
20
+ import { attachBitSub, type AttachBitSubOptions, type BitSubController } from './shared'
21
+
22
+ /** Minimal Shaka Player surface used by the adapter. */
23
+ export interface ShakaPlayerLike {
24
+ getMediaElement(): HTMLMediaElement | null
25
+ addEventListener?(type: string, listener: (...args: unknown[]) => void): void
26
+ removeEventListener?(type: string, listener: (...args: unknown[]) => void): void
27
+ }
28
+
29
+ export type BitSubShakaOptions = AttachBitSubOptions
30
+
31
+ export interface BitSubShakaHandle extends BitSubController {
32
+ /** Underlying Shaka player reference. */
33
+ readonly player: ShakaPlayerLike
34
+ }
35
+
36
+ /**
37
+ * Attach libbitsub to a Shaka Player instance.
38
+ * Rebinds automatically when Shaka swaps the media element (if event APIs exist).
39
+ */
40
+ export function attachBitSubToShaka(player: ShakaPlayerLike, options: BitSubShakaOptions = {}): BitSubShakaHandle {
41
+ const media = player.getMediaElement()
42
+ if (!(media instanceof HTMLVideoElement)) {
43
+ throw new Error('libbitsub Shaka adapter requires a player with an HTMLVideoElement media element')
44
+ }
45
+
46
+ const controller = attachBitSub(media, options)
47
+
48
+ const onLoaded = (): void => {
49
+ if (controller.disposed) return
50
+ const next = player.getMediaElement()
51
+ if (next instanceof HTMLVideoElement) {
52
+ controller.setVideo(next)
53
+ }
54
+ }
55
+
56
+ const onUnloading = (): void => {
57
+ if (!controller.disposed) controller.clear()
58
+ }
59
+
60
+ player.addEventListener?.('loaded', onLoaded)
61
+ player.addEventListener?.('unloading', onUnloading)
62
+
63
+ const dispose = (): void => {
64
+ player.removeEventListener?.('loaded', onLoaded)
65
+ player.removeEventListener?.('unloading', onUnloading)
66
+ controller.dispose()
67
+ }
68
+
69
+ return {
70
+ player,
71
+ get renderer() {
72
+ return controller.renderer
73
+ },
74
+ get video() {
75
+ return controller.video
76
+ },
77
+ get disposed() {
78
+ return controller.disposed
79
+ },
80
+ load: (source) => controller.load(source),
81
+ clear: () => controller.clear(),
82
+ dispose,
83
+ getDisplaySettings: () => controller.getDisplaySettings(),
84
+ setDisplaySettings: (settings) => controller.setDisplaySettings(settings),
85
+ resetDisplaySettings: () => controller.resetDisplaySettings(),
86
+ getStats: () => controller.getStats(),
87
+ setVideo: (video) => controller.setVideo(video)
88
+ }
89
+ }
90
+
91
+ export type { AttachBitSubOptions, BitSubController, BitSubSourceOptions, BitSubRenderer } from './shared'
92
+ export { attachBitSub, createBitSubRenderer } from './shared'