avbridge 2.1.2 → 2.2.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 (62) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/dist/{avi-GNTV5ZOH.cjs → avi-6SJLWIWW.cjs} +19 -4
  3. package/dist/avi-6SJLWIWW.cjs.map +1 -0
  4. package/dist/{avi-V6HYQVR2.js → avi-GCGM7OJI.js} +18 -3
  5. package/dist/avi-GCGM7OJI.js.map +1 -0
  6. package/dist/{chunk-EJH67FXG.js → chunk-5DMTJVIU.js} +99 -3
  7. package/dist/chunk-5DMTJVIU.js.map +1 -0
  8. package/dist/{chunk-3AUGRKPY.js → chunk-C5VA5U5O.js} +94 -16
  9. package/dist/chunk-C5VA5U5O.js.map +1 -0
  10. package/dist/{chunk-JQH6D4OE.cjs → chunk-G4APZMCP.cjs} +100 -3
  11. package/dist/chunk-G4APZMCP.cjs.map +1 -0
  12. package/dist/{chunk-Y5FYF5KG.cjs → chunk-HZLQNKFN.cjs} +5 -2
  13. package/dist/chunk-HZLQNKFN.cjs.map +1 -0
  14. package/dist/{chunk-PQTZS7OA.js → chunk-ILKDNBSE.js} +5 -2
  15. package/dist/chunk-ILKDNBSE.js.map +1 -0
  16. package/dist/{chunk-DPVIOYGC.cjs → chunk-OE66B34H.cjs} +98 -20
  17. package/dist/chunk-OE66B34H.cjs.map +1 -0
  18. package/dist/element-browser.js +210 -10
  19. package/dist/element-browser.js.map +1 -1
  20. package/dist/element.cjs +4 -4
  21. package/dist/element.d.cts +1 -1
  22. package/dist/element.d.ts +1 -1
  23. package/dist/element.js +3 -3
  24. package/dist/index.cjs +18 -18
  25. package/dist/index.d.cts +2 -2
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +5 -5
  28. package/dist/libav-loader-27RDIN2I.js +3 -0
  29. package/dist/{libav-loader-XKH2TKUW.js.map → libav-loader-27RDIN2I.js.map} +1 -1
  30. package/dist/libav-loader-IV4AJ2HW.cjs +12 -0
  31. package/dist/{libav-loader-6APXVNIV.cjs.map → libav-loader-IV4AJ2HW.cjs.map} +1 -1
  32. package/dist/{player-BdtUG4rh.d.cts → player-DUyvltvy.d.cts} +3 -3
  33. package/dist/{player-BdtUG4rh.d.ts → player-DUyvltvy.d.ts} +3 -3
  34. package/dist/source-CN43EI7Z.cjs +28 -0
  35. package/dist/{source-SC6ZEQYR.cjs.map → source-CN43EI7Z.cjs.map} +1 -1
  36. package/dist/source-FFZ7TW2B.js +3 -0
  37. package/dist/{source-ZFS4H7J3.js.map → source-FFZ7TW2B.js.map} +1 -1
  38. package/package.json +1 -1
  39. package/src/classify/rules.ts +9 -2
  40. package/src/player.ts +22 -1
  41. package/src/probe/avi.ts +8 -1
  42. package/src/strategies/fallback/audio-output.ts +25 -3
  43. package/src/strategies/fallback/decoder.ts +96 -8
  44. package/src/strategies/fallback/index.ts +90 -6
  45. package/src/strategies/fallback/libav-loader.ts +12 -0
  46. package/src/types.ts +10 -1
  47. package/src/util/debug.ts +131 -0
  48. package/src/util/source.ts +4 -0
  49. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  50. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  51. package/dist/avi-GNTV5ZOH.cjs.map +0 -1
  52. package/dist/avi-V6HYQVR2.js.map +0 -1
  53. package/dist/chunk-3AUGRKPY.js.map +0 -1
  54. package/dist/chunk-DPVIOYGC.cjs.map +0 -1
  55. package/dist/chunk-EJH67FXG.js.map +0 -1
  56. package/dist/chunk-JQH6D4OE.cjs.map +0 -1
  57. package/dist/chunk-PQTZS7OA.js.map +0 -1
  58. package/dist/chunk-Y5FYF5KG.cjs.map +0 -1
  59. package/dist/libav-loader-6APXVNIV.cjs +0 -12
  60. package/dist/libav-loader-XKH2TKUW.js +0 -3
  61. package/dist/source-SC6ZEQYR.cjs +0 -28
  62. package/dist/source-ZFS4H7J3.js +0 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,99 @@ All notable changes to **avbridge** are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.2.0]
