libbitsub 1.10.2 → 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.
- package/README.md +184 -34
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/hlsjs.d.ts +45 -0
- package/dist/integrations/hlsjs.d.ts.map +1 -0
- package/dist/integrations/hlsjs.js +121 -0
- package/dist/integrations/hlsjs.js.map +1 -0
- package/dist/integrations/index.d.ts +11 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +11 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/react.d.ts +55 -0
- package/dist/integrations/react.d.ts.map +1 -0
- package/dist/integrations/react.js +127 -0
- package/dist/integrations/react.js.map +1 -0
- package/dist/integrations/shaka.d.ts +38 -0
- package/dist/integrations/shaka.d.ts.map +1 -0
- package/dist/integrations/shaka.js +71 -0
- package/dist/integrations/shaka.js.map +1 -0
- package/dist/integrations/shared.d.ts +41 -0
- package/dist/integrations/shared.d.ts.map +1 -0
- package/dist/integrations/shared.js +123 -0
- package/dist/integrations/shared.js.map +1 -0
- package/dist/integrations/videojs.d.ts +85 -0
- package/dist/integrations/videojs.d.ts.map +1 -0
- package/dist/integrations/videojs.js +111 -0
- package/dist/integrations/videojs.js.map +1 -0
- package/dist/ts/diagnostics.d.ts.map +1 -1
- package/dist/ts/diagnostics.js.map +1 -1
- package/dist/ts/frame-export.d.ts.map +1 -1
- package/dist/ts/frame-export.js +4 -4
- package/dist/ts/frame-export.js.map +1 -1
- package/dist/ts/parsers.d.ts +7 -6
- package/dist/ts/parsers.d.ts.map +1 -1
- package/dist/ts/parsers.js +76 -13
- package/dist/ts/parsers.js.map +1 -1
- package/dist/ts/range-loader.d.ts +31 -0
- package/dist/ts/range-loader.d.ts.map +1 -0
- package/dist/ts/range-loader.js +220 -0
- package/dist/ts/range-loader.js.map +1 -0
- package/dist/ts/renderers.d.ts +14 -0
- package/dist/ts/renderers.d.ts.map +1 -1
- package/dist/ts/renderers.js +445 -162
- package/dist/ts/renderers.js.map +1 -1
- package/dist/ts/types.d.ts +51 -1
- package/dist/ts/types.d.ts.map +1 -1
- package/dist/ts/utils.d.ts.map +1 -1
- package/dist/ts/utils.js.map +1 -1
- package/dist/ts/wasm.d.ts +2 -0
- package/dist/ts/wasm.d.ts.map +1 -1
- package/dist/ts/wasm.js +12 -0
- package/dist/ts/wasm.js.map +1 -1
- package/dist/ts/webgl2-renderer.d.ts +9 -0
- package/dist/ts/webgl2-renderer.d.ts.map +1 -1
- package/dist/ts/webgl2-renderer.js +34 -0
- package/dist/ts/webgl2-renderer.js.map +1 -1
- package/dist/ts/webgpu-renderer.d.ts +9 -0
- package/dist/ts/webgpu-renderer.d.ts.map +1 -1
- package/dist/ts/webgpu-renderer.js +70 -2
- package/dist/ts/webgpu-renderer.js.map +1 -1
- package/dist/ts/worker.d.ts +11 -0
- package/dist/ts/worker.d.ts.map +1 -1
- package/dist/ts/worker.js +229 -161
- package/dist/ts/worker.js.map +1 -1
- package/dist/wrapper.d.ts +3 -1
- package/dist/wrapper.d.ts.map +1 -1
- package/dist/wrapper.js +2 -0
- package/dist/wrapper.js.map +1 -1
- package/examples/README.md +38 -0
- package/examples/hlsjs/README.md +53 -0
- package/examples/react/README.md +60 -0
- package/examples/shaka/README.md +61 -0
- package/examples/videojs/README.md +55 -0
- package/package.json +64 -7
- package/pkg/README.md +184 -34
- package/pkg/libbitsub.d.ts +14 -0
- package/pkg/libbitsub.js +50 -0
- package/pkg/libbitsub_bg.wasm +0 -0
- package/pkg/libbitsub_bg.wasm.d.ts +7 -0
- package/pkg/package.json +1 -1
- package/src/integrations/hlsjs.ts +154 -0
- package/src/integrations/index.ts +36 -0
- package/src/integrations/react.tsx +170 -0
- package/src/integrations/shaka.ts +92 -0
- package/src/integrations/shared.ts +170 -0
- package/src/integrations/videojs.ts +200 -0
- package/src/wrapper.ts +12 -0
package/pkg/libbitsub.d.ts
CHANGED
|
@@ -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
|
*/
|
package/pkg/libbitsub_bg.wasm
CHANGED
|
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
|
@@ -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'
|