avbridge 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/dist/{avi-GNTV5ZOH.cjs → avi-6SJLWIWW.cjs} +19 -4
  3. package/dist/avi-6SJLWIWW.cjs.map +1 -0
  4. package/dist/{avi-V6HYQVR2.js → avi-GCGM7OJI.js} +18 -3
  5. package/dist/avi-GCGM7OJI.js.map +1 -0
  6. package/dist/{chunk-EJH67FXG.js → chunk-5DMTJVIU.js} +99 -3
  7. package/dist/chunk-5DMTJVIU.js.map +1 -0
  8. package/dist/{chunk-3AUGRKPY.js → chunk-C5VA5U5O.js} +94 -16
  9. package/dist/chunk-C5VA5U5O.js.map +1 -0
  10. package/dist/{chunk-JQH6D4OE.cjs → chunk-G4APZMCP.cjs} +100 -3
  11. package/dist/chunk-G4APZMCP.cjs.map +1 -0
  12. package/dist/{chunk-Y5FYF5KG.cjs → chunk-HZLQNKFN.cjs} +5 -2
  13. package/dist/chunk-HZLQNKFN.cjs.map +1 -0
  14. package/dist/{chunk-PQTZS7OA.js → chunk-ILKDNBSE.js} +5 -2
  15. package/dist/chunk-ILKDNBSE.js.map +1 -0
  16. package/dist/{chunk-DPVIOYGC.cjs → chunk-OE66B34H.cjs} +98 -20
  17. package/dist/chunk-OE66B34H.cjs.map +1 -0
  18. package/dist/element-browser.js +210 -10
  19. package/dist/element-browser.js.map +1 -1
  20. package/dist/element.cjs +4 -4
  21. package/dist/element.d.cts +1 -1
  22. package/dist/element.d.ts +1 -1
  23. package/dist/element.js +3 -3
  24. package/dist/index.cjs +18 -18
  25. package/dist/index.d.cts +2 -2
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +5 -5
  28. package/dist/libav-loader-27RDIN2I.js +3 -0
  29. package/dist/{libav-loader-XKH2TKUW.js.map → libav-loader-27RDIN2I.js.map} +1 -1
  30. package/dist/libav-loader-IV4AJ2HW.cjs +12 -0
  31. package/dist/{libav-loader-6APXVNIV.cjs.map → libav-loader-IV4AJ2HW.cjs.map} +1 -1
  32. package/dist/{player-BdtUG4rh.d.cts → player-DUyvltvy.d.cts} +3 -3
  33. package/dist/{player-BdtUG4rh.d.ts → player-DUyvltvy.d.ts} +3 -3
  34. package/dist/source-CN43EI7Z.cjs +28 -0
  35. package/dist/{source-SC6ZEQYR.cjs.map → source-CN43EI7Z.cjs.map} +1 -1
  36. package/dist/source-FFZ7TW2B.js +3 -0
  37. package/dist/{source-ZFS4H7J3.js.map → source-FFZ7TW2B.js.map} +1 -1
  38. package/package.json +1 -1
  39. package/src/classify/rules.ts +9 -2
  40. package/src/player.ts +22 -1
  41. package/src/probe/avi.ts +8 -1
  42. package/src/strategies/fallback/audio-output.ts +25 -3
  43. package/src/strategies/fallback/decoder.ts +96 -8
  44. package/src/strategies/fallback/index.ts +90 -6
  45. package/src/strategies/fallback/libav-loader.ts +12 -0
  46. package/src/types.ts +10 -1
  47. package/src/util/debug.ts +131 -0
  48. package/src/util/source.ts +4 -0
  49. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  50. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  51. package/dist/avi-GNTV5ZOH.cjs.map +0 -1
  52. package/dist/avi-V6HYQVR2.js.map +0 -1
  53. package/dist/chunk-3AUGRKPY.js.map +0 -1
  54. package/dist/chunk-DPVIOYGC.cjs.map +0 -1
  55. package/dist/chunk-EJH67FXG.js.map +0 -1
  56. package/dist/chunk-JQH6D4OE.cjs.map +0 -1
  57. package/dist/chunk-PQTZS7OA.js.map +0 -1
  58. package/dist/chunk-Y5FYF5KG.cjs.map +0 -1
  59. package/dist/libav-loader-6APXVNIV.cjs +0 -12
  60. package/dist/libav-loader-XKH2TKUW.js +0 -3
  61. package/dist/source-SC6ZEQYR.cjs +0 -28
  62. package/dist/source-ZFS4H7J3.js +0 -3
