avbridge 2.2.0 → 2.3.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 (121) hide show
  1. package/CHANGELOG.md +125 -1
  2. package/NOTICE.md +2 -2
  3. package/README.md +100 -74
  4. package/THIRD_PARTY_LICENSES.md +2 -2
  5. package/dist/avi-2JPBSHGA.js +183 -0
  6. package/dist/avi-2JPBSHGA.js.map +1 -0
  7. package/dist/avi-F6WZJK5T.cjs +185 -0
  8. package/dist/avi-F6WZJK5T.cjs.map +1 -0
  9. package/dist/{avi-GCGM7OJI.js → avi-NJXAXUXK.js} +9 -3
  10. package/dist/avi-NJXAXUXK.js.map +1 -0
  11. package/dist/{avi-6SJLWIWW.cjs → avi-W6L3BTWU.cjs} +10 -4
  12. package/dist/avi-W6L3BTWU.cjs.map +1 -0
  13. package/dist/{chunk-ILKDNBSE.js → chunk-2PGRFCWB.js} +59 -10
  14. package/dist/chunk-2PGRFCWB.js.map +1 -0
  15. package/dist/chunk-5YAWWKA3.js +18 -0
  16. package/dist/chunk-5YAWWKA3.js.map +1 -0
  17. package/dist/chunk-6UUT4BEA.cjs +219 -0
  18. package/dist/chunk-6UUT4BEA.cjs.map +1 -0
  19. package/dist/{chunk-OE66B34H.cjs → chunk-7RGG6ME7.cjs} +562 -94
  20. package/dist/chunk-7RGG6ME7.cjs.map +1 -0
  21. package/dist/{chunk-WD2ZNQA7.js → chunk-DCSOQH2N.js} +7 -4
  22. package/dist/chunk-DCSOQH2N.js.map +1 -0
  23. package/dist/chunk-F3LQJKXK.cjs +20 -0
  24. package/dist/chunk-F3LQJKXK.cjs.map +1 -0
  25. package/dist/chunk-IAYKFGFG.js +200 -0
  26. package/dist/chunk-IAYKFGFG.js.map +1 -0
  27. package/dist/chunk-NNVOHKXJ.cjs +204 -0
  28. package/dist/chunk-NNVOHKXJ.cjs.map +1 -0
  29. package/dist/{chunk-C5VA5U5O.js → chunk-NV7ILLWH.js} +556 -92
  30. package/dist/chunk-NV7ILLWH.js.map +1 -0
  31. package/dist/{chunk-HZLQNKFN.cjs → chunk-QQXBPW72.js} +54 -15
  32. package/dist/chunk-QQXBPW72.js.map +1 -0
  33. package/dist/chunk-XKPSTC34.cjs +210 -0
  34. package/dist/chunk-XKPSTC34.cjs.map +1 -0
  35. package/dist/{chunk-L4NPOJ36.cjs → chunk-Z33SBWL5.cjs} +7 -4
  36. package/dist/chunk-Z33SBWL5.cjs.map +1 -0
  37. package/dist/element-browser.js +631 -103
  38. package/dist/element-browser.js.map +1 -1
  39. package/dist/element.cjs +4 -4
  40. package/dist/element.d.cts +1 -1
  41. package/dist/element.d.ts +1 -1
  42. package/dist/element.js +3 -3
  43. package/dist/index.cjs +174 -26
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.cts +48 -4
  46. package/dist/index.d.ts +48 -4
  47. package/dist/index.js +93 -12
  48. package/dist/index.js.map +1 -1
  49. package/dist/libav-http-reader-AZLE7YFS.cjs +16 -0
  50. package/dist/{libav-http-reader-FPYDBMYK.cjs.map → libav-http-reader-AZLE7YFS.cjs.map} +1 -1
  51. package/dist/libav-http-reader-WXG3Z7AI.js +3 -0
  52. package/dist/{libav-http-reader-NQJVY273.js.map → libav-http-reader-WXG3Z7AI.js.map} +1 -1
  53. package/dist/{player-DUyvltvy.d.cts → player-B6WB74RD.d.cts} +63 -3
  54. package/dist/{player-DUyvltvy.d.ts → player-B6WB74RD.d.ts} +63 -3
  55. package/dist/player.cjs +5500 -0
  56. package/dist/player.cjs.map +1 -0
  57. package/dist/player.d.cts +649 -0
  58. package/dist/player.d.ts +649 -0
  59. package/dist/player.js +5498 -0
  60. package/dist/player.js.map +1 -0
  61. package/dist/source-73CAH6HW.cjs +28 -0
  62. package/dist/{source-CN43EI7Z.cjs.map → source-73CAH6HW.cjs.map} +1 -1
  63. package/dist/source-F656KYYV.js +3 -0
  64. package/dist/{source-FFZ7TW2B.js.map → source-F656KYYV.js.map} +1 -1
  65. package/dist/source-QJR3OHTW.js +3 -0
  66. package/dist/source-QJR3OHTW.js.map +1 -0
  67. package/dist/source-VB74JQ7Z.cjs +28 -0
  68. package/dist/source-VB74JQ7Z.cjs.map +1 -0
  69. package/dist/variant-routing-434STYAB.js +3 -0
  70. package/dist/{variant-routing-JOBWXYKD.js.map → variant-routing-434STYAB.js.map} +1 -1
  71. package/dist/variant-routing-HONNAA6R.cjs +12 -0
  72. package/dist/{variant-routing-GOHB2RZN.cjs.map → variant-routing-HONNAA6R.cjs.map} +1 -1
  73. package/package.json +9 -1
  74. package/src/classify/rules.ts +27 -5
  75. package/src/convert/remux.ts +8 -0
  76. package/src/convert/transcode.ts +41 -8
  77. package/src/element/avbridge-player.ts +845 -0
  78. package/src/element/player-icons.ts +25 -0
  79. package/src/element/player-styles.ts +472 -0
  80. package/src/errors.ts +47 -0
  81. package/src/index.ts +23 -0
  82. package/src/player-element.ts +18 -0
  83. package/src/player.ts +127 -27
  84. package/src/plugins/builtin.ts +2 -2
  85. package/src/probe/avi.ts +4 -0
  86. package/src/probe/index.ts +40 -10
  87. package/src/strategies/fallback/audio-output.ts +31 -0
  88. package/src/strategies/fallback/decoder.ts +83 -2
  89. package/src/strategies/fallback/index.ts +34 -1
  90. package/src/strategies/fallback/variant-routing.ts +7 -13
  91. package/src/strategies/fallback/video-renderer.ts +129 -33
  92. package/src/strategies/hybrid/decoder.ts +131 -20
  93. package/src/strategies/hybrid/index.ts +36 -2
  94. package/src/strategies/remux/index.ts +13 -1
  95. package/src/strategies/remux/mse.ts +12 -2
  96. package/src/strategies/remux/pipeline.ts +6 -0
  97. package/src/subtitles/index.ts +7 -3
  98. package/src/types.ts +53 -1
  99. package/src/util/libav-http-reader.ts +5 -1
  100. package/src/util/source.ts +28 -8
  101. package/src/util/transport.ts +26 -0
  102. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  103. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  104. package/dist/avi-6SJLWIWW.cjs.map +0 -1
  105. package/dist/avi-GCGM7OJI.js.map +0 -1
  106. package/dist/chunk-C5VA5U5O.js.map +0 -1
  107. package/dist/chunk-HZLQNKFN.cjs.map +0 -1
  108. package/dist/chunk-ILKDNBSE.js.map +0 -1
  109. package/dist/chunk-J5MCMN3S.js +0 -27
  110. package/dist/chunk-J5MCMN3S.js.map +0 -1
  111. package/dist/chunk-L4NPOJ36.cjs.map +0 -1
  112. package/dist/chunk-NZU7W256.cjs +0 -29
  113. package/dist/chunk-NZU7W256.cjs.map +0 -1
  114. package/dist/chunk-OE66B34H.cjs.map +0 -1
  115. package/dist/chunk-WD2ZNQA7.js.map +0 -1
  116. package/dist/libav-http-reader-FPYDBMYK.cjs +0 -16
  117. package/dist/libav-http-reader-NQJVY273.js +0 -3
  118. package/dist/source-CN43EI7Z.cjs +0 -28
  119. package/dist/source-FFZ7TW2B.js +0 -3
  120. package/dist/variant-routing-GOHB2RZN.cjs +0 -12
  121. package/dist/variant-routing-JOBWXYKD.js +0 -3
