avbridge 2.1.2 → 2.2.1

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 (67) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +98 -71
  3. package/dist/{avi-GNTV5ZOH.cjs → avi-6SJLWIWW.cjs} +19 -4
  4. package/dist/avi-6SJLWIWW.cjs.map +1 -0
  5. package/dist/{avi-V6HYQVR2.js → avi-GCGM7OJI.js} +18 -3
  6. package/dist/avi-GCGM7OJI.js.map +1 -0
  7. package/dist/{chunk-EJH67FXG.js → chunk-5DMTJVIU.js} +99 -3
  8. package/dist/chunk-5DMTJVIU.js.map +1 -0
  9. package/dist/{chunk-3AUGRKPY.js → chunk-DMWARSEF.js} +160 -27
  10. package/dist/chunk-DMWARSEF.js.map +1 -0
  11. package/dist/{chunk-JQH6D4OE.cjs → chunk-G4APZMCP.cjs} +100 -3
  12. package/dist/chunk-G4APZMCP.cjs.map +1 -0
  13. package/dist/{chunk-Y5FYF5KG.cjs → chunk-HZLQNKFN.cjs} +5 -2
  14. package/dist/chunk-HZLQNKFN.cjs.map +1 -0
  15. package/dist/{chunk-PQTZS7OA.js → chunk-ILKDNBSE.js} +5 -2
  16. package/dist/chunk-ILKDNBSE.js.map +1 -0
  17. package/dist/{chunk-DPVIOYGC.cjs → chunk-UF2N5L63.cjs} +164 -31
  18. package/dist/chunk-UF2N5L63.cjs.map +1 -0
  19. package/dist/element-browser.js +276 -21
  20. package/dist/element-browser.js.map +1 -1
  21. package/dist/element.cjs +4 -4
  22. package/dist/element.d.cts +1 -1
  23. package/dist/element.d.ts +1 -1
  24. package/dist/element.js +3 -3
  25. package/dist/index.cjs +18 -18
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +5 -5
  29. package/dist/libav-loader-27RDIN2I.js +3 -0
  30. package/dist/{libav-loader-XKH2TKUW.js.map → libav-loader-27RDIN2I.js.map} +1 -1
  31. package/dist/libav-loader-IV4AJ2HW.cjs +12 -0
  32. package/dist/{libav-loader-6APXVNIV.cjs.map → libav-loader-IV4AJ2HW.cjs.map} +1 -1
  33. package/dist/{player-BdtUG4rh.d.cts → player-U2NPmFvA.d.cts} +4 -3
  34. package/dist/{player-BdtUG4rh.d.ts → player-U2NPmFvA.d.ts} +4 -3
  35. package/dist/source-CN43EI7Z.cjs +28 -0
  36. package/dist/{source-SC6ZEQYR.cjs.map → source-CN43EI7Z.cjs.map} +1 -1
  37. package/dist/source-FFZ7TW2B.js +3 -0
  38. package/dist/{source-ZFS4H7J3.js.map → source-FFZ7TW2B.js.map} +1 -1
  39. package/package.json +1 -1
  40. package/src/classify/rules.ts +9 -2
  41. package/src/player.ts +46 -17
  42. package/src/probe/avi.ts +8 -1
  43. package/src/strategies/fallback/audio-output.ts +25 -3
  44. package/src/strategies/fallback/decoder.ts +96 -8
  45. package/src/strategies/fallback/index.ts +98 -6
  46. package/src/strategies/fallback/libav-loader.ts +12 -0
  47. package/src/strategies/fallback/video-renderer.ts +5 -1
  48. package/src/strategies/hybrid/index.ts +9 -1
  49. package/src/strategies/remux/index.ts +13 -1
  50. package/src/strategies/remux/pipeline.ts +6 -0
  51. package/src/types.ts +10 -1
  52. package/src/util/debug.ts +131 -0
  53. package/src/util/source.ts +4 -0
  54. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  55. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  56. package/dist/avi-GNTV5ZOH.cjs.map +0 -1
  57. package/dist/avi-V6HYQVR2.js.map +0 -1
  58. package/dist/chunk-3AUGRKPY.js.map +0 -1
  59. package/dist/chunk-DPVIOYGC.cjs.map +0 -1
  60. package/dist/chunk-EJH67FXG.js.map +0 -1
  61. package/dist/chunk-JQH6D4OE.cjs.map +0 -1
  62. package/dist/chunk-PQTZS7OA.js.map +0 -1
  63. package/dist/chunk-Y5FYF5KG.cjs.map +0 -1
  64. package/dist/libav-loader-6APXVNIV.cjs +0 -12
  65. package/dist/libav-loader-XKH2TKUW.js +0 -3
  66. package/dist/source-SC6ZEQYR.cjs +0 -28
  67. package/dist/source-ZFS4H7J3.js +0 -3