@@ -145,6 +145,9 @@ function sniffContainerFromBytes(head) {
145
145
  }
146
146
  if (head[0] === 48 && head[1] === 38 && head[2] === 178 && head[3] === 117 && head[4] === 142 && head[5] === 102 && head[6] === 207 && head[7] === 17) return "asf";
147
147
  if (head[0] === 70 && head[1] === 76 && head[2] === 86) return "flv";
148
+ if (head[0] === 46 && head[1] === 82 && head[2] === 77 && head[3] === 70) {
149
+ return "rm";
150
+ }
148
151
  if (head[0] === 79 && head[1] === 103 && head[2] === 103 && head[3] === 83) return "ogg";
149
152
  if (head[0] === 102 && head[1] === 76 && head[2] === 97 && head[3] === 67) return "flac";
150
153
  if (head[0] === 73 && head[1] === 68 && head[2] === 51) return "mp3";
@@ -29628,6 +29631,98 @@ var init_libav_http_reader = __esm({
29628
29631
  }
29629
29632
  });
29630
29633
 
29634
+ // src/util/debug.ts
29635
+ function isDebugEnabled() {
29636
+ if (typeof globalThis === "undefined") return false;
29637
+ const g = globalThis;
29638
+ if (g.AVBRIDGE_DEBUG === true) return true;
29639
+ if (typeof location !== "undefined" && typeof URLSearchParams !== "undefined") {
29640
+ try {
29641
+ const p = new URLSearchParams(location.search);
29642
+ if (p.has("avbridge_debug")) {
29643
+ g.AVBRIDGE_DEBUG = true;
29644
+ return true;
29645
+ }
29646
+ } catch {
29647
+ }
29648
+ }
29649
+ return false;
29650
+ }
29651
+ function fmt(tag) {
29652
+ return `[avbridge:${tag}]`;
29653
+ }
29654
+ function hintForTag(tag) {
29655
+ switch (tag) {
29656
+ case "probe":
29657
+ return "slow network (range request), large sniff window, or libav cold-start";
29658
+ case "libav-load":
29659
+ return "large .wasm download, misconfigured AVBRIDGE_LIBAV_BASE, or server-side MIME type";
29660
+ case "bootstrap":
29661
+ return "probe+classify+strategy-init chain; enable AVBRIDGE_DEBUG for a phase breakdown";
29662
+ case "cold-start":
29663
+ return "decoder is producing output slower than realtime \u2014 check framesDecoded in getDiagnostics()";
29664
+ default:
29665
+ return "unknown stage \u2014 enable globalThis.AVBRIDGE_DEBUG for more detail";
29666
+ }
29667
+ }
29668
+ var dbg;
29669
+ var init_debug = __esm({
29670
+ "src/util/debug.ts"() {
29671
+ dbg = {
29672
+ /** Verbose — only when debug is enabled. The hot-path normal case. */
29673
+ info(tag, ...args) {
29674
+ if (isDebugEnabled()) console.info(fmt(tag), ...args);
29675
+ },
29676
+ /** Warning — only when debug is enabled. Non-fatal oddities. */
29677
+ warn(tag, ...args) {
29678
+ if (isDebugEnabled()) console.warn(fmt(tag), ...args);
29679
+ },
29680
+ /**
29681
+ * Self-diagnosis warning. **Always** emits regardless of debug flag.
29682
+ * Use this only for conditions that mean something is actually wrong
29683
+ * or degraded — not for routine chatter.
29684
+ */
29685
+ diag(tag, ...args) {
29686
+ console.warn(fmt(tag), ...args);
29687
+ },
29688
+ /**
29689
+ * Timing helper: wraps an async call and logs its elapsed time when
29690
+ * debug is on. The callback runs whether debug is on or off — this is
29691
+ * just for the `dbg.info` at the end.
29692
+ *
29693
+ * Also unconditionally fires `dbg.diag` if the elapsed time exceeds
29694
+ * `slowMs`, so "the bootstrap took 8 seconds" shows up even without
29695
+ * debug mode enabled.
29696
+ */
29697
+ async timed(tag, label, slowMs, fn) {
29698
+ const start = performance.now();
29699
+ try {
29700
+ const result = await fn();
29701
+ const elapsed = performance.now() - start;
29702
+ if (isDebugEnabled()) {
29703
+ console.info(fmt(tag), `${label} ${elapsed.toFixed(0)}ms`);
29704
+ }
29705
+ if (elapsed > slowMs) {
29706
+ console.warn(
29707
+ fmt(tag),
29708
+ `${label} took ${elapsed.toFixed(0)}ms (>${slowMs}ms expected) \u2014 this is unusually slow; possible causes: ${hintForTag(tag)}`
29709
+ );
29710
+ }
29711
+ return result;
29712
+ } catch (err) {
29713
+ const elapsed = performance.now() - start;
29714
+ console.warn(
29715
+ fmt(tag),
29716
+ `${label} FAILED after ${elapsed.toFixed(0)}ms:`,
29717
+ err
29718
+ );
29719
+ throw err;
29720
+ }
29721
+ }
29722
+ };
29723
+ }
29724
+ });
29725
+
29631
29726
  // src/strategies/fallback/libav-loader.ts
