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.
- package/CHANGELOG.md +138 -0
- package/README.md +98 -71
- package/dist/{avi-GNTV5ZOH.cjs → avi-6SJLWIWW.cjs} +19 -4
- package/dist/avi-6SJLWIWW.cjs.map +1 -0
- package/dist/{avi-V6HYQVR2.js → avi-GCGM7OJI.js} +18 -3
- package/dist/avi-GCGM7OJI.js.map +1 -0
- package/dist/{chunk-EJH67FXG.js → chunk-5DMTJVIU.js} +99 -3
- package/dist/chunk-5DMTJVIU.js.map +1 -0
- package/dist/{chunk-3AUGRKPY.js → chunk-DMWARSEF.js} +160 -27
- package/dist/chunk-DMWARSEF.js.map +1 -0
- package/dist/{chunk-JQH6D4OE.cjs → chunk-G4APZMCP.cjs} +100 -3
- package/dist/chunk-G4APZMCP.cjs.map +1 -0
- package/dist/{chunk-Y5FYF5KG.cjs → chunk-HZLQNKFN.cjs} +5 -2
- package/dist/chunk-HZLQNKFN.cjs.map +1 -0
- package/dist/{chunk-PQTZS7OA.js → chunk-ILKDNBSE.js} +5 -2
- package/dist/chunk-ILKDNBSE.js.map +1 -0
- package/dist/{chunk-DPVIOYGC.cjs → chunk-UF2N5L63.cjs} +164 -31
- package/dist/chunk-UF2N5L63.cjs.map +1 -0
- package/dist/element-browser.js +276 -21
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +4 -4
- package/dist/element.d.cts +1 -1
- package/dist/element.d.ts +1 -1
- package/dist/element.js +3 -3
- package/dist/index.cjs +18 -18
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/libav-loader-27RDIN2I.js +3 -0
- package/dist/{libav-loader-XKH2TKUW.js.map → libav-loader-27RDIN2I.js.map} +1 -1
- package/dist/libav-loader-IV4AJ2HW.cjs +12 -0
- package/dist/{libav-loader-6APXVNIV.cjs.map → libav-loader-IV4AJ2HW.cjs.map} +1 -1
- package/dist/{player-BdtUG4rh.d.cts → player-U2NPmFvA.d.cts} +4 -3
- package/dist/{player-BdtUG4rh.d.ts → player-U2NPmFvA.d.ts} +4 -3
- package/dist/source-CN43EI7Z.cjs +28 -0
- package/dist/{source-SC6ZEQYR.cjs.map → source-CN43EI7Z.cjs.map} +1 -1
- package/dist/source-FFZ7TW2B.js +3 -0
- package/dist/{source-ZFS4H7J3.js.map → source-FFZ7TW2B.js.map} +1 -1
- package/package.json +1 -1
- package/src/classify/rules.ts +9 -2
- package/src/player.ts +46 -17
- package/src/probe/avi.ts +8 -1
- package/src/strategies/fallback/audio-output.ts +25 -3
- package/src/strategies/fallback/decoder.ts +96 -8
- package/src/strategies/fallback/index.ts +98 -6
- package/src/strategies/fallback/libav-loader.ts +12 -0
- package/src/strategies/fallback/video-renderer.ts +5 -1
- package/src/strategies/hybrid/index.ts +9 -1
- package/src/strategies/remux/index.ts +13 -1
- package/src/strategies/remux/pipeline.ts +6 -0
- package/src/types.ts +10 -1
- package/src/util/debug.ts +131 -0
- package/src/util/source.ts +4 -0
- package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
- package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
- package/dist/avi-GNTV5ZOH.cjs.map +0 -1
- package/dist/avi-V6HYQVR2.js.map +0 -1
- package/dist/chunk-3AUGRKPY.js.map +0 -1
- package/dist/chunk-DPVIOYGC.cjs.map +0 -1
- package/dist/chunk-EJH67FXG.js.map +0 -1
- package/dist/chunk-JQH6D4OE.cjs.map +0 -1
- package/dist/chunk-PQTZS7OA.js.map +0 -1
- package/dist/chunk-Y5FYF5KG.cjs.map +0 -1
- package/dist/libav-loader-6APXVNIV.cjs +0 -12
- package/dist/libav-loader-XKH2TKUW.js +0 -3
- package/dist/source-SC6ZEQYR.cjs +0 -28
- 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-
|
|
104
|
-
//# sourceMappingURL=chunk-
|
|
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-
|
|
2
|
-
import { loadLibav } from './chunk-
|
|
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-
|
|
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-
|
|
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([
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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-
|
|
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.
|
|
2448
|
-
var READY_TIMEOUT_SECONDS2 =
|
|
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-
|
|
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
|
|
2486
|
-
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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
|
|
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-
|
|
3081
|
-
//# sourceMappingURL=chunk-
|
|
3213
|
+
//# sourceMappingURL=chunk-DMWARSEF.js.map
|
|
3214
|
+
//# sourceMappingURL=chunk-DMWARSEF.js.map
|