@@ -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":"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","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",
@@ -1049,6 +1070,10 @@ async function createRemuxPipeline(ctx, video) {
1049
1070
  console.error("[avbridge] remux pipeline reseek failed:", err);
1050
1071
  });
1051
1072
  },
1073
+ setAutoPlay(autoPlay) {
1074
+ pendingAutoPlay = autoPlay;
1075
+ if (sink) sink.setPlayOnSeek(autoPlay);
1076
+ },
1052
1077
  async destroy() {
1053
1078
  destroyed = true;
1054
1079
  pumpToken++;
@@ -1089,7 +1114,11 @@ async function createRemuxSession(context, video) {
1089
1114
  await pipeline.start(video.currentTime || 0, true);
1090
1115
  return;
1091
1116
  }
1092
- await video.play();
1117
+ pipeline.setAutoPlay(true);
1118
+ try {
1119
+ await video.play();
1120
+ } catch {
1121
+ }
1093
1122
  },
1094
1123
  pause() {
1095
1124
  wantPlay = false;
@@ -1137,7 +1166,7 @@ var VideoRenderer = class {
1137
1166
  this.resolveFirstFrame = resolve;
1138
1167
  });
1139
1168
  this.canvas = document.createElement("canvas");
1140
- this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;";
1169
+ this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;object-fit:contain;";
1141
1170
  const parent = target.parentElement ?? target.parentNode;