@@ -0,0 +1,219 @@
1
+ 'use strict';
2
+
3
+ // src/util/transport.ts
4
+ function mergeFetchInit(base, extra) {
5
+ if (!base && !extra) return void 0;
6
+ return {
7
+ ...base,
8
+ ...extra,
9
+ headers: {
10
+ ...base?.headers ?? {},
11
+ ...extra?.headers ?? {}
12
+ }
13
+ };
14
+ }
15
+ function fetchWith(transport) {
16
+ return transport?.fetchFn ?? globalThis.fetch;
17
+ }
18
+
19
+ // src/errors.ts
20
+ var AvbridgeError = class extends Error {
21
+ constructor(code, message, recovery, options) {
22
+ super(message, options);
23
+ this.code = code;
24
+ this.recovery = recovery;
25
+ }
26
+ code;
27
+ recovery;
28
+ name = "AvbridgeError";
29
+ };
30
+ var ERR_PROBE_FAILED = "ERR_AVBRIDGE_PROBE_FAILED";
31
+ var ERR_PROBE_UNKNOWN_CONTAINER = "ERR_AVBRIDGE_PROBE_UNKNOWN_CONTAINER";
32
+ var ERR_PROBE_FETCH_FAILED = "ERR_AVBRIDGE_PROBE_FETCH_FAILED";
33
+ var ERR_CODEC_NOT_SUPPORTED = "ERR_AVBRIDGE_CODEC_NOT_SUPPORTED";
34
+ var ERR_STRATEGY_FAILED = "ERR_AVBRIDGE_STRATEGY_FAILED";
35
+ var ERR_ALL_STRATEGIES_EXHAUSTED = "ERR_AVBRIDGE_ALL_STRATEGIES_EXHAUSTED";
36
+ var ERR_PLAYER_NOT_READY = "ERR_AVBRIDGE_PLAYER_NOT_READY";
37
+ var ERR_RANGE_NOT_SUPPORTED = "ERR_AVBRIDGE_RANGE_NOT_SUPPORTED";
38
+ var ERR_FETCH_FAILED = "ERR_AVBRIDGE_FETCH_FAILED";
39
+ var ERR_LIBAV_NOT_REACHABLE = "ERR_AVBRIDGE_LIBAV_NOT_REACHABLE";
40
+ var ERR_MSE_NOT_SUPPORTED = "ERR_AVBRIDGE_MSE_NOT_SUPPORTED";
41
+ var ERR_MSE_CODEC_NOT_SUPPORTED = "ERR_AVBRIDGE_MSE_CODEC_NOT_SUPPORTED";
42
+
43
+ // src/util/source.ts
44
+ var SNIFF_BYTES_NEEDED = 380;
45
+ var URL_SNIFF_RANGE_BYTES = 32 * 1024;
46
+ function isInMemorySource(source) {
47
+ return source.kind === "blob";
48
+ }
49
+ async function normalizeSource(source, transport) {
50
+ if (source instanceof File) {
51
+ return {
52
+ kind: "blob",
53
+ blob: source,
54
+ name: source.name,
55
+ byteLength: source.size,
56
+ original: source
57
+ };
58
+ }
59
+ if (source instanceof Blob) {
60
+ return { kind: "blob", blob: source, byteLength: source.size, original: source };
61
+ }
62
+ if (source instanceof ArrayBuffer) {
63
+ const blob = new Blob([source]);
64
+ return { kind: "blob", blob, byteLength: blob.size, original: source };
65
+ }
66
+ if (source instanceof Uint8Array) {
67
+ const blob = new Blob([source]);
68
+ return { kind: "blob", blob, byteLength: blob.size, original: source };
69
+ }
70
+ if (typeof source === "string" || source instanceof URL) {
71
+ const url = source instanceof URL ? source.toString() : source;
72
+ return await fetchUrlForSniff(url, source, transport);
73
+ }
74
+ throw new TypeError("unsupported source type");
75
+ }
76
+ async function fetchUrlForSniff(url, originalSource, transport) {
77
+ const name = url.split("/").pop()?.split("?")[0] ?? void 0;
78
+ const doFetch = fetchWith(transport);
79
+ let res;
80
+ try {
81
+ res = await doFetch(url, mergeFetchInit(transport?.requestInit, {
82
+ headers: { Range: `bytes=0-${URL_SNIFF_RANGE_BYTES - 1}` }
83
+ }));
84
+ } catch (err) {
85
+ throw new AvbridgeError(
86
+ ERR_PROBE_FETCH_FAILED,
87
+ `Failed to fetch source ${url}: ${err.message}`,
88
+ "Check that the URL is reachable and CORS is configured. If the source requires authentication, pass requestInit with credentials/headers."
89
+ );
90
+ }
91
+ if (!res.ok && res.status !== 206) {
92
+ throw new AvbridgeError(
93
+ ERR_PROBE_FETCH_FAILED,
94
+ `Failed to fetch source ${url}: ${res.status} ${res.statusText}`,
95
+ res.status === 403 || res.status === 401 ? "The server rejected the request. Pass requestInit with the required Authorization header or credentials." : "Check that the URL is correct and the server is reachable."
96
+ );
97
+ }
98
+ let byteLength;
99
+ const contentRange = res.headers.get("content-range");
100
+ if (contentRange) {
101
+ const m = contentRange.match(/\/(\d+)$/);
102
+ if (m) byteLength = parseInt(m[1], 10);
103
+ }
104
+ if (byteLength === void 0) {
105
+ const cl = res.headers.get("content-length");
106
+ if (cl) {
107
+ const n = parseInt(cl, 10);
108
+ if (Number.isFinite(n)) {
109
+ if (res.status === 200) byteLength = n;
110
+ else if (res.status === 206 && !contentRange) byteLength = n;
111
+ }
112
+ }
113
+ }
114
+ const reader = res.body?.getReader();
115
+ if (!reader) {
116
+ const buf = new Uint8Array(await res.arrayBuffer());
117
+ const sniffBytes2 = buf.slice(0, URL_SNIFF_RANGE_BYTES);
118
+ return { kind: "url", url, sniffBytes: sniffBytes2, name, byteLength, original: originalSource };
119
+ }
120
+ const chunks = [];
121
+ let collected = 0;
122
+ while (collected < URL_SNIFF_RANGE_BYTES) {
123
+ const { done, value } = await reader.read();
124
+ if (done) break;
125
+ chunks.push(value);
126
+ collected += value.byteLength;
127
+ }
128
+ await reader.cancel().catch(() => {
129
+ });
130
+ const total = Math.min(collected, URL_SNIFF_RANGE_BYTES);
131
+ const sniffBytes = new Uint8Array(total);
132
+ let offset = 0;
133
+ for (const chunk of chunks) {
134
+ if (offset >= total) break;
135
+ const room = total - offset;
136
+ sniffBytes.set(chunk.subarray(0, Math.min(chunk.byteLength, room)), offset);
137
+ offset += chunk.byteLength;
138
+ }
139
+ return { kind: "url", url, sniffBytes, name, byteLength, original: originalSource };
140
+ }
141
+ function sniffContainerFromBytes(head) {
142
+ if (head.length >= 376 && head[0] === 71 && head[188] === 71) {
143
+ return "mpegts";
144
+ }
145
+ if (head.length >= 380 && head[4] === 71 && head[192] === 71) {
146
+ return "mpegts";
147
+ }
148
+ if (head[0] === 82 && head[1] === 73 && head[2] === 70 && head[3] === 70 && head[8] === 65 && head[9] === 86 && head[10] === 73) return "avi";
149
+ if (head[0] === 82 && head[1] === 73 && head[2] === 70 && head[3] === 70 && head[8] === 87 && head[9] === 65 && head[10] === 86 && head[11] === 69) return "wav";
150
+ if (head[0] === 26 && head[1] === 69 && head[2] === 223 && head[3] === 163) {
151
+ return "mkv";
152
+ }
153
+ if (head[4] === 102 && head[5] === 116 && head[6] === 121 && head[7] === 112) {
154
+ const brand = String.fromCharCode(head[8], head[9], head[10], head[11]);
155
+ if (brand.startsWith("qt")) return "mov";
156
+ return "mp4";
157
+ }
158
+ if (head[0] === 48 && head[1] === 38 && head[2] === 178 && head[3] === 117 && head[4] === 142 && head[5] === 102 && head[6] === 207 && head[7] === 17) return "asf";
159
+ if (head[0] === 70 && head[1] === 76 && head[2] === 86) return "flv";
160
+ if (head[0] === 46 && head[1] === 82 && head[2] === 77 && head[3] === 70) {
161
+ return "rm";
162
+ }
163
+ if (head[0] === 79 && head[1] === 103 && head[2] === 103 && head[3] === 83) return "ogg";
164
+ if (head[0] === 102 && head[1] === 76 && head[2] === 97 && head[3] === 67) return "flac";
165
+ if (head[0] === 73 && head[1] === 68 && head[2] === 51) return "mp3";
166
+ if (head[0] === 255 && (head[1] & 224) === 224) {
167
+ if ((head[1] & 246) === 240) return "adts";
168
+ return "mp3";
169
+ }
170
+ return "unknown";
171
+ }
172
+ async function sniffNormalizedSource(source) {
173
+ if (source.kind === "url") {
174
+ return sniffContainerFromBytes(source.sniffBytes);
175
+ }
176
+ const buf = await readBlobBytes(source.blob, SNIFF_BYTES_NEEDED);
177
+ return sniffContainerFromBytes(new Uint8Array(buf));
178
+ }
179
+ async function sniffContainer(blob) {
180
+ const buf = await readBlobBytes(blob, SNIFF_BYTES_NEEDED);
181
+ return sniffContainerFromBytes(new Uint8Array(buf));
182
+ }
183
+ async function readBlobBytes(blob, limit) {
184
+ const slice = blob.slice(0, limit);
185
+ if (typeof slice.arrayBuffer === "function") {
186
+ try {
187
+ return await slice.arrayBuffer();
188
+ } catch {
189
+ }
190
+ }
191
+ return new Promise((resolve, reject) => {
192
+ const reader = new FileReader();
193
+ reader.onload = () => resolve(reader.result);
194
+ reader.onerror = () => reject(reader.error ?? new Error("FileReader failed"));
195
+ reader.readAsArrayBuffer(slice);
196
+ });
197
+ }
198
+
199
+ exports.AvbridgeError = AvbridgeError;
200
+ exports.ERR_ALL_STRATEGIES_EXHAUSTED = ERR_ALL_STRATEGIES_EXHAUSTED;
201
+ exports.ERR_CODEC_NOT_SUPPORTED = ERR_CODEC_NOT_SUPPORTED;
202
+ exports.ERR_FETCH_FAILED = ERR_FETCH_FAILED;
203
+ exports.ERR_LIBAV_NOT_REACHABLE = ERR_LIBAV_NOT_REACHABLE;
204
+ exports.ERR_MSE_CODEC_NOT_SUPPORTED = ERR_MSE_CODEC_NOT_SUPPORTED;
205
+ exports.ERR_MSE_NOT_SUPPORTED = ERR_MSE_NOT_SUPPORTED;
206
+ exports.ERR_PLAYER_NOT_READY = ERR_PLAYER_NOT_READY;
207
+ exports.ERR_PROBE_FAILED = ERR_PROBE_FAILED;
208
+ exports.ERR_PROBE_FETCH_FAILED = ERR_PROBE_FETCH_FAILED;
209
+ exports.ERR_PROBE_UNKNOWN_CONTAINER = ERR_PROBE_UNKNOWN_CONTAINER;
210
+ exports.ERR_RANGE_NOT_SUPPORTED = ERR_RANGE_NOT_SUPPORTED;
211
+ exports.ERR_STRATEGY_FAILED = ERR_STRATEGY_FAILED;
212
+ exports.fetchWith = fetchWith;
213
+ exports.isInMemorySource = isInMemorySource;
214
+ exports.normalizeSource = normalizeSource;
215
+ exports.sniffContainer = sniffContainer;
216
+ exports.sniffContainerFromBytes = sniffContainerFromBytes;
217
+ exports.sniffNormalizedSource = sniffNormalizedSource;
218
+ //# sourceMappingURL=chunk-6UUT4BEA.cjs.map
219
+ //# sourceMappingURL=chunk-6UUT4BEA.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util/transport.ts","../src/errors.ts","../src/util/source.ts"],"names":["sniffBytes"],"mappings":"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","file":"chunk-6UUT4BEA.cjs","sourcesContent":["import type { FetchFn, TransportConfig } from \"../types.js\";\n\n/**\n * Merge two RequestInit objects. Headers are merged (not overwritten) so\n * the caller's auth headers coexist with the player's Range headers.\n * Other fields (credentials, mode, signal, etc.) in `extra` override `base`.\n */\nexport function mergeFetchInit(\n base: RequestInit | undefined,\n extra: RequestInit | undefined,\n): RequestInit | undefined {\n if (!base && !extra) return undefined;\n return {\n ...base,\n ...extra,\n headers: {\n ...(base?.headers as Record<string, string> | undefined ?? {}),\n ...(extra?.headers as Record<string, string> | undefined ?? {}),\n },\n };\n}\n\n/** Return the fetch function from a TransportConfig, falling back to globalThis.fetch. */\nexport function fetchWith(transport?: TransportConfig): FetchFn {\n return transport?.fetchFn ?? globalThis.fetch;\n}\n","/**\n * Structured error with a machine-readable code and human-readable\n * recovery hint. Consumers can switch on `error.code` for programmatic\n * handling and show `error.recovery` in UI.\n *\n * All codes use the `ERR_AVBRIDGE_` prefix to avoid collisions.\n */\nexport class AvbridgeError extends Error {\n override name = \"AvbridgeError\";\n\n constructor(\n /** Machine-readable error code. */\n public readonly code: string,\n message: string,\n /** Human-readable recovery suggestion. */\n public readonly recovery?: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n }\n}\n\n// ── Error codes ────────────────────────────────────────────────────────\n\n// Probe\nexport const ERR_PROBE_FAILED = \"ERR_AVBRIDGE_PROBE_FAILED\";\nexport const ERR_PROBE_UNKNOWN_CONTAINER = \"ERR_AVBRIDGE_PROBE_UNKNOWN_CONTAINER\";\nexport const ERR_PROBE_FETCH_FAILED = \"ERR_AVBRIDGE_PROBE_FETCH_FAILED\";\n\n// Codec / strategy\nexport const ERR_CODEC_NOT_SUPPORTED = \"ERR_AVBRIDGE_CODEC_NOT_SUPPORTED\";\nexport const ERR_STRATEGY_FAILED = \"ERR_AVBRIDGE_STRATEGY_FAILED\";\nexport const ERR_ALL_STRATEGIES_EXHAUSTED = \"ERR_AVBRIDGE_ALL_STRATEGIES_EXHAUSTED\";\n\n// Player lifecycle\nexport const ERR_PLAYER_NOT_READY = \"ERR_AVBRIDGE_PLAYER_NOT_READY\";\n\n// Transport / network\nexport const ERR_RANGE_NOT_SUPPORTED = \"ERR_AVBRIDGE_RANGE_NOT_SUPPORTED\";\nexport const ERR_FETCH_FAILED = \"ERR_AVBRIDGE_FETCH_FAILED\";\n\n// libav\nexport const ERR_LIBAV_NOT_REACHABLE = \"ERR_AVBRIDGE_LIBAV_NOT_REACHABLE\";\n\n// MSE\nexport const ERR_MSE_NOT_SUPPORTED = \"ERR_AVBRIDGE_MSE_NOT_SUPPORTED\";\nexport const ERR_MSE_CODEC_NOT_SUPPORTED = \"ERR_AVBRIDGE_MSE_CODEC_NOT_SUPPORTED\";\n","import type { ContainerKind, MediaInput, TransportConfig } from \"../types.js\";\nimport { mergeFetchInit, fetchWith } from \"./transport.js\";\nimport { AvbridgeError, ERR_PROBE_FETCH_FAILED } from \"../errors.js\";\n\n/**\n * Bytes needed by the sniffer to identify every container we recognize.\n * MPEG-TS needs the most: a sync byte at offset 0 *and* offset 188 (one TS\n * packet apart). Allow a little extra for the M2TS variant (offset 4/192).\n */\nconst SNIFF_BYTES_NEEDED = 380;\n\n/**\n * Bytes to fetch from a URL during the initial sniff. We grab a slightly\n * larger range than `SNIFF_BYTES_NEEDED` so the cache has some headroom for\n * the demuxer's first read after sniffing, in case it wants to look at\n * a few extra bytes (e.g. mp4 ftyp + first moov box).\n */\nconst URL_SNIFF_RANGE_BYTES = 32 * 1024;\n\n/**\n * `NormalizedSource` is a discriminated union: every consumer (probe,\n * strategies) decides what to do based on `kind`. URL sources are NOT\n * fetched eagerly; we only do a Range request for the first ~32 KB so the\n * sniffer has bytes to look at. The strategies are then handed the URL\n * directly so they can stream the rest via Range requests.\n *\n * For File / Blob / ArrayBuffer / Uint8Array sources, the bytes are\n * already in memory, so we wrap them as a `blob` variant.\n */\nexport type NormalizedSource =\n | {\n kind: \"blob\";\n blob: Blob;\n name?: string;\n byteLength: number;\n original: MediaInput;\n }\n | {\n kind: \"url\";\n url: string;\n /** Bytes pulled via Range request for the sniffer. NOT the full file. */\n sniffBytes: Uint8Array;\n name?: string;\n /** Total file size from Content-Length / Content-Range. May be undefined. */\n byteLength: number | undefined;\n original: MediaInput;\n };\n\n/** True if this source carries the entire file's bytes (vs. streaming). */\nexport function isInMemorySource(source: NormalizedSource): source is Extract<NormalizedSource, { kind: \"blob\" }> {\n return source.kind === \"blob\";\n}\n\n\n/**\n * Normalize a `MediaInput` for the probe + strategy layers. **Does not**\n * download URL sources in full — only fetches the first ~32 KB via a\n * Range request, which is enough for the sniffer to identify the\n * container. The strategies are then expected to stream the rest via\n * mediabunny's `UrlSource` (Range requests, prefetch, parallelism, cache).\n *\n * For non-URL inputs, the bytes are already in memory and we just wrap them.\n */\nexport async function normalizeSource(\n source: MediaInput,\n transport?: TransportConfig,\n): Promise<NormalizedSource> {\n if (source instanceof File) {\n return {\n kind: \"blob\",\n blob: source,\n name: source.name,\n byteLength: source.size,\n original: source,\n };\n }\n if (source instanceof Blob) {\n return { kind: \"blob\", blob: source, byteLength: source.size, original: source };\n }\n if (source instanceof ArrayBuffer) {\n const blob = new Blob([source]);\n return { kind: \"blob\", blob, byteLength: blob.size, original: source };\n }\n if (source instanceof Uint8Array) {\n const blob = new Blob([source as BlobPart]);\n return { kind: \"blob\", blob, byteLength: blob.size, original: source };\n }\n if (typeof source === \"string\" || source instanceof URL) {\n const url = source instanceof URL ? source.toString() : source;\n return await fetchUrlForSniff(url, source, transport);\n }\n throw new TypeError(\"unsupported source type\");\n}\n\n/**\n * Fetch the first ~32 KB of a URL via a Range request. Falls back to a\n * full GET if the server doesn't support range requests, but in that case\n * we only read the first 32 KB and abort the rest of the response so we\n * don't accidentally buffer a large file.\n */\nasync function fetchUrlForSniff(\n url: string,\n originalSource: MediaInput,\n transport?: TransportConfig,\n): Promise<NormalizedSource> {\n const name = url.split(\"/\").pop()?.split(\"?\")[0] ?? undefined;\n const doFetch = fetchWith(transport);\n\n // First attempt: Range request for the sniff window.\n let res: Response;\n try {\n res = await doFetch(url, mergeFetchInit(transport?.requestInit, {\n headers: { Range: `bytes=0-${URL_SNIFF_RANGE_BYTES - 1}` },\n })!);\n } catch (err) {\n throw new AvbridgeError(\n ERR_PROBE_FETCH_FAILED,\n `Failed to fetch source ${url}: ${(err as Error).message}`,\n \"Check that the URL is reachable and CORS is configured. If the source requires authentication, pass requestInit with credentials/headers.\",\n );\n }\n if (!res.ok && res.status !== 206) {\n throw new AvbridgeError(\n ERR_PROBE_FETCH_FAILED,\n `Failed to fetch source ${url}: ${res.status} ${res.statusText}`,\n res.status === 403 || res.status === 401\n ? \"The server rejected the request. Pass requestInit with the required Authorization header or credentials.\"\n : \"Check that the URL is correct and the server is reachable.\",\n );\n }\n\n // Determine the total file size from Content-Range (preferred) or Content-Length.\n let byteLength: number | undefined;\n const contentRange = res.headers.get(\"content-range\");\n if (contentRange) {\n // \"bytes 0-32767/12345678\" — parse the part after the slash\n const m = contentRange.match(/\\/(\\d+)$/);\n if (m) byteLength = parseInt(m[1], 10);\n }\n if (byteLength === undefined) {\n const cl = res.headers.get(\"content-length\");\n if (cl) {\n const n = parseInt(cl, 10);\n if (Number.isFinite(n)) {\n // If the server returned 200 (full body), Content-Length is the\n // FILE size. If 206 (partial), it's the chunk size — only use it\n // as a total if no Content-Range was present (server doesn't do\n // ranges) AND the full response is smaller than our sniff window.\n if (res.status === 200) byteLength = n;\n else if (res.status === 206 && !contentRange) byteLength = n;\n }\n }\n }\n\n // Read the sniff bytes. If the server ignored the Range header and is\n // streaming the full file, only read the first window and let the rest\n // be GC'd. We use a reader so we can stop early.\n const reader = res.body?.getReader();\n if (!reader) {\n // No streamed body (some test environments). Fall back to .arrayBuffer()\n // and slice — this might pull more than we wanted, but only for the\n // initial sniff, not the full file.\n const buf = new Uint8Array(await res.arrayBuffer());\n const sniffBytes = buf.slice(0, URL_SNIFF_RANGE_BYTES);\n return { kind: \"url\", url, sniffBytes, name, byteLength, original: originalSource };\n }\n\n const chunks: Uint8Array[] = [];\n let collected = 0;\n while (collected < URL_SNIFF_RANGE_BYTES) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n collected += value.byteLength;\n }\n // Cancel the response so we don't keep downloading.\n await reader.cancel().catch(() => { /* ignore */ });\n\n // Concatenate up to URL_SNIFF_RANGE_BYTES.\n const total = Math.min(collected, URL_SNIFF_RANGE_BYTES);\n const sniffBytes = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n if (offset >= total) break;\n const room = total - offset;\n sniffBytes.set(chunk.subarray(0, Math.min(chunk.byteLength, room)), offset);\n offset += chunk.byteLength;\n }\n\n return { kind: \"url\", url, sniffBytes, name, byteLength, original: originalSource };\n}\n\n/**\n * Identify the container family from a small byte buffer. Used by the\n * probe layer for both file (Blob → first 380 bytes) and URL (Range\n * request → first 32 KB) inputs.\n *\n * Sniffing intentionally does not trust file extensions.\n */\nexport function sniffContainerFromBytes(head: Uint8Array): ContainerKind {\n // MPEG-TS: sync byte 0x47 every 188 bytes. Verify at least two sync\n // bytes in the right places to avoid false positives. Some captures\n // start with a few junk bytes — also try offsets 4 and 192 (M2TS).\n if (head.length >= 376 && head[0] === 0x47 && head[188] === 0x47) {\n return \"mpegts\";\n }\n if (head.length >= 380 && head[4] === 0x47 && head[192] === 0x47) {\n return \"mpegts\"; // M2TS — 4-byte timestamp prefix per packet\n }\n // RIFF....AVI → AVI\n if (\n head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46 &&\n head[8] === 0x41 && head[9] === 0x56 && head[10] === 0x49\n ) return \"avi\";\n // RIFF....WAVE → WAV\n if (\n head[0] === 0x52 && head[1] === 0x49 && head[2] === 0x46 && head[3] === 0x46 &&\n head[8] === 0x57 && head[9] === 0x41 && head[10] === 0x56 && head[11] === 0x45\n ) return \"wav\";\n // EBML start: 1A 45 DF A3 → MKV/WebM. Distinguish later via DocType.\n if (head[0] === 0x1a && head[1] === 0x45 && head[2] === 0xdf && head[3] === 0xa3) {\n return \"mkv\";\n }\n // ftyp at offset 4 → MP4 family\n if (head[4] === 0x66 && head[5] === 0x74 && head[6] === 0x79 && head[7] === 0x70) {\n // brand at bytes 8..11\n const brand = String.fromCharCode(head[8], head[9], head[10], head[11]);\n if (brand.startsWith(\"qt\")) return \"mov\";\n return \"mp4\";\n }\n // ASF / WMV: 30 26 B2 75 8E 66 CF 11\n if (\n head[0] === 0x30 && head[1] === 0x26 && head[2] === 0xb2 && head[3] === 0x75 &&\n head[4] === 0x8e && head[5] === 0x66 && head[6] === 0xcf && head[7] === 0x11\n ) return \"asf\";\n // FLV: 46 4C 56\n if (head[0] === 0x46 && head[1] === 0x4c && head[2] === 0x56) return \"flv\";\n // RealMedia (.rm / .rmvb): \".RMF\" — 2E 52 4D 46\n if (head[0] === 0x2e && head[1] === 0x52 && head[2] === 0x4d && head[3] === 0x46) {\n return \"rm\";\n }\n // OggS: 4F 67 67 53\n if (head[0] === 0x4f && head[1] === 0x67 && head[2] === 0x67 && head[3] === 0x53) return \"ogg\";\n // FLAC: 66 4C 61 43\n if (head[0] === 0x66 && head[1] === 0x4c && head[2] === 0x61 && head[3] === 0x43) return \"flac\";\n // ID3v2: 49 44 33 → MP3 (with id3)\n if (head[0] === 0x49 && head[1] === 0x44 && head[2] === 0x33) return \"mp3\";\n // MPEG audio frame sync: FF Fx\n if (head[0] === 0xff && (head[1] & 0xe0) === 0xe0) {\n // ADTS: FF F1 / FF F9\n if ((head[1] & 0xf6) === 0xf0) return \"adts\";\n return \"mp3\";\n }\n return \"unknown\";\n}\n\n/**\n * Convenience: sniff a `NormalizedSource` regardless of kind. For URL\n * sources, uses the pre-fetched `sniffBytes`. For blob sources, reads the\n * first 380 bytes.\n */\nexport async function sniffNormalizedSource(source: NormalizedSource): Promise<ContainerKind> {\n if (source.kind === \"url\") {\n return sniffContainerFromBytes(source.sniffBytes);\n }\n const buf = await readBlobBytes(source.blob, SNIFF_BYTES_NEEDED);\n return sniffContainerFromBytes(new Uint8Array(buf));\n}\n\n/**\n * Backwards-compatible wrapper for code that still passes a Blob directly.\n * Prefer `sniffNormalizedSource` going forward.\n */\nexport async function sniffContainer(blob: Blob): Promise<ContainerKind> {\n const buf = await readBlobBytes(blob, SNIFF_BYTES_NEEDED);\n return sniffContainerFromBytes(new Uint8Array(buf));\n}\n\n/**\n * Read up to `limit` bytes from a Blob. Tries `Blob.arrayBuffer()` first\n * (modern browsers), then falls back to `FileReader` (works under jsdom).\n */\nasync function readBlobBytes(blob: Blob, limit: number): Promise<ArrayBuffer> {\n const slice = blob.slice(0, limit);\n if (typeof (slice as Blob & { arrayBuffer?: () => Promise<ArrayBuffer> }).arrayBuffer === \"function\") {\n try {\n return await (slice as Blob & { arrayBuffer: () => Promise<ArrayBuffer> }).arrayBuffer();\n } catch {\n /* fall through to FileReader */\n }\n }\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as ArrayBuffer);\n reader.onerror = () => reject(reader.error ?? new Error(\"FileReader failed\"));\n reader.readAsArrayBuffer(slice);\n });\n}\n"]}