8
+
9
+ ### Added
10
+
11
+ - **RealMedia playback support** (`.rm`, `.rmvb`). The custom `avbridge`
12
+ libav variant now includes the `rm` demuxer and every RealVideo /
13
+ RealAudio decoder family:
14
+ - Video: `rv10`, `rv20`, `rv30`, `rv40`
15
+ - Audio: `cook`, `ra_144`, `ra_288`, `sipr`, `atrac3`
16
+ These codecs have no browser decoder, so classification routes them
17
+ to the fallback WASM strategy. The custom variant grows by a few
18
+ hundred KB of WASM; the webcodecs variant is unchanged.
19
+ - **Sniff layer recognizes `.RMF` magic bytes** and returns the new
20
+ `"rm"` `ContainerKind`. Probing for a `.rm` or `.rmvb` file goes
21
+ through libav directly (mediabunny doesn't handle RealMedia).
22
+ - **File-picker accept list** in `demo/index.html` and
23
+ `demo/convert.html` now includes `.rm` and `.rmvb`.
24
+
25
+ ### Changed
26
+
27
+ - **`VideoCodec`** gained `rv10`, `rv20`, `rv30` (existing `rv40` kept).
28
+ - **`AudioCodec`** gained `cook`, `ra_144`, `ra_288`, `sipr`, `atrac3`.
29
+ - **`ContainerKind`** gained `rm`.
30
+ - **Classifier** (`src/classify/rules.ts`) — all new RV/RA codecs added
31
+ to `FALLBACK_VIDEO_CODECS` / `FALLBACK_AUDIO_CODECS`.
32
+
33
+ ### Fallback strategy performance tuning
34
+
35
+ These are general-purpose improvements motivated by RealMedia testing
36
+ but also benefit MPEG-4 Part 2 (DivX/Xvid), WMV3, and other
37
+ software-decoded content:
38
+
39
+ - **Cold-start pre-roll gate lowered 300 ms → 40 ms, timeout 10 s → 3 s.**
40
+ The gate used to wait for 300 ms of buffered audio before starting
41
+ playback. On software-decode-bound content (rv40, mpeg4 @ 720p+),
42
+ the decoder produces output slower than realtime, so 300 ms is
43
+ unreachable — the gate would sit out its 10 s timeout before the
44
+ first frame appeared, which the user experienced as a silent 10-
45
+ second hang after clicking Play. The gate now starts on 40 ms
46
+ audio + first frame, and the safety timeout is 3 s. A diagnostic
47
+ warning fires loudly if the timeout is ever hit.
48
+ - **Decoder read batch size raised 16 KB → 64 KB.** Fewer JS↔WASM
49
+ `ff_read_frame_multi` / `ff_decode_multi` round trips per unit of
50
+ video, which measurably speeds up software decode on slow devices.
51
+ Queue burstiness is unchanged because the existing
52
+ `queueHighWater = 30` backpressure still applies.
53
+
54
+ ### Debug + self-diagnosis layer
55
+
56
+ New: **`src/util/debug.ts`** — a runtime-toggleable verbose logging
57
+ channel, plus unconditional warnings for suspicious conditions. The
58
+ goal is that subtle issues self-identify in the console instead of
59
+ requiring 10 minutes of reading diagnostics JSON.
60
+
61
+ - Set `globalThis.AVBRIDGE_DEBUG = true` (or append `?avbridge_debug`
62
+ to a demo page URL) to enable verbose logging. Every log is
63
+ prefixed `[avbridge:<tag>]` so you can filter.
64
+ - When debug is **off**, the following conditions still emit an
65
+ unconditional `console.warn`:
66
+ - **`[avbridge:cold-start] gate TIMEOUT…`** — the fallback
67
+ strategy's `waitForBuffer` hit its 3 s timeout with a
68
+ specific underflow (e.g. "audio=0 ms, frames=0"). This used to
69
+ silently hang playback for 10 seconds.
70
+ - **`[avbridge:decode-rate] decoder is running slower than
71
+ realtime…`** — watchdog in the fallback pump loop; fires once
72
+ per stall when framesDecoded/s stays below 60% of source fps
73
+ for ≥5 s after the first frame. Tells you the exact fps
74
+ ratio and names the likely cause.
75
+ - **`[avbridge:bootstrap] total bootstrap time <N>ms — unusually
76
+ slow…`** — bootstrap took >5 s end-to-end.
77
+ - **`[avbridge:probe] probe took <N>ms (>3000ms expected)…`** —
78
+ slow probe (usually a slow Range request or libav cold-start).
79
+ - **`[avbridge:libav-load] load "<variant>" took <N>ms
80
+ (>5000ms expected)…`** — slow WASM download or wrong base
81
+ path.
82
+
83
+ ### Known limitations
84
+
85
+ - **rv40 / rv30 at 720p+ may still stutter** on modest CPUs. Single-
86
+ threaded WASM software decode of RealVideo's motion compensation is
87
+ fundamentally slower than realtime on many files. libav.js pthreads
88
+ and a WebGL YUV→RGBA upload path are both plausible follow-ups but
89
+ not in 2.2.0. For reference: a 1024×768 rv40 file plays at roughly
90
+ 0.5-2× realtime on an M-series Mac depending on the bitrate. The
91
+ new `[avbridge:decode-rate]` watchdog flags this condition in the
92
+ console so the symptom is never a silent stutter.
93
+
94
+ ### Tests
95
+
96
+ - New sniff test for `.RMF` magic bytes (`tests/sniff.test.ts`).
97
+ - Two new classify tests for RealMedia routing (rv40+cook, rv30+ra_288).
98
+ - Test count: 115 → **118**.
99
+
7
100
  ## [2.1.2]
8
101
 
9
102
  ### Fixed
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkL4NPOJ36_cjs = require('./chunk-L4NPOJ36.cjs');
4
- var chunkJQH6D4OE_cjs = require('./chunk-JQH6D4OE.cjs');
4
+ var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
5
5
 
6
6
  // src/probe/avi.ts
7
7
  async function probeWithLibav(source, sniffed) {
8
- const libav = await chunkJQH6D4OE_cjs.loadLibav("avbridge");
8
+ const libav = await chunkG4APZMCP_cjs.loadLibav("avbridge");
9
9
  const filename = source.name ?? `input.${sniffed === "unknown" ? "bin" : sniffed}`;
10
10
  const handle = await chunkL4NPOJ36_cjs.prepareLibavInput(libav, filename, source);
11
11
  let fmt_ctx;
@@ -123,7 +123,12 @@ function ffmpegToAvbridgeVideo(name) {
123
123
  return "mpeg1";
124
124
  case "theora":
125
125
  return "theora";
126
+ case "rv10":
127
+ return "rv10";
128
+ case "rv20":
129
+ return "rv20";
126
130
  case "rv30":
131
+ return "rv30";
127
132
  case "rv40":
128
133
  return "rv40";
129
134
  default:
@@ -154,11 +159,21 @@ function ffmpegToAvbridgeAudio(name) {
154
159
  return "wmapro";
155
160
  case "alac":
156
161
  return "alac";
162
+ case "cook":
163
+ return "cook";
164
+ case "ra_144":
165
+ return "ra_144";
166
+ case "ra_288":
167
+ return "ra_288";
168
+ case "sipr":
169
+ return "sipr";
170
+ case "atrac3":
171
+ return "atrac3";
157
172
  default:
158
173
  return name;
159
174
  }
160
175
  }
161
176
 
162
177
  exports.probeWithLibav = probeWithLibav;
163
- //# sourceMappingURL=avi-GNTV5ZOH.cjs.map
164
- //# sourceMappingURL=avi-GNTV5ZOH.cjs.map
178
+ //# sourceMappingURL=avi-6SJLWIWW.cjs.map
179
+ //# sourceMappingURL=avi-6SJLWIWW.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/probe/avi.ts"],"names":["loadLibav","prepareLibavInput"],"mappings":"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","file":"avi-6SJLWIWW.cjs","sourcesContent":["import type {\n AudioCodec,\n AudioTrackInfo,\n ContainerKind,\n MediaContext,\n VideoCodec,\n VideoTrackInfo,\n} from \"../types.js\";\nimport type { NormalizedSource } from \"../util/source.js\";\nimport { prepareLibavInput, type LibavInputHandle } from \"../util/libav-http-reader.js\";\nimport { loadLibav } from \"../strategies/fallback/libav-loader.js\";\n\n/**\n * Probe AVI/ASF/FLV (and any other format mediabunny doesn't speak) via\n * libav.js. This module is `import()`-ed only when sniffing identifies one of\n * those containers.\n *\n * Critical: codec identification goes through `libav.avcodec_get_name(id)`\n * which returns the FFmpeg codec name as a string (e.g. \"h264\", \"mpeg4\",\n * \"wmv3\"). The numeric AV_CODEC_ID_* enum is *not* exposed on the libav\n * instance (only AVMEDIA_TYPE_*, AV_PIX_FMT_*, AV_SAMPLE_FMT_* and a handful\n * of others are), so comparing codec_ids against constants does not work.\n */\nexport async function probeWithLibav(\n source: NormalizedSource,\n sniffed: ContainerKind,\n): Promise<MediaContext> {\n // AVI/ASF/FLV demuxers are not in any libav.js npm variant — they live in\n // the custom \"avbridge\" build produced by `scripts/build-libav.sh`. The loader\n // emits an actionable error if the build hasn't been run yet. Threading\n // is OFF by default in `loadLibav` (see the comment there for why).\n const libav = (await loadLibav(\"avbridge\")) as unknown as LibavInstance;\n\n const filename = source.name ?? `input.${sniffed === \"unknown\" ? \"bin\" : sniffed}`;\n // For Blob/File sources we use libav's in-memory readahead file. For URL\n // sources we attach an HTTP block reader so libav demuxes via Range\n // requests instead of buffering the whole file.\n const handle: LibavInputHandle = await prepareLibavInput(libav as unknown as Parameters<typeof prepareLibavInput>[0], filename, source);\n\n let fmt_ctx: number | undefined;\n let streams: LibavStream[] = [];\n try {\n const result = await libav.ff_init_demuxer_file(filename);\n fmt_ctx = result[0];\n streams = result[1];\n } catch (err) {\n await handle.detach().catch(() => {});\n // Errors thrown across the libav.js worker/pthread boundary aren't\n // always Error instances — they can be plain objects, numbers (errno\n // codes), or strings. Stringify defensively so the user-facing message\n // never has `(undefined)` in it.\n const inner =\n err instanceof Error\n ? err.message\n : typeof err === \"object\" && err !== null\n ? JSON.stringify(err)\n : String(err);\n // eslint-disable-next-line no-console\n console.error(\"[avbridge] ff_init_demuxer_file raw error:\", err);\n throw new Error(\n `libav.js could not demux ${filename}. The current libav variant likely lacks the required demuxer (e.g. AVI). See vendor/libav/README.md for build instructions. (${inner || \"no message — see console for raw error\"})`,\n );\n }\n\n const videoTracks: VideoTrackInfo[] = [];\n const audioTracks: AudioTrackInfo[] = [];\n\n for (const stream of streams) {\n const codecName = (await safe(() => libav.avcodec_get_name(stream.codec_id))) ?? `unknown(${stream.codec_id})`;\n // codecpar holds width/height/channels/sample_rate/profile/level/extradata\n // for the actual stream. We have to copy it out of WASM memory.\n const codecpar = await safe(() => libav.ff_copyout_codecpar(stream.codecpar));\n\n if (stream.codec_type === libav.AVMEDIA_TYPE_VIDEO) {\n videoTracks.push({\n id: stream.index,\n codec: ffmpegToAvbridgeVideo(codecName),\n width: codecpar?.width ?? 0,\n height: codecpar?.height ?? 0,\n fps: framerate(stream),\n });\n } else if (stream.codec_type === libav.AVMEDIA_TYPE_AUDIO) {\n audioTracks.push({\n id: stream.index,\n codec: ffmpegToAvbridgeAudio(codecName),\n channels: codecpar?.channels ?? codecpar?.ch_layout_nb_channels ?? 0,\n sampleRate: codecpar?.sample_rate ?? 0,\n });\n }\n }\n\n // We need this duration but cannot reliably get it from the streams alone\n // for AVI; libav.js exposes it via the AVFormatContext duration helper.\n const duration = await safeDuration(libav, fmt_ctx!);\n\n // Close the demuxer; the strategy will reopen it later if it ends up being\n // chosen. Probing should not pin native resources.\n await libav.avformat_close_input_js(fmt_ctx!).catch(() => {});\n await handle.detach().catch(() => {});\n\n return {\n source: source.original,\n name: source.name,\n byteLength: source.byteLength,\n container: sniffed === \"unknown\" ? \"unknown\" : sniffed,\n videoTracks,\n audioTracks,\n subtitleTracks: [],\n probedBy: \"libav\",\n duration,\n };\n}\n\nfunction framerate(stream: LibavStream): number | undefined {\n if (typeof stream.avg_frame_rate_num === \"number\" && stream.avg_frame_rate_den) {\n return stream.avg_frame_rate_num / stream.avg_frame_rate_den;\n }\n if (stream.avg_frame_rate && typeof stream.avg_frame_rate === \"object\") {\n if (stream.avg_frame_rate.den === 0) return undefined;\n return stream.avg_frame_rate.num / stream.avg_frame_rate.den;\n }\n return undefined;\n}\n\nasync function safeDuration(libav: LibavInstance, fmt_ctx: number): Promise<number | undefined> {\n try {\n // `AVFormatContext.duration` is an int64 in microseconds (AV_TIME_BASE).\n // libav.js exposes it as a split lo/hi pair the same way it does for\n // packet pts — `AVFormatContext_duration(ctx)` returns the low 32 bits,\n // `AVFormatContext_durationhi(ctx)` returns the high 32 bits. Reading\n // only the low half (the previous bug) gave garbage for any file whose\n // duration > ~35 minutes, and zero for shorter files where the value\n // happened to live in the high half.\n const lo = await libav.AVFormatContext_duration?.(fmt_ctx);\n const hi = await libav.AVFormatContext_durationhi?.(fmt_ctx);\n if (typeof lo !== \"number\" || typeof hi !== \"number\") return undefined;\n\n // AV_NOPTS_VALUE = -2^63 → ptshi = -2147483648, pts = 0. Means \"unknown\".\n if (hi === -2147483648 && lo === 0) return undefined;\n\n // Reconstruct the 64-bit value. Prefer libav's helper when available\n // because it correctly handles signed 32-bit two's complement.\n const us =\n typeof libav.i64tof64 === \"function\"\n ? libav.i64tof64(lo, hi)\n : hi * 0x100000000 + lo + (lo < 0 ? 0x100000000 : 0);\n\n if (!Number.isFinite(us) || us <= 0) return undefined;\n return us / 1_000_000;\n } catch {\n return undefined;\n }\n}\n\nasync function safe<T>(fn: () => Promise<T> | T): Promise<T | undefined> {\n try { return await fn(); } catch { return undefined; }\n}\n\n/** Map FFmpeg codec names to avbridge video codec identifiers. */\nfunction ffmpegToAvbridgeVideo(name: string): VideoCodec {\n switch (name) {\n case \"h264\": return \"h264\";\n case \"hevc\": return \"h265\";\n case \"vp8\": return \"vp8\";\n case \"vp9\": return \"vp9\";\n case \"av1\": return \"av1\";\n case \"mpeg4\": return \"mpeg4\"; // MPEG-4 Part 2 / DivX / Xvid\n case \"msmpeg4v1\":\n case \"msmpeg4v2\":\n case \"msmpeg4v3\": // a.k.a. DIV3\n return \"mpeg4\";\n case \"wmv1\":\n case \"wmv2\":\n case \"wmv3\":\n return \"wmv3\";\n case \"vc1\": return \"vc1\";\n case \"mpeg2video\": return \"mpeg2\";\n case \"mpeg1video\": return \"mpeg1\";\n case \"theora\": return \"theora\";\n case \"rv10\": return \"rv10\";\n case \"rv20\": return \"rv20\";\n case \"rv30\": return \"rv30\";\n case \"rv40\": return \"rv40\";\n default: return name as VideoCodec;\n }\n}\n\nfunction ffmpegToAvbridgeAudio(name: string): AudioCodec {\n switch (name) {\n case \"aac\": return \"aac\";\n case \"mp3\":\n case \"mp3float\":\n return \"mp3\";\n case \"opus\": return \"opus\";\n case \"vorbis\": return \"vorbis\";\n case \"flac\": return \"flac\";\n case \"ac3\": return \"ac3\";\n case \"eac3\": return \"eac3\";\n case \"wmav1\":\n case \"wmav2\": return \"wmav2\";\n case \"wmapro\": return \"wmapro\";\n case \"alac\": return \"alac\";\n case \"cook\": return \"cook\";\n case \"ra_144\": return \"ra_144\";\n case \"ra_288\": return \"ra_288\";\n case \"sipr\": return \"sipr\";\n case \"atrac3\": return \"atrac3\";\n default: return name as AudioCodec;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Minimal structural types for the slice of libav.js we touch.\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface LibavStream {\n index: number;\n codec_type: number;\n codec_id: number;\n codecpar: number;\n avg_frame_rate?: { num: number; den: number };\n avg_frame_rate_num?: number;\n avg_frame_rate_den?: number;\n}\n\ninterface LibavCodecpar {\n width?: number;\n height?: number;\n channels?: number;\n ch_layout_nb_channels?: number;\n sample_rate?: number;\n profile?: number;\n level?: number;\n}\n\ninterface LibavInstance {\n mkreadaheadfile(name: string, blob: Blob): Promise<void>;\n unlinkreadaheadfile(name: string): Promise<void>;\n ff_init_demuxer_file(name: string): Promise<[number, LibavStream[]]>;\n ff_copyout_codecpar(codecpar: number): Promise<LibavCodecpar>;\n avcodec_get_name(codec_id: number): Promise<string>;\n avformat_close_input_js(ctx: number): Promise<void>;\n AVFormatContext_duration?(ctx: number): Promise<number>;\n AVFormatContext_durationhi?(ctx: number): Promise<number>;\n i64tof64?(lo: number, hi: number): number;\n\n AVMEDIA_TYPE_VIDEO: number;\n AVMEDIA_TYPE_AUDIO: number;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { prepareLibavInput } from './chunk-WD2ZNQA7.js';
2
- import { loadLibav } from './chunk-EJH67FXG.js';
2
+ import { loadLibav } from './chunk-5DMTJVIU.js';
3
3
 
4
4
  // src/probe/avi.ts
5
5
  async function probeWithLibav(source, sniffed) {
@@ -121,7 +121,12 @@ function ffmpegToAvbridgeVideo(name) {
121
121
  return "mpeg1";
122
122
  case "theora":
123
123
  return "theora";
124
+ case "rv10":
125
+ return "rv10";
126
+ case "rv20":
127
+ return "rv20";
124
128
  case "rv30":
129
+ return "rv30";
125
130
  case "rv40":
126
131
  return "rv40";
127
132
  default:
@@ -152,11 +157,21 @@ function ffmpegToAvbridgeAudio(name) {
152
157
  return "wmapro";
153
158
  case "alac":
154
159
  return "alac";
160
+ case "cook":
161
+ return "cook";
162
+ case "ra_144":
163
+ return "ra_144";
164
+ case "ra_288":
165
+ return "ra_288";
166
+ case "sipr":
167
+ return "sipr";
168
+ case "atrac3":
169
+ return "atrac3";
155
170
  default:
156
171
  return name;
157
172
  }
158
173
  }
159
174
 
160
175
  export { probeWithLibav };
161
- //# sourceMappingURL=avi-V6HYQVR2.js.map
162
- //# sourceMappingURL=avi-V6HYQVR2.js.map
176
+ //# sourceMappingURL=avi-GCGM7OJI.js.map
177
+ //# sourceMappingURL=avi-GCGM7OJI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/probe/avi.ts"],"names":[],"mappings":"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","file":"avi-GCGM7OJI.js","sourcesContent":["import type {\n AudioCodec,\n AudioTrackInfo,\n ContainerKind,\n MediaContext,\n VideoCodec,\n VideoTrackInfo,\n} from \"../types.js\";\nimport type { NormalizedSource } from \"../util/source.js\";\nimport { prepareLibavInput, type LibavInputHandle } from \"../util/libav-http-reader.js\";\nimport { loadLibav } from \"../strategies/fallback/libav-loader.js\";\n\n/**\n * Probe AVI/ASF/FLV (and any other format mediabunny doesn't speak) via\n * libav.js. This module is `import()`-ed only when sniffing identifies one of\n * those containers.\n *\n * Critical: codec identification goes through `libav.avcodec_get_name(id)`\n * which returns the FFmpeg codec name as a string (e.g. \"h264\", \"mpeg4\",\n * \"wmv3\"). The numeric AV_CODEC_ID_* enum is *not* exposed on the libav\n * instance (only AVMEDIA_TYPE_*, AV_PIX_FMT_*, AV_SAMPLE_FMT_* and a handful\n * of others are), so comparing codec_ids against constants does not work.\n */\nexport async function probeWithLibav(\n source: NormalizedSource,\n sniffed: ContainerKind,\n): Promise<MediaContext> {\n // AVI/ASF/FLV demuxers are not in any libav.js npm variant — they live in\n // the custom \"avbridge\" build produced by `scripts/build-libav.sh`. The loader\n // emits an actionable error if the build hasn't been run yet. Threading\n // is OFF by default in `loadLibav` (see the comment there for why).\n const libav = (await loadLibav(\"avbridge\")) as unknown as LibavInstance;\n\n const filename = source.name ?? `input.${sniffed === \"unknown\" ? \"bin\" : sniffed}`;\n // For Blob/File sources we use libav's in-memory readahead file. For URL\n // sources we attach an HTTP block reader so libav demuxes via Range\n // requests instead of buffering the whole file.\n const handle: LibavInputHandle = await prepareLibavInput(libav as unknown as Parameters<typeof prepareLibavInput>[0], filename, source);\n\n let fmt_ctx: number | undefined;\n let streams: LibavStream[] = [];\n try {\n const result = await libav.ff_init_demuxer_file(filename);\n fmt_ctx = result[0];\n streams = result[1];\n } catch (err) {\n await handle.detach().catch(() => {});\n // Errors thrown across the libav.js worker/pthread boundary aren't\n // always Error instances — they can be plain objects, numbers (errno\n // codes), or strings. Stringify defensively so the user-facing message\n // never has `(undefined)` in it.\n const inner =\n err instanceof Error\n ? err.message\n : typeof err === \"object\" && err !== null\n ? JSON.stringify(err)\n : String(err);\n // eslint-disable-next-line no-console\n console.error(\"[avbridge] ff_init_demuxer_file raw error:\", err);\n throw new Error(\n `libav.js could not demux ${filename}. The current libav variant likely lacks the required demuxer (e.g. AVI). See vendor/libav/README.md for build instructions. (${inner || \"no message — see console for raw error\"})`,\n );\n }\n\n const videoTracks: VideoTrackInfo[] = [];\n const audioTracks: AudioTrackInfo[] = [];\n\n for (const stream of streams) {\n const codecName = (await safe(() => libav.avcodec_get_name(stream.codec_id))) ?? `unknown(${stream.codec_id})`;\n // codecpar holds width/height/channels/sample_rate/profile/level/extradata\n // for the actual stream. We have to copy it out of WASM memory.\n const codecpar = await safe(() => libav.ff_copyout_codecpar(stream.codecpar));\n\n if (stream.codec_type === libav.AVMEDIA_TYPE_VIDEO) {\n videoTracks.push({\n id: stream.index,\n codec: ffmpegToAvbridgeVideo(codecName),\n width: codecpar?.width ?? 0,\n height: codecpar?.height ?? 0,\n fps: framerate(stream),\n });\n } else if (stream.codec_type === libav.AVMEDIA_TYPE_AUDIO) {\n audioTracks.push({\n id: stream.index,\n codec: ffmpegToAvbridgeAudio(codecName),\n channels: codecpar?.channels ?? codecpar?.ch_layout_nb_channels ?? 0,\n sampleRate: codecpar?.sample_rate ?? 0,\n });\n }\n }\n\n // We need this duration but cannot reliably get it from the streams alone\n // for AVI; libav.js exposes it via the AVFormatContext duration helper.\n const duration = await safeDuration(libav, fmt_ctx!);\n\n // Close the demuxer; the strategy will reopen it later if it ends up being\n // chosen. Probing should not pin native resources.\n await libav.avformat_close_input_js(fmt_ctx!).catch(() => {});\n await handle.detach().catch(() => {});\n\n return {\n source: source.original,\n name: source.name,\n byteLength: source.byteLength,\n container: sniffed === \"unknown\" ? \"unknown\" : sniffed,\n videoTracks,\n audioTracks,\n subtitleTracks: [],\n probedBy: \"libav\",\n duration,\n };\n}\n\nfunction framerate(stream: LibavStream): number | undefined {\n if (typeof stream.avg_frame_rate_num === \"number\" && stream.avg_frame_rate_den) {\n return stream.avg_frame_rate_num / stream.avg_frame_rate_den;\n }\n if (stream.avg_frame_rate && typeof stream.avg_frame_rate === \"object\") {\n if (stream.avg_frame_rate.den === 0) return undefined;\n return stream.avg_frame_rate.num / stream.avg_frame_rate.den;\n }\n return undefined;\n}\n\nasync function safeDuration(libav: LibavInstance, fmt_ctx: number): Promise<number | undefined> {\n try {\n // `AVFormatContext.duration` is an int64 in microseconds (AV_TIME_BASE).\n // libav.js exposes it as a split lo/hi pair the same way it does for\n // packet pts — `AVFormatContext_duration(ctx)` returns the low 32 bits,\n // `AVFormatContext_durationhi(ctx)` returns the high 32 bits. Reading\n // only the low half (the previous bug) gave garbage for any file whose\n // duration > ~35 minutes, and zero for shorter files where the value\n // happened to live in the high half.\n const lo = await libav.AVFormatContext_duration?.(fmt_ctx);\n const hi = await libav.AVFormatContext_durationhi?.(fmt_ctx);\n if (typeof lo !== \"number\" || typeof hi !== \"number\") return undefined;\n\n // AV_NOPTS_VALUE = -2^63 → ptshi = -2147483648, pts = 0. Means \"unknown\".\n if (hi === -2147483648 && lo === 0) return undefined;\n\n // Reconstruct the 64-bit value. Prefer libav's helper when available\n // because it correctly handles signed 32-bit two's complement.\n const us =\n typeof libav.i64tof64 === \"function\"\n ? libav.i64tof64(lo, hi)\n : hi * 0x100000000 + lo + (lo < 0 ? 0x100000000 : 0);\n\n if (!Number.isFinite(us) || us <= 0) return undefined;\n return us / 1_000_000;\n } catch {\n return undefined;\n }\n}\n\nasync function safe<T>(fn: () => Promise<T> | T): Promise<T | undefined> {\n try { return await fn(); } catch { return undefined; }\n}\n\n/** Map FFmpeg codec names to avbridge video codec identifiers. */\nfunction ffmpegToAvbridgeVideo(name: string): VideoCodec {\n switch (name) {\n case \"h264\": return \"h264\";\n case \"hevc\": return \"h265\";\n case \"vp8\": return \"vp8\";\n case \"vp9\": return \"vp9\";\n case \"av1\": return \"av1\";\n case \"mpeg4\": return \"mpeg4\"; // MPEG-4 Part 2 / DivX / Xvid\n case \"msmpeg4v1\":\n case \"msmpeg4v2\":\n case \"msmpeg4v3\": // a.k.a. DIV3\n return \"mpeg4\";\n case \"wmv1\":\n case \"wmv2\":\n case \"wmv3\":\n return \"wmv3\";\n case \"vc1\": return \"vc1\";\n case \"mpeg2video\": return \"mpeg2\";\n case \"mpeg1video\": return \"mpeg1\";\n case \"theora\": return \"theora\";\n case \"rv10\": return \"rv10\";\n case \"rv20\": return \"rv20\";\n case \"rv30\": return \"rv30\";\n case \"rv40\": return \"rv40\";\n default: return name as VideoCodec;\n }\n}\n\nfunction ffmpegToAvbridgeAudio(name: string): AudioCodec {\n switch (name) {\n case \"aac\": return \"aac\";\n case \"mp3\":\n case \"mp3float\":\n return \"mp3\";\n case \"opus\": return \"opus\";\n case \"vorbis\": return \"vorbis\";\n case \"flac\": return \"flac\";\n case \"ac3\": return \"ac3\";\n case \"eac3\": return \"eac3\";\n case \"wmav1\":\n case \"wmav2\": return \"wmav2\";\n case \"wmapro\": return \"wmapro\";\n case \"alac\": return \"alac\";\n case \"cook\": return \"cook\";\n case \"ra_144\": return \"ra_144\";\n case \"ra_288\": return \"ra_288\";\n case \"sipr\": return \"sipr\";\n case \"atrac3\": return \"atrac3\";\n default: return name as AudioCodec;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Minimal structural types for the slice of libav.js we touch.\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface LibavStream {\n index: number;\n codec_type: number;\n codec_id: number;\n codecpar: number;\n avg_frame_rate?: { num: number; den: number };\n avg_frame_rate_num?: number;\n avg_frame_rate_den?: number;\n}\n\ninterface LibavCodecpar {\n width?: number;\n height?: number;\n channels?: number;\n ch_layout_nb_channels?: number;\n sample_rate?: number;\n profile?: number;\n level?: number;\n}\n\ninterface LibavInstance {\n mkreadaheadfile(name: string, blob: Blob): Promise<void>;\n unlinkreadaheadfile(name: string): Promise<void>;\n ff_init_demuxer_file(name: string): Promise<[number, LibavStream[]]>;\n ff_copyout_codecpar(codecpar: number): Promise<LibavCodecpar>;\n avcodec_get_name(codec_id: number): Promise<string>;\n avformat_close_input_js(ctx: number): Promise<void>;\n AVFormatContext_duration?(ctx: number): Promise<number>;\n AVFormatContext_durationhi?(ctx: number): Promise<number>;\n i64tof64?(lo: number, hi: number): number;\n\n AVMEDIA_TYPE_VIDEO: number;\n AVMEDIA_TYPE_AUDIO: number;\n}\n"]}
@@ -1,3 +1,90 @@
1
+ // src/util/debug.ts
2
+ function isDebugEnabled() {
3
+ if (typeof globalThis === "undefined") return false;
4
+ const g = globalThis;
5
+ if (g.AVBRIDGE_DEBUG === true) return true;
6
+ if (typeof location !== "undefined" && typeof URLSearchParams !== "undefined") {
7
+ try {
8
+ const p = new URLSearchParams(location.search);
9
+ if (p.has("avbridge_debug")) {
10
+ g.AVBRIDGE_DEBUG = true;
11
+ return true;
12
+ }
13
+ } catch {
14
+ }
15
+ }
16
+ return false;
17
+ }
18
+ function fmt(tag) {
19
+ return `[avbridge:${tag}]`;
20
+ }
21
+ var dbg = {
22
+ /** Verbose — only when debug is enabled. The hot-path normal case. */
23
+ info(tag, ...args) {
24
+ if (isDebugEnabled()) console.info(fmt(tag), ...args);
25
+ },
26
+ /** Warning — only when debug is enabled. Non-fatal oddities. */
27
+ warn(tag, ...args) {
28
+ if (isDebugEnabled()) console.warn(fmt(tag), ...args);
29
+ },
30
+ /**
31
+ * Self-diagnosis warning. **Always** emits regardless of debug flag.
32
+ * Use this only for conditions that mean something is actually wrong
33
+ * or degraded — not for routine chatter.
34
+ */
35
+ diag(tag, ...args) {
36
+ console.warn(fmt(tag), ...args);
37
+ },
38
+ /**
39
+ * Timing helper: wraps an async call and logs its elapsed time when
40
+ * debug is on. The callback runs whether debug is on or off — this is
41
+ * just for the `dbg.info` at the end.
42
+ *
43
+ * Also unconditionally fires `dbg.diag` if the elapsed time exceeds
44
+ * `slowMs`, so "the bootstrap took 8 seconds" shows up even without
45
+ * debug mode enabled.
46
+ */
47
+ async timed(tag, label, slowMs, fn) {
48
+ const start = performance.now();
49
+ try {
50
+ const result = await fn();
51
+ const elapsed = performance.now() - start;
52
+ if (isDebugEnabled()) {
53
+ console.info(fmt(tag), `${label} ${elapsed.toFixed(0)}ms`);
54
+ }
55
+ if (elapsed > slowMs) {
56
+ console.warn(
57
+ fmt(tag),
58
+ `${label} took ${elapsed.toFixed(0)}ms (>${slowMs}ms expected) \u2014 this is unusually slow; possible causes: ${hintForTag(tag)}`
59
+ );
60
+ }
61
+ return result;
62
+ } catch (err) {
63
+ const elapsed = performance.now() - start;
64
+ console.warn(
65
+ fmt(tag),
66
+ `${label} FAILED after ${elapsed.toFixed(0)}ms:`,
67
+ err
68
+ );
69
+ throw err;
70
+ }
71
+ }
72
+ };
73
+ function hintForTag(tag) {
74
+ switch (tag) {
75
+ case "probe":
76
+ return "slow network (range request), large sniff window, or libav cold-start";
77
+ case "libav-load":
78
+ return "large .wasm download, misconfigured AVBRIDGE_LIBAV_BASE, or server-side MIME type";
79
+ case "bootstrap":
80
+ return "probe+classify+strategy-init chain; enable AVBRIDGE_DEBUG for a phase breakdown";
81
+ case "cold-start":
82
+ return "decoder is producing output slower than realtime \u2014 check framesDecoded in getDiagnostics()";
83
+ default:
84
+ return "unknown stage \u2014 enable globalThis.AVBRIDGE_DEBUG for more detail";
85
+ }
86
+ }
87
+
1
88
  // src/strategies/fallback/libav-loader.ts
2
89
  var cache = /* @__PURE__ */ new Map();
3
90
  function cacheKey(variant, threads) {
@@ -15,9 +102,18 @@ function loadLibav(variant = "webcodecs", opts = {}) {
15
102
  return entry;
16
103
  }
17
104
  async function loadVariant(variant, wantThreads) {
105
+ return dbg.timed(
106
+ "libav-load",
107
+ `load "${variant}" (threads=${wantThreads})`,
108
+ 5e3,
109
+ () => loadVariantInner(variant, wantThreads)
110
+ );
111
+ }
112
+ async function loadVariantInner(variant, wantThreads) {
18
113
  const key = cacheKey(variant, wantThreads);
19
114
  const base = `${libavBaseUrl()}/${variant}`;
20
115
  const variantUrl = `${base}/libav-${variant}.mjs`;
116
+ dbg.info("libav-load", `fetching ${variantUrl}`);
21
117
  if (typeof fetch === "function") {
22
118
  try {
23
119
  const head = await fetch(variantUrl, { method: "GET", headers: { Range: "bytes=0-0" } });
@@ -99,6 +195,6 @@ function chain(message, err) {
99
195
  return new Error(`${message}: ${inner || "(no message \u2014 see browser console)"}`);
100
196
  }
101
197
 
102
- export { loadLibav };
103
- //# sourceMappingURL=chunk-EJH67FXG.js.map
104
- //# sourceMappingURL=chunk-EJH67FXG.js.map
198
+ export { dbg, loadLibav };
199
+ //# sourceMappingURL=chunk-5DMTJVIU.js.map
200
+ //# sourceMappingURL=chunk-5DMTJVIU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util/debug.ts","../src/strategies/fallback/libav-loader.ts"],"names":[],"mappings":";AA8BA,SAAS,cAAA,GAA0B;AACjC,EAAA,IAAI,OAAO,UAAA,KAAe,WAAA,EAAa,OAAO,KAAA;AAC9C,EAAA,MAAM,CAAA,GAAI,UAAA;AACV,EAAA,IAAI,CAAA,CAAE,cAAA,KAAmB,IAAA,EAAM,OAAO,IAAA;AAItC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,IAAe,OAAO,oBAAoB,WAAA,EAAa;AAC7E,IAAA,IAAI;AACF,MAAA,MAAM,CAAA,GAAI,IAAI,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAA;AAC7C,MAAA,IAAI,CAAA,CAAE,GAAA,CAAI,gBAAgB,CAAA,EAAG;AAC3B,QAAA,CAAA,CAAE,cAAA,GAAiB,IAAA;AACnB,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAAe;AAAA,EACzB;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,IAAI,GAAA,EAAqB;AAChC,EAAA,OAAO,aAAa,GAAG,CAAA,CAAA,CAAA;AACzB;AAIO,IAAM,GAAA,GAAM;AAAA;AAAA,EAEjB,IAAA,CAAK,QAAgB,IAAA,EAAuB;AAC1C,IAAA,IAAI,cAAA,IAAkB,OAAA,CAAQ,IAAA,CAAK,IAAI,GAAG,CAAA,EAAG,GAAG,IAAI,CAAA;AAAA,EACtD,CAAA;AAAA;AAAA,EAGA,IAAA,CAAK,QAAgB,IAAA,EAAuB;AAC1C,IAAA,IAAI,cAAA,IAAkB,OAAA,CAAQ,IAAA,CAAK,IAAI,GAAG,CAAA,EAAG,GAAG,IAAI,CAAA;AAAA,EACtD,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAA,CAAK,QAAgB,IAAA,EAAuB;AAC1C,IAAA,OAAA,CAAQ,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG,GAAG,IAAI,CAAA;AAAA,EAChC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,KAAA,CACJ,GAAA,EACA,KAAA,EACA,QACA,EAAA,EACY;AACZ,IAAA,MAAM,KAAA,GAAQ,YAAY,GAAA,EAAI;AAC9B,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,EAAA,EAAG;AACxB,MAAA,MAAM,OAAA,GAAU,WAAA,CAAY,GAAA,EAAI,GAAI,KAAA;AACpC,MAAA,IAAI,gBAAe,EAAG;AACpB,QAAA,OAAA,CAAQ,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG,CAAA,EAAG,KAAK,CAAA,CAAA,EAAI,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAA,CAAI,CAAA;AAAA,MAC3D;AACA,MAAA,IAAI,UAAU,MAAA,EAAQ;AACpB,QAAA,OAAA,CAAQ,IAAA;AAAA,UACN,IAAI,GAAG,CAAA;AAAA,UACP,CAAA,EAAG,KAAK,CAAA,MAAA,EAAS,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA,KAAA,EAAQ,MAAM,CAAA,6DAAA,EACL,UAAA,CAAW,GAAG,CAAC,CAAA;AAAA,SAC7D;AAAA,MACF;AACA,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,OAAA,GAAU,WAAA,CAAY,GAAA,EAAI,GAAI,KAAA;AACpC,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,IAAI,GAAG,CAAA;AAAA,QACP,GAAG,KAAK,CAAA,cAAA,EAAiB,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA,GAAA,CAAA;AAAA,QAC3C;AAAA,OACF;AACA,MAAA,MAAM,GAAA;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,WAAW,GAAA,EAAqB;AACvC,EAAA,QAAQ,GAAA;AAAK,IACX,KAAK,OAAA;AACH,MAAA,OAAO,uEAAA;AAAA,IACT,KAAK,YAAA;AACH,MAAA,OAAO,mFAAA;AAAA,IACT,KAAK,WAAA;AACH,MAAA,OAAO,iFAAA;AAAA,IACT,KAAK,YAAA;AACH,MAAA,OAAO,iGAAA;AAAA,IACT;AACE,MAAA,OAAO,uEAAA;AAAA;AAEb;;;ACzFA,IAAM,KAAA,uBAAiD,GAAA,EAAI;AAE3D,SAAS,QAAA,CAAS,SAAuB,OAAA,EAA0B;AACjE,EAAA,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,OAAA,GAAU,QAAQ,MAAM,CAAA,CAAA;AAC/C;AAOO,SAAS,SAAA,CACd,OAAA,GAAwB,WAAA,EACxB,IAAA,GAAyB,EAAC,EACF;AAgBxB,EAAA,MAAM,GAAA,GAAM,UAAA;AACZ,EAAA,MAAM,cACJ,IAAA,CAAK,OAAA,KAAY,SACb,IAAA,CAAK,OAAA,GACL,IAAI,sBAAA,KAA2B,IAAA;AAErC,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,OAAA,EAAS,WAAW,CAAA;AACzC,EAAA,IAAI,KAAA,GAAQ,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AACzB,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,KAAA,GAAQ,WAAA,CAAY,SAAS,WAAW,CAAA;AACxC,IAAA,KAAA,CAAM,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,EACtB;AACA,EAAA,OAAO,KAAA;AACT;AAEA,eAAe,WAAA,CACb,SACA,WAAA,EACwB;AACxB,EAAA,OAAO,GAAA,CAAI,KAAA;AAAA,IAAM,YAAA;AAAA,IAAc,CAAA,MAAA,EAAS,OAAO,CAAA,WAAA,EAAc,WAAW,CAAA,CAAA,CAAA;AAAA,IAAK,GAAA;AAAA,IAAM,MACjF,gBAAA,CAAiB,OAAA,EAAS,WAAW;AAAA,GACvC;AACF;AAEA,eAAe,gBAAA,CACb,SACA,WAAA,EACwB;AACxB,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,OAAA,EAAS,WAAW,CAAA;AACzC,EAAA,MAAM,IAAA,GAAO,CAAA,EAAG,YAAA,EAAc,IAAI,OAAO,CAAA,CAAA;AAGzC,EAAA,MAAM,UAAA,GAAa,CAAA,EAAG,IAAI,CAAA,OAAA,EAAU,OAAO,CAAA,IAAA,CAAA;AAC3C,EAAA,GAAA,CAAI,IAAA,CAAK,YAAA,EAAc,CAAA,SAAA,EAAY,UAAU,CAAA,CAAE,CAAA;AAO/C,EAAA,IAAI,OAAO,UAAU,UAAA,EAAY;AAC/B,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,GAAO,MAAM,KAAA,CAAM,UAAA,EAAY,EAAE,MAAA,EAAQ,KAAA,EAAO,OAAA,EAAS,EAAE,KAAA,EAAO,WAAA,EAAY,EAAG,CAAA;AACvF,MAAA,IAAI,CAAC,IAAA,CAAK,EAAA,IAAM,IAAA,CAAK,WAAW,GAAA,EAAK;AACnC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,QAAQ,IAAA,CAAK,MAAM,IAAI,IAAA,CAAK,UAAU,gDAChC,IAAI,CAAA,+CAAA;AAAA,SACZ;AAAA,MACF;AAEA,MAAA,IAAI;AAAE,QAAA,MAAM,KAAK,WAAA,EAAY;AAAA,MAAG,CAAA,CAAA,MAAQ;AAAA,MAAe;AAAA,IACzD,SAAS,GAAA,EAAK;AACZ,MAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAChB,MAAA,MAAM,KAAA;AAAA,QACJ,CAAA,UAAA,EAAa,OAAO,CAAA,2BAAA,EAA8B,UAAU,CAAA,CAAA;AAAA,QAC5D;AAAA,OACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AAEF,IAAA,MAAM,WAAoB,MAAM;AAAA;AAAA,MAA0B;AAAA,KAAA;AAC1D,IAAA,IAAI,CAAC,QAAA,IAAY,OAAQ,QAAA,CAAiC,UAAU,UAAA,EAAY;AAC9E,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,UAAA,EAAa,UAAU,CAAA,qBAAA,CAAuB,CAAA;AAAA,IAChE;AACA,IAAA,GAAA,GAAM,QAAA;AAAA,EACR,SAAS,GAAA,EAAK;AACZ,IAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAChB,IAAA,MAAM,IAAA,GACJ,OAAA,KAAY,UAAA,GACR,CAAA,gJAAA,CAAA,GAEA,8CAA8C,IAAI,CAAA,2EAAA,CAAA;AAExD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,yBAAA,EAA4B,OAAO,CAAA,eAAA,EAAkB,UAAU,CAAA,EAAA,EAAK,IAAI,CAAA,iBAAA,EAClD,GAAA,CAAc,OAAA,IAAW,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,KAC5D;AAAA,EACF;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,OAAQ,MAAM,GAAA,CAAI,MAAM,SAAA,CAAU,IAAA,EAAM,WAAW,CAAC,CAAA;AAC1D,IAAA,MAAM,iBAAiB,IAAI,CAAA;AAC3B,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,GAAA,EAAK;AACZ,IAAA,KAAA,CAAM,OAAO,GAAG,CAAA;AAChB,IAAA,MAAM,MAAM,CAAA,4BAAA,EAA+B,OAAO,CAAA,mBAAA,EAAsB,WAAW,KAAK,GAAG,CAAA;AAAA,EAC7F;AACF;AAYA,eAAe,iBAAiB,IAAA,EAAoC;AAClE,EAAA,IAAI;AACF,IAAA,MAAM,WAAY,IAAA,CACf,gBAAA;AACH,IAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AAClC,MAAA,MAAM,KAAA,GAAS,KAAmC,YAAA,IAAgB,CAAA,CAAA;AAClE,MAAA,MAAM,SAAS,KAAK,CAAA;AAAA,IACtB;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAEA,SAAS,SAAA,CAAU,MAAc,WAAA,EAA+C;AAK9E,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,WAAW,CAAC,WAAA;AAAA,IACZ,UAAA,EAAY;AAAA,GACd;AACF;AAEA,SAAS,YAAA,GAAuB;AAK9B,EAAA,MAAM,QAAA,GACJ,OAAO,UAAA,KAAe,WAAA,GACjB,WAAgD,mBAAA,GACjD,MAAA;AACN,EAAA,IAAI,UAAU,OAAO,QAAA;AAWrB,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,GAAA,CAAI,iBAAA,EAAmB,MAAA,CAAA,IAAA,CAAY,GAAG,CAAA,CAAE,IAAA;AAAA,EACrD,CAAA,CAAA,MAAQ;AACN,IAAA,IAAI,OAAO,QAAA,KAAa,WAAA,IAAe,SAAS,QAAA,CAAS,UAAA,CAAW,MAAM,CAAA,EAAG;AAC3E,MAAA,OAAO,CAAA,EAAG,SAAS,MAAM,CAAA,MAAA,CAAA;AAAA,IAC3B;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AACF;AAEA,SAAS,KAAA,CAAM,SAAiB,GAAA,EAAqB;AACnD,EAAA,MAAM,QAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAE7D,EAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,WAAA,EAAc,OAAO,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA;AAC3C,EAAA,OAAO,IAAI,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,EAAA,EAAK,KAAA,IAAS,yCAAoC,CAAA,CAAE,CAAA;AACjF","file":"chunk-5DMTJVIU.js","sourcesContent":["/**\n * Debug + self-diagnosis helper.\n *\n * avbridge has a lot of async stages (probe → classify → libav load →\n * strategy.execute → decoder pump → cold-start gate → first paint) and\n * when something's slow or wrong the symptom — \"it hangs\", \"it stutters\",\n * \"it plays audio without video\" — is usually nowhere near the actual\n * cause. This module gives us two things:\n *\n * 1. **Gated verbose logging.** `dbg.info(tag, ...)` etc. are no-ops\n * unless the consumer sets `globalThis.AVBRIDGE_DEBUG = true` (or\n * uses the matching `?avbridge_debug` URL search param at dev time).\n * When enabled, every log is prefixed with `[avbridge:<tag>]` so the\n * console is filterable.\n *\n * 2. **Unconditional self-diagnosis.** `dbg.warnIf(cond, tag, ...)`\n * always fires when a suspicious condition is detected, even with\n * debug off. These are the things we *know* mean something is\n * broken or degraded and the user would want to know about — e.g.\n * the cold-start gate timing out, the decoder running slower than\n * realtime, a libav variant taking longer to load than any network\n * should take, >20% of packets getting rejected.\n *\n * The guiding principle: **if a symptom caused more than 10 minutes of\n * human debugging once, add a targeted warning so the next instance\n * self-identifies in the console.** This module is where those\n * warnings live.\n */\n\n/** Read the debug flag fresh on every call so it's runtime-toggleable. */\nfunction isDebugEnabled(): boolean {\n if (typeof globalThis === \"undefined\") return false;\n const g = globalThis as { AVBRIDGE_DEBUG?: unknown };\n if (g.AVBRIDGE_DEBUG === true) return true;\n // Convenience: if running in a browser with a `?avbridge_debug` search\n // param, flip the flag on automatically. Useful for demos and quick\n // user reproduction without editing code.\n if (typeof location !== \"undefined\" && typeof URLSearchParams !== \"undefined\") {\n try {\n const p = new URLSearchParams(location.search);\n if (p.has(\"avbridge_debug\")) {\n g.AVBRIDGE_DEBUG = true;\n return true;\n }\n } catch { /* ignore */ }\n }\n return false;\n}\n\nfunction fmt(tag: string): string {\n return `[avbridge:${tag}]`;\n}\n\n/* eslint-disable no-console */\n\nexport const dbg = {\n /** Verbose — only when debug is enabled. The hot-path normal case. */\n info(tag: string, ...args: unknown[]): void {\n if (isDebugEnabled()) console.info(fmt(tag), ...args);\n },\n\n /** Warning — only when debug is enabled. Non-fatal oddities. */\n warn(tag: string, ...args: unknown[]): void {\n if (isDebugEnabled()) console.warn(fmt(tag), ...args);\n },\n\n /**\n * Self-diagnosis warning. **Always** emits regardless of debug flag.\n * Use this only for conditions that mean something is actually wrong\n * or degraded — not for routine chatter.\n */\n diag(tag: string, ...args: unknown[]): void {\n console.warn(fmt(tag), ...args);\n },\n\n /**\n * Timing helper: wraps an async call and logs its elapsed time when\n * debug is on. The callback runs whether debug is on or off — this is\n * just for the `dbg.info` at the end.\n *\n * Also unconditionally fires `dbg.diag` if the elapsed time exceeds\n * `slowMs`, so \"the bootstrap took 8 seconds\" shows up even without\n * debug mode enabled.\n */\n async timed<T>(\n tag: string,\n label: string,\n slowMs: number,\n fn: () => Promise<T>,\n ): Promise<T> {\n const start = performance.now();\n try {\n const result = await fn();\n const elapsed = performance.now() - start;\n if (isDebugEnabled()) {\n console.info(fmt(tag), `${label} ${elapsed.toFixed(0)}ms`);\n }\n if (elapsed > slowMs) {\n console.warn(\n fmt(tag),\n `${label} took ${elapsed.toFixed(0)}ms (>${slowMs}ms expected) — ` +\n `this is unusually slow; possible causes: ${hintForTag(tag)}`,\n );\n }\n return result;\n } catch (err) {\n const elapsed = performance.now() - start;\n console.warn(\n fmt(tag),\n `${label} FAILED after ${elapsed.toFixed(0)}ms:`,\n err,\n );\n throw err;\n }\n },\n};\n\nfunction hintForTag(tag: string): string {\n switch (tag) {\n case \"probe\":\n return \"slow network (range request), large sniff window, or libav cold-start\";\n case \"libav-load\":\n return \"large .wasm download, misconfigured AVBRIDGE_LIBAV_BASE, or server-side MIME type\";\n case \"bootstrap\":\n return \"probe+classify+strategy-init chain; enable AVBRIDGE_DEBUG for a phase breakdown\";\n case \"cold-start\":\n return \"decoder is producing output slower than realtime — check framesDecoded in getDiagnostics()\";\n default:\n return \"unknown stage — enable globalThis.AVBRIDGE_DEBUG for more detail\";\n }\n}\n","/**\n * Lazy libav.js loader supporting multiple variants.\n *\n * avbridge recognises three libav variants:\n *\n * - **webcodecs** — npm `@libav.js/variant-webcodecs`, ~5 MB. Modern formats\n * only (mp4/mkv/webm/ogg/wav/...) — designed to bridge to WebCodecs.\n *\n * - **default** — npm `@libav.js/variant-default`, ~12 MB. Audio-only build\n * (Opus, FLAC, WAV) despite the name. Useful for audio fallback.\n *\n * - **avbridge** — a custom build produced by `scripts/build-libav.sh` and\n * landing in `vendor/libav/`. Includes the AVI/ASF/FLV/MKV demuxers plus\n * the legacy decoders (WMV3, MPEG-4 Part 2, MS-MPEG4 v1/2/3, VC-1, MPEG-1/2,\n * AC-3/E-AC-3, WMAv1/v2/Pro). This is the only variant that can read AVI;\n * the npm variants are intentionally minimal and ship none of the legacy\n * demuxers.\n *\n * Variant resolution always goes through a runtime URL + `/* @vite-ignore *\\/`\n * dynamic import. Static imports trigger Vite's optimized-deps pipeline,\n * which rewrites `import.meta.url` away from the real `dist/` directory and\n * breaks libav's sibling-binary loading.\n */\n\nimport { dbg } from \"../../util/debug.js\";\n\nexport type LibavVariant = \"webcodecs\" | \"default\" | \"avbridge\";\n\nexport interface LoadLibavOptions {\n /**\n * Force threading on/off for this load. If unspecified, defaults to\n * \"true if `crossOriginIsolated`, otherwise false\". Some libav.js code\n * paths (notably the cross-thread reader-device protocol used during\n * `avformat_find_stream_info` for AVI) are unreliable in threaded mode,\n * so probing forces this to `false` while decode keeps it default.\n */\n threads?: boolean;\n}\n\n// Cache key includes both variant and threading mode so probe and decode\n// can run different libav instances of the same variant.\nconst cache: Map<string, Promise<LibavInstance>> = new Map();\n\nfunction cacheKey(variant: LibavVariant, threads: boolean): string {\n return `${variant}:${threads ? \"thr\" : \"wasm\"}`;\n}\n\n/**\n * Load (and cache) a libav.js variant. Pass `\"webcodecs\"` for the small\n * default; pass `\"default\"` for the audio fallback; pass `\"avbridge\"` for the\n * custom build that supports AVI/WMV/legacy codecs.\n */\nexport function loadLibav(\n variant: LibavVariant = \"webcodecs\",\n opts: LoadLibavOptions = {},\n): Promise<LibavInstance> {\n // Threading is OFF by default. The threaded libav.js variant is too\n // fragile in practice for our usage:\n // - Probe (`avformat_find_stream_info` for AVI) throws an `undefined`\n // exception out of `ff_init_demuxer_file`, apparently due to the\n // cross-thread reader-device protocol racing with the main thread.\n // - Decode hits a `TypeError: Cannot read properties of undefined\n // (reading 'apply')` inside libav.js's own worker message handler\n // within seconds of starting — a bug in libav.js's threaded message\n // dispatch that we can't fix from outside.\n //\n // Performance work for the fallback strategy needs to come from elsewhere\n // (WASM SIMD, OffscreenCanvas, larger decode batches) instead of libav's\n // pthreads. Threading can still be force-enabled with\n // `globalThis.AVBRIDGE_LIBAV_THREADS = true` for testing if libav.js fixes\n // those bugs in a future release.\n const env = globalThis as { AVBRIDGE_LIBAV_THREADS?: boolean };\n const wantThreads =\n opts.threads !== undefined\n ? opts.threads\n : env.AVBRIDGE_LIBAV_THREADS === true;\n\n const key = cacheKey(variant, wantThreads);\n let entry = cache.get(key);\n if (!entry) {\n entry = loadVariant(variant, wantThreads);\n cache.set(key, entry);\n }\n return entry;\n}\n\nasync function loadVariant(\n variant: LibavVariant,\n wantThreads: boolean,\n): Promise<LibavInstance> {\n return dbg.timed(\"libav-load\", `load \"${variant}\" (threads=${wantThreads})`, 5000, () =>\n loadVariantInner(variant, wantThreads),\n );\n}\n\nasync function loadVariantInner(\n variant: LibavVariant,\n wantThreads: boolean,\n): Promise<LibavInstance> {\n const key = cacheKey(variant, wantThreads);\n const base = `${libavBaseUrl()}/${variant}`;\n // The custom variant is named `libav-avbridge.mjs`; the npm variants follow\n // the same convention (`libav-webcodecs.mjs`, `libav-default.mjs`).\n const variantUrl = `${base}/libav-${variant}.mjs`;\n dbg.info(\"libav-load\", `fetching ${variantUrl}`);\n\n // Preflight HEAD-ish check: issue a bytes=0-0 range request so a missing\n // file fails fast with a clear error instead of hanging deep inside the\n // dynamic import or inside libav's own WASM instantiation. Surfaces the\n // most common mistake (\"libav base path is wrong\") in <100 ms instead of\n // an indeterminate stall.\n if (typeof fetch === \"function\") {\n try {\n const head = await fetch(variantUrl, { method: \"GET\", headers: { Range: \"bytes=0-0\" } });\n if (!head.ok && head.status !== 206) {\n throw new Error(\n `HTTP ${head.status} ${head.statusText} — check that libav files are served ` +\n `at ${base}/ (override via globalThis.AVBRIDGE_LIBAV_BASE)`,\n );\n }\n // Drain the tiny response so the connection can be reused.\n try { await head.arrayBuffer(); } catch { /* ignore */ }\n } catch (err) {\n cache.delete(key);\n throw chain(\n `libav.js \"${variant}\" variant not reachable at ${variantUrl}`,\n err,\n );\n }\n }\n\n let mod: LoadedVariant;\n try {\n // @ts-ignore runtime URL\n const imported: unknown = await import(/* @vite-ignore */ variantUrl);\n if (!imported || typeof (imported as { LibAV?: unknown }).LibAV !== \"function\") {\n throw new Error(`module at ${variantUrl} did not export LibAV`);\n }\n mod = imported as LoadedVariant;\n } catch (err) {\n cache.delete(key);\n const hint =\n variant === \"avbridge\"\n ? `The \"avbridge\" variant is a custom local build. Run \\`./scripts/build-libav.sh\\` ` +\n `to produce it (requires Emscripten; ~15-30 min the first time).`\n : `Make sure the variant files are present at ${base}/ (set ` +\n `globalThis.AVBRIDGE_LIBAV_BASE to override the default lookup path).`;\n throw new Error(\n `failed to load libav.js \"${variant}\" variant from ${variantUrl}. ${hint} ` +\n `Original error: ${(err as Error).message || String(err)}`,\n );\n }\n\n try {\n const inst = (await mod.LibAV(buildOpts(base, wantThreads))) as LibavInstance;\n await silenceLibavLogs(inst);\n return inst;\n } catch (err) {\n cache.delete(key);\n throw chain(`LibAV() factory failed for \"${variant}\" variant (threads=${wantThreads})`, err);\n }\n}\n\n/**\n * Lower libav's internal log level so the console doesn't get flooded with\n * `[mp3 @ ...] Header missing` and `Video uses a non-standard and wasteful\n * way to store B-frames` warnings on every legacy file. We still get any\n * actual JS-level errors via the normal Error path; this only affects\n * libav's own ffmpeg log channel.\n *\n * AV_LOG_QUIET = -8 (no output at all). If you want to keep fatal errors,\n * use AV_LOG_FATAL = 8 instead.\n */\nasync function silenceLibavLogs(inst: LibavInstance): Promise<void> {\n try {\n const setLevel = (inst as { av_log_set_level?: (n: number) => Promise<void> })\n .av_log_set_level;\n if (typeof setLevel === \"function\") {\n const quiet = (inst as { AV_LOG_QUIET?: number }).AV_LOG_QUIET ?? -8;\n await setLevel(quiet);\n }\n } catch {\n /* not fatal — verbose logs are noise, not an error */\n }\n}\n\nfunction buildOpts(base: string, wantThreads: boolean): Record<string, unknown> {\n // The wantThreads decision is made by `loadLibav()` so callers (probe,\n // decoder) can override per-load. Decode wants pthreads for speed; probe\n // forces them off because libav.js's cross-thread reader-device protocol\n // is unreliable mid-`avformat_find_stream_info` for some AVI files.\n return {\n base,\n nothreads: !wantThreads,\n yesthreads: wantThreads,\n };\n}\n\nfunction libavBaseUrl(): string {\n // Consumer override — the documented \"LGPL replaceability\" hook.\n // Setting `globalThis.AVBRIDGE_LIBAV_BASE = \"/my/path\"` lets anyone swap\n // in a different libav build (custom fragments, security patches, etc.)\n // without rebuilding avbridge.\n const override =\n typeof globalThis !== \"undefined\"\n ? (globalThis as { AVBRIDGE_LIBAV_BASE?: string }).AVBRIDGE_LIBAV_BASE\n : undefined;\n if (override) return override;\n\n // Default: resolve relative to this module's URL. When avbridge is installed\n // under `node_modules/avbridge/`, this module lives at `dist/chunk-*.js` (or\n // `dist/element-browser.js` for the browser entry) and `../vendor/libav`\n // resolves to `node_modules/avbridge/vendor/libav`, where the build step\n // vendored every variant's binaries. That's the zero-config path.\n //\n // `import.meta.url` throws in some synthetic environments (CJS tests, some\n // SSR evaluators). If it fails, fall back to the legacy `/libav` path so\n // consumers who relied on the pre-2.1 behavior still work.\n try {\n return new URL(\"../vendor/libav\", import.meta.url).href;\n } catch {\n if (typeof location !== \"undefined\" && location.protocol.startsWith(\"http\")) {\n return `${location.origin}/libav`;\n }\n return \"/libav\";\n }\n}\n\nfunction chain(message: string, err: unknown): Error {\n const inner = err instanceof Error ? err.message : String(err);\n // eslint-disable-next-line no-console\n console.error(`[avbridge] ${message}:`, err);\n return new Error(`${message}: ${inner || \"(no message — see browser console)\"}`);\n}\n\ninterface LoadedVariant {\n LibAV: (opts?: Record<string, unknown>) => Promise<Record<string, unknown>>;\n}\n\n/** Loose structural type — the AVI probe and the fallback decoder add fields. */\nexport type LibavInstance = Record<string, unknown> & {\n mkreadaheadfile(name: string, blob: Blob): Promise<void>;\n unlinkreadaheadfile(name: string): Promise<void>;\n};\n"]}
@@ -1,5 +1,5 @@
1
- import { normalizeSource, sniffNormalizedSource } from './chunk-PQTZS7OA.js';
2
- import { loadLibav } from './chunk-EJH67FXG.js';
1
+ import { normalizeSource, sniffNormalizedSource } from './chunk-ILKDNBSE.js';
2
+ import { dbg, loadLibav } from './chunk-5DMTJVIU.js';
3
3
  import { pickLibavVariant } from './chunk-J5MCMN3S.js';
4
4
 
5
5
  // src/probe/mediabunny.ts
@@ -190,7 +190,7 @@ async function probe(source) {
190
190
  mediabunnyErr.message
191
191
  );
192
192
  try {
193
- const { probeWithLibav } = await import('./avi-V6HYQVR2.js');
193
+ const { probeWithLibav } = await import('./avi-GCGM7OJI.js');
194
194
  return await probeWithLibav(normalized, sniffed);
195
195
  } catch (libavErr) {
196
196
  const mbMsg = mediabunnyErr.message || String(mediabunnyErr);
@@ -202,7 +202,7 @@ async function probe(source) {
202
202
  }
203
203
  }
204
204
  try {
205
- const { probeWithLibav } = await import('./avi-V6HYQVR2.js');
205
+ const { probeWithLibav } = await import('./avi-GCGM7OJI.js');
206
206
  return await probeWithLibav(normalized, sniffed);
207
207
  } catch (err) {
208
208
  const inner = err instanceof Error ? err.message : String(err);
@@ -288,8 +288,29 @@ var NATIVE_AUDIO_CODECS = /* @__PURE__ */ new Set([
288
288
  "vorbis",
289
289
  "flac"
290
290
  ]);
291
- var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set(["wmv3", "vc1", "mpeg4", "rv40", "mpeg2", "mpeg1", "theora"]);
292
- var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set(["wmav2", "wmapro", "ac3", "eac3"]);
291
+ var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set([
292
+ "wmv3",
293
+ "vc1",
294
+ "mpeg4",
295
+ "rv10",
296
+ "rv20",
297
+ "rv30",
298
+ "rv40",
299
+ "mpeg2",
300
+ "mpeg1",
301
+ "theora"
302
+ ]);
303
+ var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set([
304
+ "wmav2",
305
+ "wmapro",
306
+ "ac3",
307
+ "eac3",
308
+ "cook",
309
+ "ra_144",
310
+ "ra_288",
311
+ "sipr",
312
+ "atrac3"
313
+ ]);
293
314
  var NATIVE_CONTAINERS = /* @__PURE__ */ new Set([
294
315
  "mp4",
295
316
  "mov",
@@ -1923,7 +1944,7 @@ async function loadBridge() {
1923
1944
  var READY_AUDIO_BUFFER_SECONDS = 0.3;
1924
1945
  var READY_TIMEOUT_SECONDS = 10;
1925
1946
  async function createHybridSession(ctx, target) {
1926
- const { normalizeSource: normalizeSource2 } = await import('./source-ZFS4H7J3.js');
1947
+ const { normalizeSource: normalizeSource2 } = await import('./source-FFZ7TW2B.js');
1927
1948
  const source = await normalizeSource2(ctx.source);
1928
1949
  const fps = ctx.videoTracks[0]?.fps ?? 30;
1929
1950
  const audio = new AudioOutput();
@@ -2090,6 +2111,9 @@ async function startDecoder(opts) {
2090
2111
  let packetsRead = 0;
2091
2112
  let videoFramesDecoded = 0;
2092
2113
  let audioFramesDecoded = 0;
2114
+ let watchdogFirstFrameMs = 0;
2115
+ let watchdogSlowSinceMs = 0;
2116
+ let watchdogWarned = false;
2093
2117
  let syntheticVideoUs = 0;
2094
2118
  let syntheticAudioUs = 0;
2095
2119
  const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
@@ -2101,7 +2125,7 @@ async function startDecoder(opts) {
2101
2125
  let packets;
2102
2126
  try {
2103
2127
  [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
2104
- limit: 16 * 1024
2128
+ limit: 64 * 1024
2105
2129
  });
2106
2130
  } catch (err) {
2107
2131
  console.error("[avbridge] ff_read_frame_multi failed:", err);
@@ -2118,6 +2142,28 @@ async function startDecoder(opts) {
2118
2142
  await decodeAudioBatch(audioPackets, myToken);
2119
2143
  }
2120
2144
  packetsRead += (videoPackets?.length ?? 0) + (audioPackets?.length ?? 0);
2145
+ if (videoFramesDecoded > 0) {
2146
+ if (watchdogFirstFrameMs === 0) {
2147
+ watchdogFirstFrameMs = performance.now();
2148
+ }
2149
+ const elapsedSinceFirst = (performance.now() - watchdogFirstFrameMs) / 1e3;
2150
+ if (elapsedSinceFirst > 1 && !watchdogWarned) {
2151
+ const expectedFrames = elapsedSinceFirst * videoFps;
2152
+ const ratio = videoFramesDecoded / expectedFrames;
2153
+ if (ratio < 0.6) {
2154
+ if (watchdogSlowSinceMs === 0) watchdogSlowSinceMs = performance.now();
2155
+ if ((performance.now() - watchdogSlowSinceMs) / 1e3 > 5) {
2156
+ watchdogWarned = true;
2157
+ console.warn(
2158
+ "[avbridge:decode-rate]",
2159
+ `decoder is running slower than realtime: ${videoFramesDecoded} frames in ${elapsedSinceFirst.toFixed(1)}s (${(videoFramesDecoded / elapsedSinceFirst).toFixed(1)} fps vs ${videoFps} fps source \u2014 ${(ratio * 100).toFixed(0)}% of realtime). Playback will stutter. Typical causes: software decode of a codec with no WebCodecs support (rv40, mpeg4 @ 720p+, wmv3), or a resolution too large for single-threaded WASM to keep up with.`
2160
+ );
2161
+ }
2162
+ } else {
2163
+ watchdogSlowSinceMs = 0;
2164
+ }
2165
+ }
2166
+ }
2121
2167
  while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
2122
2168
  await new Promise((r) => setTimeout(r, 50));
2123
2169
  }
@@ -2444,10 +2490,10 @@ async function loadBridge2() {
2444
2490
  }
2445
2491
 
2446
2492
  // src/strategies/fallback/index.ts
2447
- var READY_AUDIO_BUFFER_SECONDS2 = 0.3;
2448
- var READY_TIMEOUT_SECONDS2 = 10;
2493
+ var READY_AUDIO_BUFFER_SECONDS2 = 0.04;
2494
+ var READY_TIMEOUT_SECONDS2 = 3;
2449
2495
  async function createFallbackSession(ctx, target) {
2450
- const { normalizeSource: normalizeSource2 } = await import('./source-ZFS4H7J3.js');
2496
+ const { normalizeSource: normalizeSource2 } = await import('./source-FFZ7TW2B.js');
2451
2497
  const source = await normalizeSource2(ctx.source);
2452
2498
  const fps = ctx.videoTracks[0]?.fps ?? 30;
2453
2499
  const audio = new AudioOutput();
@@ -2481,12 +2527,26 @@ async function createFallbackSession(ctx, target) {
2481
2527
  }
2482
2528
  async function waitForBuffer() {
2483
2529
  const start = performance.now();
2530
+ dbg.info(
2531
+ "cold-start",
2532
+ `gate entry: need audio >= ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms + 1 frame`
2533
+ );
2484
2534
  while (true) {
2485
- const audioReady = audio.isNoAudio() || audio.bufferAhead() >= READY_AUDIO_BUFFER_SECONDS2;
2486
- if (audioReady && renderer.hasFrames()) {
2535
+ const audioAhead = audio.isNoAudio() ? Infinity : audio.bufferAhead();
2536
+ const audioReady = audio.isNoAudio() || audioAhead >= READY_AUDIO_BUFFER_SECONDS2;
2537
+ const hasFrames = renderer.hasFrames();
2538
+ if (audioReady && hasFrames) {
2539
+ dbg.info(
2540
+ "cold-start",
2541
+ `gate satisfied in ${(performance.now() - start).toFixed(0)}ms (audio=${(audioAhead * 1e3).toFixed(0)}ms, frames=${renderer.queueDepth()})`
2542
+ );
2487
2543
  return;
2488
2544
  }
2489
2545
  if ((performance.now() - start) / 1e3 > READY_TIMEOUT_SECONDS2) {
2546
+ dbg.diag(
2547
+ "cold-start",
2548
+ `gate TIMEOUT after ${READY_TIMEOUT_SECONDS2}s \u2014 audio=${(audioAhead * 1e3).toFixed(0)}ms (needed ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms), frames=${renderer.queueDepth()} (needed \u22651). Software decoder is producing output slower than realtime \u2014 playback will stutter. Check getDiagnostics().runtime for the decode rate.`
2549
+ );
2490
2550
  return;
2491
2551
  }
2492
2552
  await new Promise((r) => setTimeout(r, 50));
@@ -2701,8 +2761,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
2701
2761
  return player;
2702
2762
  }
2703
2763
  async bootstrap() {
2764
+ const bootstrapStart = performance.now();
2704
2765
  try {
2705
- const ctx = await probe(this.options.source);
2766
+ dbg.info("bootstrap", "start");
2767
+ const ctx = await dbg.timed("probe", "probe", 3e3, () => probe(this.options.source));
2768
+ dbg.info(
2769
+ "probe",
2770
+ `container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
2771
+ );
2706
2772
  this.diag.recordProbe(ctx);
2707
2773
  this.mediaContext = ctx;
2708
2774
  if (this.options.subtitles) {
@@ -2728,6 +2794,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
2728
2794
  }
2729
2795
  }
2730
2796
  const decision = this.options.initialStrategy ? buildInitialDecision(this.options.initialStrategy, ctx) : classifyContext(ctx);
2797
+ dbg.info(
2798
+ "classify",
2799
+ `strategy=${decision.strategy} class=${decision.class} reason="${decision.reason}"` + (decision.fallbackChain ? ` fallback=${decision.fallbackChain.join("\u2192")}` : "")
2800
+ );
2731
2801
  this.classification = decision;
2732
2802
  this.diag.recordClassification(decision);
2733
2803
  this.emitter.emitSticky("strategy", {
@@ -2753,6 +2823,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
2753
2823
  this.startTimeupdateLoop();
2754
2824
  this.options.target.addEventListener("ended", () => this.emitter.emit("ended", void 0));
2755
2825
  this.emitter.emitSticky("ready", void 0);
2826
+ const bootstrapElapsed = performance.now() - bootstrapStart;
2827
+ dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
2828
+ if (bootstrapElapsed > 5e3) {
2829
+ console.warn(
2830
+ "[avbridge:bootstrap]",
2831
+ `total bootstrap time ${bootstrapElapsed.toFixed(0)}ms \u2014 unusually slow. Enable globalThis.AVBRIDGE_DEBUG for a per-phase breakdown.`
2832
+ );
2833
+ }
2756
2834
  } catch (err) {
2757
2835
  const e = err instanceof Error ? err : new Error(String(err));
2758
2836
  this.diag.recordError(e);
@@ -3077,5 +3155,5 @@ function defaultFallbackChain(strategy) {
3077
3155
  }
3078
3156
 
3079
3157
  export { UnifiedPlayer, avbridgeAudioToMediabunny, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, classifyContext, createPlayer, probe, srtToVtt };
3080
- //# sourceMappingURL=chunk-3AUGRKPY.js.map
3081
- //# sourceMappingURL=chunk-3AUGRKPY.js.map
3158
+ //# sourceMappingURL=chunk-C5VA5U5O.js.map
3159
+ //# sourceMappingURL=chunk-C5VA5U5O.js.map