avbridge 2.1.1 → 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 (68) hide show
  1. package/CHANGELOG.md +136 -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-CUQD23WO.js → chunk-C5VA5U5O.js} +122 -23
  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-O34444ID.cjs → chunk-OE66B34H.cjs} +126 -27
  17. package/dist/chunk-OE66B34H.cjs.map +1 -0
  18. package/dist/element-browser.js +244 -19
  19. package/dist/element-browser.js.map +1 -1
  20. package/dist/element.cjs +9 -5
  21. package/dist/element.cjs.map +1 -1
  22. package/dist/element.d.cts +1 -1
  23. package/dist/element.d.ts +1 -1
  24. package/dist/element.js +8 -4
  25. package/dist/element.js.map +1 -1
  26. package/dist/index.cjs +18 -18
  27. package/dist/index.d.cts +12 -8
  28. package/dist/index.d.ts +12 -8
  29. package/dist/index.js +5 -5
  30. package/dist/libav-loader-27RDIN2I.js +3 -0
  31. package/dist/{libav-loader-XKH2TKUW.js.map → libav-loader-27RDIN2I.js.map} +1 -1
  32. package/dist/libav-loader-IV4AJ2HW.cjs +12 -0
  33. package/dist/{libav-loader-6APXVNIV.cjs.map → libav-loader-IV4AJ2HW.cjs.map} +1 -1
  34. package/dist/{player-BdtUG4rh.d.cts → player-DUyvltvy.d.cts} +3 -3
  35. package/dist/{player-BdtUG4rh.d.ts → player-DUyvltvy.d.ts} +3 -3
  36. package/dist/source-CN43EI7Z.cjs +28 -0
  37. package/dist/{source-SC6ZEQYR.cjs.map → source-CN43EI7Z.cjs.map} +1 -1
  38. package/dist/source-FFZ7TW2B.js +3 -0
  39. package/dist/{source-ZFS4H7J3.js.map → source-FFZ7TW2B.js.map} +1 -1
  40. package/package.json +1 -1
  41. package/src/classify/rules.ts +9 -2
  42. package/src/element/avbridge-video.ts +12 -1
  43. package/src/player.ts +22 -1
  44. package/src/probe/avi.ts +8 -1
  45. package/src/probe/index.ts +30 -9
  46. package/src/strategies/fallback/audio-output.ts +25 -3
  47. package/src/strategies/fallback/decoder.ts +96 -8
  48. package/src/strategies/fallback/index.ts +90 -6
  49. package/src/strategies/fallback/libav-loader.ts +12 -0
  50. package/src/strategies/fallback/video-renderer.ts +29 -4
  51. package/src/strategies/remux/pipeline.ts +10 -1
  52. package/src/types.ts +10 -1
  53. package/src/util/debug.ts +131 -0
  54. package/src/util/source.ts +4 -0
  55. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  56. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  57. package/dist/avi-GNTV5ZOH.cjs.map +0 -1
  58. package/dist/avi-V6HYQVR2.js.map +0 -1
  59. package/dist/chunk-CUQD23WO.js.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-O34444ID.cjs.map +0 -1
  63. package/dist/chunk-PQTZS7OA.js.map +0 -1
  64. package/dist/chunk-Y5FYF5KG.cjs.map +0 -1
  65. package/dist/libav-loader-6APXVNIV.cjs +0 -12
  66. package/dist/libav-loader-XKH2TKUW.js +0 -3
  67. package/dist/source-SC6ZEQYR.cjs +0 -28
  68. package/dist/source-ZFS4H7J3.js +0 -3