1142
1171
  if (parent && parent instanceof HTMLElement) {
1143
1172
  if (getComputedStyle(parent).position === "static") {
@@ -1379,9 +1408,13 @@ var AudioOutput = class {
1379
1408
  const node = this.ctx.createBufferSource();
1380
1409
  node.buffer = buffer;
1381
1410
  node.connect(this.gain);
1382
- const ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
1383
- const safeStart = Math.max(ctxStart, this.ctx.currentTime);
1384
- node.start(safeStart);
1411
+ let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
1412
+ if (ctxStart < this.ctx.currentTime) {
1413
+ this.ctxTimeAtAnchor = this.ctx.currentTime;
1414
+ this.mediaTimeOfAnchor = this.mediaTimeOfNext;
1415
+ ctxStart = this.ctx.currentTime;
1416
+ }
1417
+ node.start(ctxStart);
1385
1418
  this.mediaTimeOfNext += frameCount / sampleRate;
1386
1419
  this.framesScheduled++;
1387
1420
  }
@@ -1923,7 +1956,7 @@ async function loadBridge() {
1923
1956
  var READY_AUDIO_BUFFER_SECONDS = 0.3;
1924
1957
  var READY_TIMEOUT_SECONDS = 10;
1925
1958
  async function createHybridSession(ctx, target) {
1926
- const { normalizeSource: normalizeSource2 } = await import('./source-ZFS4H7J3.js');
1959
+ const { normalizeSource: normalizeSource2 } = await import('./source-FFZ7TW2B.js');
1927
1960
  const source = await normalizeSource2(ctx.source);
1928
1961
  const fps = ctx.videoTracks[0]?.fps ?? 30;
1929
1962
  const audio = new AudioOutput();
@@ -1949,6 +1982,10 @@ async function createHybridSession(ctx, target) {
1949
1982
  void doSeek(v);
1950
1983
  }
1951
1984
  });
1985
+ Object.defineProperty(target, "paused", {
1986
+ configurable: true,
1987
+ get: () => !audio.isPlaying()
1988
+ });
1952
1989
  if (ctx.duration && Number.isFinite(ctx.duration)) {
1953
1990
  Object.defineProperty(target, "duration", {
1954
1991
  configurable: true,
@@ -2013,6 +2050,7 @@ async function createHybridSession(ctx, target) {
2013
2050
  try {
2014
2051
  delete target.currentTime;
2015
2052
  delete target.duration;
2053
+ delete target.paused;
2016
2054
  } catch {
2017
2055
  }
2018
2056
  },
@@ -2090,6 +2128,10 @@ async function startDecoder(opts) {
2090
2128
  let packetsRead = 0;
2091
2129
  let videoFramesDecoded = 0;
2092
2130
  let audioFramesDecoded = 0;
2131
+ let watchdogFirstFrameMs = 0;
2132
+ let watchdogSlowSinceMs = 0;
2133
+ let watchdogSlowWarned = false;
2134
+ let watchdogOverflowWarned = false;
2093
2135
  let syntheticVideoUs = 0;
2094
2136
  let syntheticAudioUs = 0;
2095
2137
  const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
@@ -2110,14 +2152,47 @@ async function startDecoder(opts) {
2110
2152
  if (myToken !== pumpToken || destroyed) return;
2111
2153
  const videoPackets = videoStream ? packets[videoStream.index] : void 0;
2112
2154
  const audioPackets = audioStream ? packets[audioStream.index] : void 0;
2113
- if (videoDec && videoPackets && videoPackets.length > 0) {
2114
- await decodeVideoBatch(videoPackets, myToken);
2115
- }
2116
- if (myToken !== pumpToken || destroyed) return;
2117
2155
  if (audioDec && audioPackets && audioPackets.length > 0) {
2118
2156
  await decodeAudioBatch(audioPackets, myToken);
2119
2157
  }
2158
+ if (myToken !== pumpToken || destroyed) return;
2159
+ if (videoDec && videoPackets && videoPackets.length > 0) {
2160
+ await decodeVideoBatch(videoPackets, myToken);
2161
+ }
2120
2162
  packetsRead += (videoPackets?.length ?? 0) + (audioPackets?.length ?? 0);
2163
+ if (videoFramesDecoded > 0) {
2164
+ if (watchdogFirstFrameMs === 0) {
2165
+ watchdogFirstFrameMs = performance.now();
2166
+ }
2167
+ const elapsedSinceFirst = (performance.now() - watchdogFirstFrameMs) / 1e3;
2168
+ if (elapsedSinceFirst > 1 && !watchdogSlowWarned) {
2169
+ const expectedFrames = elapsedSinceFirst * videoFps;
2170
+ const ratio = videoFramesDecoded / expectedFrames;
2171
+ if (ratio < 0.6) {
2172
+ if (watchdogSlowSinceMs === 0) watchdogSlowSinceMs = performance.now();
2173
+ if ((performance.now() - watchdogSlowSinceMs) / 1e3 > 5) {
2174
+ watchdogSlowWarned = true;
2175
+ console.warn(
2176
+ "[avbridge:decode-rate]",
2177
+ `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.`
2178
+ );
2179
+ }
2180
+ } else {
2181
+ watchdogSlowSinceMs = 0;
2182
+ }
2183
+ }
2184
+ if (!watchdogOverflowWarned && videoFramesDecoded > 100) {
2185
+ const rendererStats = opts.renderer.stats();
2186
+ const overflow = rendererStats.framesDroppedOverflow ?? 0;
2187
+ if (overflow / videoFramesDecoded > 0.1) {
2188
+ watchdogOverflowWarned = true;
2189
+ console.warn(
2190
+ "[avbridge:overflow-drop]",
2191
+ `renderer is dropping ${overflow}/${videoFramesDecoded} frames (${(overflow / videoFramesDecoded * 100).toFixed(0)}%) because the decoder is producing bursts faster than the canvas can drain. Symptom: choppy playback despite decoder keeping up on average. Fix would be smaller read batches in the pump loop or a lower queueHighWater cap \u2014 see src/strategies/fallback/decoder.ts.`
2192
+ );
2193
+ }
2194
+ }
2195
+ }
2121
2196
  while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
2122
2197
  await new Promise((r) => setTimeout(r, 50));
2123
2198
  }
@@ -2444,10 +2519,10 @@ async function loadBridge2() {
2444
2519
  }
2445
2520
 
2446
2521
  // src/strategies/fallback/index.ts
2447
- var READY_AUDIO_BUFFER_SECONDS2 = 0.3;
2448
- var READY_TIMEOUT_SECONDS2 = 10;
2522
+ var READY_AUDIO_BUFFER_SECONDS2 = 0.04;
2523
+ var READY_TIMEOUT_SECONDS2 = 3;
2449
2524
  async function createFallbackSession(ctx, target) {
2450
- const { normalizeSource: normalizeSource2 } = await import('./source-ZFS4H7J3.js');
2525
+ const { normalizeSource: normalizeSource2 } = await import('./source-FFZ7TW2B.js');
2451
2526
  const source = await normalizeSource2(ctx.source);
2452
2527
  const fps = ctx.videoTracks[0]?.fps ?? 30;
2453
2528
  const audio = new AudioOutput();
@@ -2473,6 +2548,10 @@ async function createFallbackSession(ctx, target) {
2473
2548
  void doSeek(v);
2474
2549
  }
2475
2550
  });
2551
+ Object.defineProperty(target, "paused", {
2552
+ configurable: true,
2553
+ get: () => !audio.isPlaying()
2554
+ });
2476
2555
  if (ctx.duration && Number.isFinite(ctx.duration)) {
2477
2556
  Object.defineProperty(target, "duration", {
2478
2557
  configurable: true,
@@ -2481,12 +2560,36 @@ async function createFallbackSession(ctx, target) {
2481
2560
  }
2482
2561
  async function waitForBuffer() {
2483
2562
  const start = performance.now();
2563
+ let firstFrameAtMs = 0;
2564
+ dbg.info(
2565
+ "cold-start",
2566
+ `gate entry: want audio \u2265 ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms + 1 frame`
2567
+ );
2484
2568
  while (true) {
2485
- const audioReady = audio.isNoAudio() || audio.bufferAhead() >= READY_AUDIO_BUFFER_SECONDS2;
2486
- if (audioReady && renderer.hasFrames()) {
2569
+ const audioAhead = audio.isNoAudio() ? Infinity : audio.bufferAhead();
2570
+ const audioReady = audio.isNoAudio() || audioAhead >= READY_AUDIO_BUFFER_SECONDS2;
2571
+ const hasFrames = renderer.hasFrames();
2572
+ const nowMs = performance.now();
2573
+ if (hasFrames && firstFrameAtMs === 0) firstFrameAtMs = nowMs;
2574
+ if (audioReady && hasFrames) {
2575
+ dbg.info(
2576
+ "cold-start",
2577
+ `gate satisfied in ${(nowMs - start).toFixed(0)}ms (audio=${(audioAhead * 1e3).toFixed(0)}ms, frames=${renderer.queueDepth()})`
2578
+ );
2487
2579
  return;
2488
2580
  }
2489
- if ((performance.now() - start) / 1e3 > READY_TIMEOUT_SECONDS2) {
2581
+ if (hasFrames && firstFrameAtMs > 0 && nowMs - firstFrameAtMs >= 500) {
2582
+ dbg.info(
2583
+ "cold-start",
2584
+ `gate released on video-only grace at ${(nowMs - start).toFixed(0)}ms (frames=${renderer.queueDepth()}, audio=${(audioAhead * 1e3).toFixed(0)}ms \u2014 demuxer hasn't delivered audio packets yet, starting anyway and letting the audio scheduler catch up at its media-time anchor)`
2585
+ );
2586
+ return;
2587
+ }
2588
+ if ((nowMs - start) / 1e3 > READY_TIMEOUT_SECONDS2) {
2589
+ dbg.diag(
2590
+ "cold-start",
2591
+ `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). Decoder produced nothing in ${READY_TIMEOUT_SECONDS2}s \u2014 either a corrupt source, a missing codec, or WASM is catastrophically slow on this file. Check getDiagnostics().runtime for decode counters.`
2592
+ );
2490
2593
  return;
2491
2594
  }
2492
2595
  await new Promise((r) => setTimeout(r, 50));
@@ -2534,6 +2637,7 @@ async function createFallbackSession(ctx, target) {
2534
2637
  try {
2535
2638
  delete target.currentTime;
2536
2639
  delete target.duration;
2640
+ delete target.paused;
2537
2641
  } catch {
2538
2642
  }
2539
2643
  },
@@ -2676,6 +2780,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
2676
2780
  lastProgressTime = 0;
2677
2781
  lastProgressPosition = -1;
2678
2782
  errorListener = null;
2783
+ // Bound so we can removeEventListener in destroy(); without this the
2784
+ // listener outlives the player and accumulates on elements that swap
2785
+ // source (e.g. <avbridge-video>).
2786
+ endedListener = null;
2679
2787
  // Serializes escalation / setStrategy calls
2680
2788
  switchingPromise = Promise.resolve();
2681
2789
  // Owns blob URLs created during sidecar discovery + SRT->VTT conversion.
@@ -2701,8 +2809,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
2701
2809
  return player;
2702
2810
  }
2703
2811
  async bootstrap() {
2812
+ const bootstrapStart = performance.now();
2704
2813
  try {
2705
- const ctx = await probe(this.options.source);
2814
+ dbg.info("bootstrap", "start");
2815
+ const ctx = await dbg.timed("probe", "probe", 3e3, () => probe(this.options.source));
2816
+ dbg.info(
2817
+ "probe",
2818
+ `container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
2819
+ );
2706
2820
  this.diag.recordProbe(ctx);
2707
2821
  this.mediaContext = ctx;
2708
2822
  if (this.options.subtitles) {
@@ -2728,6 +2842,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
2728
2842
  }
2729
2843
  }
2730
2844
  const decision = this.options.initialStrategy ? buildInitialDecision(this.options.initialStrategy, ctx) : classifyContext(ctx);
2845
+ dbg.info(
2846
+ "classify",
2847
+ `strategy=${decision.strategy} class=${decision.class} reason="${decision.reason}"` + (decision.fallbackChain ? ` fallback=${decision.fallbackChain.join("\u2192")}` : "")
2848
+ );
2731
2849
  this.classification = decision;
2732
2850
  this.diag.recordClassification(decision);
2733
2851
  this.emitter.emitSticky("strategy", {
@@ -2751,8 +2869,17 @@ var UnifiedPlayer = class _UnifiedPlayer {
2751
2869
  subtitle: ctx.subtitleTracks
2752
2870
  });
2753
2871
  this.startTimeupdateLoop();
2754
- this.options.target.addEventListener("ended", () => this.emitter.emit("ended", void 0));
2872
+ this.endedListener = () => this.emitter.emit("ended", void 0);
2873
+ this.options.target.addEventListener("ended", this.endedListener);
2755
2874
  this.emitter.emitSticky("ready", void 0);
2875
+ const bootstrapElapsed = performance.now() - bootstrapStart;
2876
+ dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
2877
+ if (bootstrapElapsed > 5e3) {
2878
+ console.warn(
2879
+ "[avbridge:bootstrap]",
2880
+ `total bootstrap time ${bootstrapElapsed.toFixed(0)}ms \u2014 unusually slow. Enable globalThis.AVBRIDGE_DEBUG for a per-phase breakdown.`
2881
+ );
2882
+ }
2756
2883
  } catch (err) {
2757
2884
  const e = err instanceof Error ? err : new Error(String(err));
2758
2885
  this.diag.recordError(e);
@@ -3029,6 +3156,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
3029
3156
  this.timeupdateInterval = null;
3030
3157
  }
3031
3158
  this.clearSupervisor();
3159
+ if (this.endedListener) {
3160
+ this.options.target.removeEventListener("ended", this.endedListener);
3161
+ this.endedListener = null;
3162
+ }
3032
3163
  if (this.session) {
3033
3164
  await this.session.destroy();
3034
3165
  this.session = null;
@@ -3043,11 +3174,13 @@ async function createPlayer(options) {
3043
3174
  function buildInitialDecision(initial, ctx) {
3044
3175
  const natural = classifyContext(ctx);
3045
3176
  const cls = strategyToClass(initial, natural);
3177
+ const inherited = natural.fallbackChain ?? defaultFallbackChain(initial);
3178
+ const fallbackChain = inherited.filter((s) => s !== initial);
3046
3179
  return {
3047
3180
  class: cls,
3048
3181
  strategy: initial,
3049
3182
  reason: `initial strategy "${initial}" requested via options.initialStrategy`,
3050
- fallbackChain: natural.fallbackChain ?? defaultFallbackChain(initial)
3183
+ fallbackChain
3051
3184
  };
3052
3185
  }
3053
3186
  function strategyToClass(strategy, natural) {
@@ -3077,5 +3210,5 @@ function defaultFallbackChain(strategy) {
3077
3210
  }
3078
3211
 
3079
3212
  export { UnifiedPlayer, avbridgeAudioToMediabunny, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, classifyContext, createPlayer, probe, srtToVtt };
3080
- //# sourceMappingURL=chunk-3AUGRKPY.js.map
3081
- //# sourceMappingURL=chunk-3AUGRKPY.js.map
3213
+ //# sourceMappingURL=chunk-DMWARSEF.js.map
3214
+ //# sourceMappingURL=chunk-DMWARSEF.js.map