29632
29727
  function cacheKey(variant, threads) {
29633
29728
  return `${variant}:${threads ? "thr" : "wasm"}`;
@@ -29644,9 +29739,18 @@ function loadLibav(variant = "webcodecs", opts = {}) {
29644
29739
  return entry;
29645
29740
  }
29646
29741
  async function loadVariant(variant, wantThreads) {
29742
+ return dbg.timed(
29743
+ "libav-load",
29744
+ `load "${variant}" (threads=${wantThreads})`,
29745
+ 5e3,
29746
+ () => loadVariantInner(variant, wantThreads)
29747
+ );
29748
+ }
29749
+ async function loadVariantInner(variant, wantThreads) {
29647
29750
  const key = cacheKey(variant, wantThreads);
29648
29751
  const base = `${libavBaseUrl()}/${variant}`;
29649
29752
  const variantUrl = `${base}/libav-${variant}.mjs`;
29753
+ dbg.info("libav-load", `fetching ${variantUrl}`);
29650
29754
  if (typeof fetch === "function") {
29651
29755
  try {
29652
29756
  const head = await fetch(variantUrl, { method: "GET", headers: { Range: "bytes=0-0" } });
@@ -29730,6 +29834,7 @@ function chain(message, err) {
29730
29834
  var cache;
29731
29835
  var init_libav_loader = __esm({
29732
29836
  "src/strategies/fallback/libav-loader.ts"() {
29837
+ init_debug();
29733
29838
  cache = /* @__PURE__ */ new Map();
29734
29839
  }
29735
29840
  });
@@ -29858,7 +29963,12 @@ function ffmpegToAvbridgeVideo(name) {
29858
29963
  return "mpeg1";
29859
29964
  case "theora":
29860
29965
  return "theora";
29966
+ case "rv10":
29967
+ return "rv10";
29968
+ case "rv20":
29969
+ return "rv20";
29861
29970
  case "rv30":
29971
+ return "rv30";
29862
29972
  case "rv40":
29863
29973
  return "rv40";
29864
29974
  default:
@@ -29889,6 +29999,16 @@ function ffmpegToAvbridgeAudio(name) {
29889
29999
  return "wmapro";
29890
30000
  case "alac":
29891
30001
  return "alac";
30002
+ case "cook":
30003
+ return "cook";
30004
+ case "ra_144":
30005
+ return "ra_144";
30006
+ case "ra_288":
30007
+ return "ra_288";
30008
+ case "sipr":
30009
+ return "sipr";
30010
+ case "atrac3":
30011
+ return "atrac3";
29892
30012
  default:
29893
30013
  return name;
29894
30014
  }
@@ -31154,8 +31274,29 @@ var NATIVE_AUDIO_CODECS = /* @__PURE__ */ new Set([
31154
31274
  "vorbis",
31155
31275
  "flac"
31156
31276
  ]);
31157
- var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set(["wmv3", "vc1", "mpeg4", "rv40", "mpeg2", "mpeg1", "theora"]);
31158
- var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set(["wmav2", "wmapro", "ac3", "eac3"]);
31277
+ var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set([
31278
+ "wmv3",
31279
+ "vc1",
31280
+ "mpeg4",
31281
+ "rv10",
31282
+ "rv20",
31283
+ "rv30",
31284
+ "rv40",
31285
+ "mpeg2",
31286
+ "mpeg1",
31287
+ "theora"
31288
+ ]);
31289
+ var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set([
31290
+ "wmav2",
31291
+ "wmapro",
31292
+ "ac3",
31293
+ "eac3",
31294
+ "cook",
31295
+ "ra_144",
31296
+ "ra_288",
31297
+ "sipr",
31298
+ "atrac3"
31299
+ ]);
31159
31300
  var NATIVE_CONTAINERS = /* @__PURE__ */ new Set([
31160
31301
  "mp4",
31161
31302
  "mov",
@@ -32906,6 +33047,9 @@ async function startDecoder(opts) {
32906
33047
  let packetsRead = 0;
32907
33048
  let videoFramesDecoded = 0;
32908
33049
  let audioFramesDecoded = 0;
33050
+ let watchdogFirstFrameMs = 0;
33051
+ let watchdogSlowSinceMs = 0;
33052
+ let watchdogWarned = false;
32909
33053
  let syntheticVideoUs = 0;
32910
33054
  let syntheticAudioUs = 0;
32911
33055
  const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
@@ -32917,7 +33061,7 @@ async function startDecoder(opts) {
32917
33061
  let packets;
32918
33062
  try {
32919
33063
  [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
32920
- limit: 16 * 1024
33064
+ limit: 64 * 1024
32921
33065
  });
32922
33066
  } catch (err) {
32923
33067
  console.error("[avbridge] ff_read_frame_multi failed:", err);
@@ -32934,6 +33078,28 @@ async function startDecoder(opts) {
32934
33078
  await decodeAudioBatch(audioPackets, myToken);
32935
33079
  }
32936
33080
  packetsRead += (videoPackets?.length ?? 0) + (audioPackets?.length ?? 0);
33081
+ if (videoFramesDecoded > 0) {
33082
+ if (watchdogFirstFrameMs === 0) {
33083
+ watchdogFirstFrameMs = performance.now();
33084
+ }
33085
+ const elapsedSinceFirst = (performance.now() - watchdogFirstFrameMs) / 1e3;
33086
+ if (elapsedSinceFirst > 1 && !watchdogWarned) {
33087
+ const expectedFrames = elapsedSinceFirst * videoFps;
33088
+ const ratio = videoFramesDecoded / expectedFrames;
33089
+ if (ratio < 0.6) {
33090
+ if (watchdogSlowSinceMs === 0) watchdogSlowSinceMs = performance.now();
33091
+ if ((performance.now() - watchdogSlowSinceMs) / 1e3 > 5) {
33092
+ watchdogWarned = true;
33093
+ console.warn(
33094
+ "[avbridge:decode-rate]",
33095
+ `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.`
33096
+ );
33097
+ }
33098
+ } else {
33099
+ watchdogSlowSinceMs = 0;
33100
+ }
33101
+ }
33102
+ }
32937
33103
  while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
32938
33104
  await new Promise((r) => setTimeout(r, 50));
32939
33105
  }
@@ -33260,8 +33426,9 @@ async function loadBridge2() {
33260
33426
  }
33261
33427
 
33262
33428
  // src/strategies/fallback/index.ts
33263
- var READY_AUDIO_BUFFER_SECONDS2 = 0.3;
33264
- var READY_TIMEOUT_SECONDS2 = 10;
33429
+ init_debug();
33430
+ var READY_AUDIO_BUFFER_SECONDS2 = 0.04;
33431
+ var READY_TIMEOUT_SECONDS2 = 3;
33265
33432
  async function createFallbackSession(ctx, target) {
33266
33433
  const { normalizeSource: normalizeSource2 } = await Promise.resolve().then(() => (init_source(), source_exports));
33267
33434
  const source = await normalizeSource2(ctx.source);
@@ -33297,12 +33464,26 @@ async function createFallbackSession(ctx, target) {
33297
33464
  }
33298
33465
  async function waitForBuffer() {
33299
33466
  const start = performance.now();
33467
+ dbg.info(
33468
+ "cold-start",
33469
+ `gate entry: need audio >= ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms + 1 frame`
33470
+ );
33300
33471
  while (true) {
33301
- const audioReady = audio.isNoAudio() || audio.bufferAhead() >= READY_AUDIO_BUFFER_SECONDS2;
33302
- if (audioReady && renderer.hasFrames()) {
33472
+ const audioAhead = audio.isNoAudio() ? Infinity : audio.bufferAhead();
33473
+ const audioReady = audio.isNoAudio() || audioAhead >= READY_AUDIO_BUFFER_SECONDS2;
33474
+ const hasFrames = renderer.hasFrames();
33475
+ if (audioReady && hasFrames) {
33476
+ dbg.info(
33477
+ "cold-start",
33478
+ `gate satisfied in ${(performance.now() - start).toFixed(0)}ms (audio=${(audioAhead * 1e3).toFixed(0)}ms, frames=${renderer.queueDepth()})`
33479
+ );
33303
33480
  return;
33304
33481
  }
33305
33482
  if ((performance.now() - start) / 1e3 > READY_TIMEOUT_SECONDS2) {
33483
+ dbg.diag(
33484
+ "cold-start",
33485
+ `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.`
33486
+ );
33306
33487
  return;
33307
33488
  }
33308
33489
  await new Promise((r) => setTimeout(r, 50));
@@ -33413,8 +33594,8 @@ function convertTiming(line) {
33413
33594
  line.trim()
33414
33595
  );
33415
33596
  if (!m) return null;
33416
- const fmt = (h, mm, s, ms) => `${h.padStart(2, "0")}:${mm}:${s}.${ms.padEnd(3, "0").slice(0, 3)}`;
33417
- return `${fmt(m[1], m[2], m[3], m[4])} --> ${fmt(m[5], m[6], m[7], m[8])}${m[9] ?? ""}`;
33597
+ const fmt2 = (h, mm, s, ms) => `${h.padStart(2, "0")}:${mm}:${s}.${ms.padEnd(3, "0").slice(0, 3)}`;
33598
+ return `${fmt2(m[1], m[2], m[3], m[4])} --> ${fmt2(m[5], m[6], m[7], m[8])}${m[9] ?? ""}`;
33418
33599
  }
33419
33600
 
33420
33601
  // src/subtitles/vtt.ts
@@ -33500,6 +33681,7 @@ async function attachSubtitleTracks(video, tracks, bag, onError) {
33500
33681
  }
33501
33682
 
33502
33683
  // src/player.ts
33684
+ init_debug();
33503
33685
  var UnifiedPlayer = class _UnifiedPlayer {
33504
33686
  /**
33505
33687
  * @internal Use {@link createPlayer} or {@link UnifiedPlayer.create} instead.
@@ -33547,8 +33729,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
33547
33729
  return player;
33548
33730
  }
33549
33731
  async bootstrap() {
33732
+ const bootstrapStart = performance.now();
33550
33733
  try {
33551
- const ctx = await probe(this.options.source);
33734
+ dbg.info("bootstrap", "start");
33735
+ const ctx = await dbg.timed("probe", "probe", 3e3, () => probe(this.options.source));
33736
+ dbg.info(
33737
+ "probe",
33738
+ `container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
33739
+ );
33552
33740
  this.diag.recordProbe(ctx);
33553
33741
  this.mediaContext = ctx;
33554
33742
  if (this.options.subtitles) {
@@ -33574,6 +33762,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
33574
33762
  }
33575
33763
  }
33576
33764
  const decision = this.options.initialStrategy ? buildInitialDecision(this.options.initialStrategy, ctx) : classifyContext(ctx);
33765
+ dbg.info(
33766
+ "classify",
33767
+ `strategy=${decision.strategy} class=${decision.class} reason="${decision.reason}"` + (decision.fallbackChain ? ` fallback=${decision.fallbackChain.join("\u2192")}` : "")
33768
+ );
33577
33769
  this.classification = decision;
33578
33770
  this.diag.recordClassification(decision);
33579
33771
  this.emitter.emitSticky("strategy", {
@@ -33599,6 +33791,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
33599
33791
  this.startTimeupdateLoop();
33600
33792
  this.options.target.addEventListener("ended", () => this.emitter.emit("ended", void 0));
33601
33793
  this.emitter.emitSticky("ready", void 0);
33794
+ const bootstrapElapsed = performance.now() - bootstrapStart;
33795
+ dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
33796
+ if (bootstrapElapsed > 5e3) {
33797
+ console.warn(
33798
+ "[avbridge:bootstrap]",
33799
+ `total bootstrap time ${bootstrapElapsed.toFixed(0)}ms \u2014 unusually slow. Enable globalThis.AVBRIDGE_DEBUG for a per-phase breakdown.`
33800
+ );
33801
+ }
33602
33802
  } catch (err) {
33603
33803
  const e = err instanceof Error ? err : new Error(String(err));
33604
33804
  this.diag.recordError(e);