@@ -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
@@ -184,14 +184,25 @@ async function probe(source) {
184
184
  if (MEDIABUNNY_CONTAINERS.has(sniffed)) {
185
185
  try {
186
186
  return await probeWithMediabunny(normalized, sniffed);
187
- } catch (err) {
188
- throw new Error(
189
- `mediabunny failed to probe a ${sniffed} file: ${err.message}`
187
+ } catch (mediabunnyErr) {
188
+ console.warn(
189
+ `[avbridge] mediabunny rejected ${sniffed} file, falling back to libav:`,
190
+ mediabunnyErr.message
190
191
  );
192
+ try {
193
+ const { probeWithLibav } = await import('./avi-GCGM7OJI.js');
194
+ return await probeWithLibav(normalized, sniffed);
195
+ } catch (libavErr) {
196
+ const mbMsg = mediabunnyErr.message || String(mediabunnyErr);
197
+ const lvMsg = libavErr instanceof Error ? libavErr.message : String(libavErr);
198
+ throw new Error(
199
+ `failed to probe ${sniffed} file. mediabunny: ${mbMsg}. libav fallback: ${lvMsg}.`
200
+ );
201
+ }
191
202
  }
192
203
  }
193
204
  try {
194
- const { probeWithLibav } = await import('./avi-V6HYQVR2.js');
205
+ const { probeWithLibav } = await import('./avi-GCGM7OJI.js');
195
206
  return await probeWithLibav(normalized, sniffed);
196
207
  } catch (err) {
197
208
  const inner = err instanceof Error ? err.message : String(err);
@@ -277,8 +288,29 @@ var NATIVE_AUDIO_CODECS = /* @__PURE__ */ new Set([
277
288
  "vorbis",
278
289
  "flac"
279
290
  ]);
280
- var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set(["wmv3", "vc1", "mpeg4", "rv40", "mpeg2", "mpeg1", "theora"]);
281
- 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
+ ]);
282
314
  var NATIVE_CONTAINERS = /* @__PURE__ */ new Set([
283
315
  "mp4",
284
316
  "mov",
@@ -988,7 +1020,8 @@ async function createRemuxPipeline(ctx, video) {
988
1020
  if (destroyed || pumpToken !== token) break;
989
1021
  const vTs = !vNext.done ? vNext.value.timestamp : Number.POSITIVE_INFINITY;
990
1022
  const aTs = !aNext.done ? aNext.value.timestamp : Number.POSITIVE_INFINITY;
991
- if (!vNext.done && vTs <= aTs) {
1023
+ const forceVideoFirst = firstVideo && !vNext.done;
1024
+ if (!vNext.done && (forceVideoFirst || vTs <= aTs)) {
992
1025
  await videoSource.add(
993
1026
  vNext.value,
994
1027
  firstVideo && videoConfig ? { decoderConfig: videoConfig } : void 0
@@ -1126,11 +1159,20 @@ var VideoRenderer = class {
1126
1159
  });
1127
1160
  this.canvas = document.createElement("canvas");
1128
1161
  this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;";
1129
- const parent = target.parentElement;
1130
- if (parent && getComputedStyle(parent).position === "static") {
1131
- parent.style.position = "relative";
1162
+ const parent = target.parentElement ?? target.parentNode;
1163
+ if (parent && parent instanceof HTMLElement) {
1164
+ if (getComputedStyle(parent).position === "static") {
1165
+ parent.style.position = "relative";
1166
+ }
1167
+ }
1168
+ if (parent) {
1169
+ parent.insertBefore(this.canvas, target);
1170
+ } else {
1171
+ console.warn(
1172
+ "[avbridge] fallback renderer: target <video> has no parent; appending canvas to document.body as a fallback."
1173
+ );
1174
+ document.body.appendChild(this.canvas);
1132
1175
  }
1133
- parent?.insertBefore(this.canvas, target);
1134
1176
  target.style.visibility = "hidden";
1135
1177
  const ctx = this.canvas.getContext("2d");
1136
1178
  if (!ctx) throw new Error("video renderer: failed to acquire 2D context");
@@ -1902,7 +1944,7 @@ async function loadBridge() {
1902
1944
  var READY_AUDIO_BUFFER_SECONDS = 0.3;
1903
1945
  var READY_TIMEOUT_SECONDS = 10;
1904
1946
  async function createHybridSession(ctx, target) {
1905
- const { normalizeSource: normalizeSource2 } = await import('./source-ZFS4H7J3.js');
1947
+ const { normalizeSource: normalizeSource2 } = await import('./source-FFZ7TW2B.js');
1906
1948
  const source = await normalizeSource2(ctx.source);
1907
1949
  const fps = ctx.videoTracks[0]?.fps ?? 30;
1908
1950
  const audio = new AudioOutput();
@@ -2069,6 +2111,9 @@ async function startDecoder(opts) {
2069
2111
  let packetsRead = 0;
2070
2112
  let videoFramesDecoded = 0;
2071
2113
  let audioFramesDecoded = 0;
2114
+ let watchdogFirstFrameMs = 0;
2115
+ let watchdogSlowSinceMs = 0;
2116
+ let watchdogWarned = false;
2072
2117
  let syntheticVideoUs = 0;
2073
2118
  let syntheticAudioUs = 0;
2074
2119
  const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
@@ -2080,7 +2125,7 @@ async function startDecoder(opts) {
2080
2125
  let packets;
2081
2126
  try {
2082
2127
  [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
2083
- limit: 16 * 1024
2128
+ limit: 64 * 1024
2084
2129
  });
2085
2130
  } catch (err) {
2086
2131
  console.error("[avbridge] ff_read_frame_multi failed:", err);
@@ -2097,6 +2142,28 @@ async function startDecoder(opts) {
2097
2142
  await decodeAudioBatch(audioPackets, myToken);
2098
2143
  }
2099
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
+ }
2100
2167
  while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
2101
2168
  await new Promise((r) => setTimeout(r, 50));
2102
2169
  }
@@ -2423,10 +2490,10 @@ async function loadBridge2() {
2423
2490
  }
2424
2491
 
2425
2492
  // src/strategies/fallback/index.ts
2426
- var READY_AUDIO_BUFFER_SECONDS2 = 0.3;
2427
- var READY_TIMEOUT_SECONDS2 = 10;
2493
+ var READY_AUDIO_BUFFER_SECONDS2 = 0.04;
2494
+ var READY_TIMEOUT_SECONDS2 = 3;
2428
2495
  async function createFallbackSession(ctx, target) {
2429
- const { normalizeSource: normalizeSource2 } = await import('./source-ZFS4H7J3.js');
2496
+ const { normalizeSource: normalizeSource2 } = await import('./source-FFZ7TW2B.js');
2430
2497
  const source = await normalizeSource2(ctx.source);
2431
2498
  const fps = ctx.videoTracks[0]?.fps ?? 30;
2432
2499
  const audio = new AudioOutput();
@@ -2460,12 +2527,26 @@ async function createFallbackSession(ctx, target) {
2460
2527
  }
2461
2528
  async function waitForBuffer() {
2462
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
+ );
2463
2534
  while (true) {
2464
- const audioReady = audio.isNoAudio() || audio.bufferAhead() >= READY_AUDIO_BUFFER_SECONDS2;
2465
- 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
+ );
2466
2543
  return;
2467
2544
  }
2468
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
+ );
2469
2550
  return;
2470
2551
  }
2471
2552
  await new Promise((r) => setTimeout(r, 50));
@@ -2680,8 +2761,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
2680
2761
  return player;
2681
2762
  }
2682
2763
  async bootstrap() {
2764
+ const bootstrapStart = performance.now();
2683
2765
  try {
2684
- 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
+ );
2685
2772
  this.diag.recordProbe(ctx);
2686
2773
  this.mediaContext = ctx;
2687
2774
  if (this.options.subtitles) {
@@ -2707,6 +2794,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
2707
2794
  }
2708
2795
  }
2709
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
+ );
2710
2801
  this.classification = decision;
2711
2802
  this.diag.recordClassification(decision);
2712
2803
  this.emitter.emitSticky("strategy", {
@@ -2732,6 +2823,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
2732
2823
  this.startTimeupdateLoop();
2733
2824
  this.options.target.addEventListener("ended", () => this.emitter.emit("ended", void 0));
2734
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
+ }
2735
2834
  } catch (err) {
2736
2835
  const e = err instanceof Error ? err : new Error(String(err));
2737
2836
  this.diag.recordError(e);
@@ -3056,5 +3155,5 @@ function defaultFallbackChain(strategy) {
3056
3155
  }
3057
3156
 
3058
3157
  export { UnifiedPlayer, avbridgeAudioToMediabunny, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, classifyContext, createPlayer, probe, srtToVtt };
3059
- //# sourceMappingURL=chunk-CUQD23WO.js.map
3060
- //# sourceMappingURL=chunk-CUQD23WO.js.map
3158
+ //# sourceMappingURL=chunk-C5VA5U5O.js.map
3159
+ //# sourceMappingURL=chunk-C5VA5U5O.js.map