avbridge 2.11.0 → 2.12.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 (84) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/{avi-B5CQYB7L.cjs → avi-32UABODO.cjs} +14 -6
  3. package/dist/avi-32UABODO.cjs.map +1 -0
  4. package/dist/{avi-2ILLBNPQ.cjs → avi-5BPR6QUX.cjs} +14 -6
  5. package/dist/avi-5BPR6QUX.cjs.map +1 -0
  6. package/dist/{avi-RWWPN2PR.js → avi-BLIH7KKV.js} +13 -5
  7. package/dist/avi-BLIH7KKV.js.map +1 -0
  8. package/dist/{avi-JXU4GQL2.js → avi-GX2H34IQ.js} +13 -5
  9. package/dist/avi-GX2H34IQ.js.map +1 -0
  10. package/dist/{chunk-DCSOQH2N.js → chunk-3AI5WFFN.js} +40 -16
  11. package/dist/chunk-3AI5WFFN.js.map +1 -0
  12. package/dist/{chunk-2NSOOMXW.js → chunk-3YKWU4FM.js} +3 -3
  13. package/dist/{chunk-2NSOOMXW.js.map → chunk-3YKWU4FM.js.map} +1 -1
  14. package/dist/{chunk-GYIJU44C.js → chunk-5CX7BVVV.js} +5 -5
  15. package/dist/{chunk-GYIJU44C.js.map → chunk-5CX7BVVV.js.map} +1 -1
  16. package/dist/{chunk-CL6UEUQF.js → chunk-B76QWPFM.js} +5 -5
  17. package/dist/{chunk-CL6UEUQF.js.map → chunk-B76QWPFM.js.map} +1 -1
  18. package/dist/{chunk-IHNHHEA2.js → chunk-BN7BRTLY.js} +143 -32
  19. package/dist/chunk-BN7BRTLY.js.map +1 -0
  20. package/dist/{chunk-OTFS7DC4.cjs → chunk-E5MAM2P4.cjs} +14 -14
  21. package/dist/{chunk-OTFS7DC4.cjs.map → chunk-E5MAM2P4.cjs.map} +1 -1
  22. package/dist/{chunk-L7A3ECI2.cjs → chunk-HZUVMXBN.cjs} +4 -4
  23. package/dist/{chunk-L7A3ECI2.cjs.map → chunk-HZUVMXBN.cjs.map} +1 -1
  24. package/dist/{chunk-37UOSAVI.cjs → chunk-UM6WCSGL.cjs} +157 -46
  25. package/dist/chunk-UM6WCSGL.cjs.map +1 -0
  26. package/dist/{chunk-BYGZN4Z5.cjs → chunk-VLI3Y6IJ.cjs} +5 -5
  27. package/dist/{chunk-BYGZN4Z5.cjs.map → chunk-VLI3Y6IJ.cjs.map} +1 -1
  28. package/dist/{chunk-Z33SBWL5.cjs → chunk-YPZFGJV3.cjs} +40 -16
  29. package/dist/chunk-YPZFGJV3.cjs.map +1 -0
  30. package/dist/element-browser.js +186 -43
  31. package/dist/element-browser.js.map +1 -1
  32. package/dist/element.cjs +5 -5
  33. package/dist/element.d.cts +1 -1
  34. package/dist/element.d.ts +1 -1
  35. package/dist/element.js +4 -4
  36. package/dist/index.cjs +21 -21
  37. package/dist/index.d.cts +2 -2
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.js +9 -9
  40. package/dist/{libav-demux-3N5Y3VQA.cjs → libav-demux-575OYCT2.cjs} +9 -9
  41. package/dist/{libav-demux-3N5Y3VQA.cjs.map → libav-demux-575OYCT2.cjs.map} +1 -1
  42. package/dist/{libav-demux-JXD4OTLM.js → libav-demux-SXZDLC7W.js} +4 -4
  43. package/dist/{libav-demux-JXD4OTLM.js.map → libav-demux-SXZDLC7W.js.map} +1 -1
  44. package/dist/libav-http-reader-2S5HAHW4.js +3 -0
  45. package/dist/{libav-http-reader-WXG3Z7AI.js.map → libav-http-reader-2S5HAHW4.js.map} +1 -1
  46. package/dist/libav-http-reader-Q356EO2K.cjs +16 -0
  47. package/dist/{libav-http-reader-AZLE7YFS.cjs.map → libav-http-reader-Q356EO2K.cjs.map} +1 -1
  48. package/dist/{player-DDdNVFDv.d.cts → player-bQ6n4hVp.d.cts} +15 -0
  49. package/dist/{player-DDdNVFDv.d.ts → player-bQ6n4hVp.d.ts} +15 -0
  50. package/dist/player.cjs +264 -53
  51. package/dist/player.cjs.map +1 -1
  52. package/dist/player.d.cts +22 -0
  53. package/dist/player.d.ts +22 -0
  54. package/dist/player.js +264 -53
  55. package/dist/player.js.map +1 -1
  56. package/dist/remux-NSBJFMLG.cjs +35 -0
  57. package/dist/{remux-KUS5GIL6.cjs.map → remux-NSBJFMLG.cjs.map} +1 -1
  58. package/dist/remux-PHUHO3VV.js +10 -0
  59. package/dist/{remux-56V7LDAD.js.map → remux-PHUHO3VV.js.map} +1 -1
  60. package/package.json +1 -1
  61. package/src/element/avbridge-player.ts +123 -23
  62. package/src/element/player-styles.ts +13 -1
  63. package/src/player.ts +3 -3
  64. package/src/probe/avi.ts +34 -2
  65. package/src/strategies/fallback/decoder.ts +148 -19
  66. package/src/strategies/fallback/video-renderer.ts +41 -3
  67. package/src/strategies/hybrid/decoder.ts +34 -9
  68. package/src/types.ts +15 -0
  69. package/src/util/libav-http-reader.ts +58 -19
  70. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  71. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  72. package/vendor/libav/avbridge/libav-avbridge.mjs +1 -1
  73. package/dist/avi-2ILLBNPQ.cjs.map +0 -1
  74. package/dist/avi-B5CQYB7L.cjs.map +0 -1
  75. package/dist/avi-JXU4GQL2.js.map +0 -1
  76. package/dist/avi-RWWPN2PR.js.map +0 -1
  77. package/dist/chunk-37UOSAVI.cjs.map +0 -1
  78. package/dist/chunk-DCSOQH2N.js.map +0 -1
  79. package/dist/chunk-IHNHHEA2.js.map +0 -1
  80. package/dist/chunk-Z33SBWL5.cjs.map +0 -1
  81. package/dist/libav-http-reader-AZLE7YFS.cjs +0 -16
  82. package/dist/libav-http-reader-WXG3Z7AI.js +0 -3
  83. package/dist/remux-56V7LDAD.js +0 -10
  84. package/dist/remux-KUS5GIL6.cjs +0 -35
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkWRKO6Q42_cjs = require('./chunk-WRKO6Q42.cjs');
4
- var chunkBYGZN4Z5_cjs = require('./chunk-BYGZN4Z5.cjs');
4
+ var chunkVLI3Y6IJ_cjs = require('./chunk-VLI3Y6IJ.cjs');
5
5
  var chunk2IJ66NTD_cjs = require('./chunk-2IJ66NTD.cjs');
6
- var chunkL7A3ECI2_cjs = require('./chunk-L7A3ECI2.cjs');
6
+ var chunkHZUVMXBN_cjs = require('./chunk-HZUVMXBN.cjs');
7
7
  var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
8
8
  var chunkF3LQJKXK_cjs = require('./chunk-F3LQJKXK.cjs');
9
9
 
@@ -734,12 +734,12 @@ async function createRemuxPipeline(ctx, video) {
734
734
  const mb = await import('mediabunny');
735
735
  const videoTrackInfo = ctx.videoTracks[0];
736
736
  if (!videoTrackInfo) throw new Error("remux: source has no video track");
737
- const mbVideoCodec = chunkBYGZN4Z5_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec);
737
+ const mbVideoCodec = chunkVLI3Y6IJ_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec);
738
738
  if (!mbVideoCodec) {
739
739
  throw new Error(`remux: video codec "${videoTrackInfo.codec}" is not supported by mediabunny output`);
740
740
  }
741
741
  const input = new mb.Input({
742
- source: await chunkBYGZN4Z5_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
742
+ source: await chunkVLI3Y6IJ_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
743
743
  formats: mb.ALL_FORMATS
744
744
  });
745
745
  const allTracks = await input.getTracks();
@@ -771,7 +771,7 @@ async function createRemuxPipeline(ctx, video) {
771
771
  throw new Error("remux: audio track not found in input");
772
772
  }
773
773
  inputAudio = newInput;
774
- mbAudioCodec = chunkBYGZN4Z5_cjs.avbridgeAudioToMediabunny(trackInfo.codec);
774
+ mbAudioCodec = chunkVLI3Y6IJ_cjs.avbridgeAudioToMediabunny(trackInfo.codec);
775
775
  audioSink = new mb.EncodedPacketSink(newInput);
776
776
  audioConfig = await newInput.getDecoderConfig();
777
777
  }
@@ -1104,10 +1104,20 @@ var VideoRenderer = class {
1104
1104
  /** Resolves once the first decoded frame has been enqueued. */
1105
1105
  firstFrameReady;
1106
1106
  resolveFirstFrame;
1107
- /** True once at least one frame has been enqueued. */
1107
+ /**
1108
+ * True once at least one frame has been enqueued *since the last flush*.
1109
+ * Used by `readyState` — initial cold-start reports HAVE_NOTHING until
1110
+ * any frame has arrived, and after a seek we want the same semantics
1111
+ * (HAVE_NOTHING until post-seek frames arrive), so the cumulative
1112
+ * `framesPainted > 0` that used to live here was wrong: it kept the
1113
+ * state "true forever" after the first frame ever, so post-seek
1114
+ * `waitForBuffer()` would exit immediately with an empty queue and
1115
+ * leave video frozen while audio kept going.
1116
+ */
1108
1117
  hasFrames() {
1109
- return this.queue.length > 0 || this.framesPainted > 0;
1118
+ return this.queue.length > 0 || this.hasEverEnqueuedSinceFlush;
1110
1119
  }
1120
+ hasEverEnqueuedSinceFlush = false;
1111
1121
  /** Current depth of the frame queue. Used by the decoder for backpressure. */
1112
1122
  queueDepth() {
1113
1123
  return this.queue.length;
@@ -1126,6 +1136,7 @@ var VideoRenderer = class {
1126
1136
  return;
1127
1137
  }
1128
1138
  this.queue.push(frame);
1139
+ this.hasEverEnqueuedSinceFlush = true;
1129
1140
  if (this.queue.length === 1 && this.framesPainted === 0) {
1130
1141
  this.resolveFirstFrame();
1131
1142
  }
@@ -1259,7 +1270,8 @@ var VideoRenderer = class {
1259
1270
  }
1260
1271
  return;
1261
1272
  }
1262
- const dropThresholdUs = audioNowUs - frameDurationUs * 2;
1273
+ const _relaxDrop = globalThis.AVBRIDGE_RELAX_DROP === true;
1274
+ const dropThresholdUs = _relaxDrop ? audioNowUs - 60 * 1e6 : audioNowUs - frameDurationUs * 2;
1263
1275
  let dropped = 0;
1264
1276
  while (bestIdx > 0) {
1265
1277
  const ts = this.queue[0].timestamp ?? 0;
@@ -1320,16 +1332,28 @@ var VideoRenderer = class {
1320
1332
  while (this.queue.length > 0) this.queue.shift()?.close();
1321
1333
  this.prerolled = false;
1322
1334
  this.ptsCalibrated = false;
1335
+ this.hasEverEnqueuedSinceFlush = false;
1323
1336
  if (isDebug() && count > 0) {
1324
1337
  console.log(`[avbridge:renderer] FLUSH discarded=${count} painted=${this.framesPainted} drops=${this.framesDroppedLate}`);
1325
1338
  }
1326
1339
  }
1327
1340
  stats() {
1341
+ let queueSpanMs = 0;
1342
+ let queueHeadMs = 0;
1343
+ let queueTailMs = 0;
1344
+ if (this.queue.length > 0) {
1345
+ queueHeadMs = Math.round((this.queue[0].timestamp ?? 0) / 1e3);
1346
+ queueTailMs = Math.round((this.queue[this.queue.length - 1].timestamp ?? 0) / 1e3);
1347
+ queueSpanMs = Math.max(0, queueTailMs - queueHeadMs);
1348
+ }
1328
1349
  return {
1329
1350
  framesPainted: this.framesPainted,
1330
1351
  framesDroppedLate: this.framesDroppedLate,
1331
1352
  framesDroppedOverflow: this.framesDroppedOverflow,
1332
- queueDepth: this.queue.length
1353
+ queueDepth: this.queue.length,
1354
+ queueHeadMs,
1355
+ queueTailMs,
1356
+ queueSpanMs
1333
1357
  };
1334
1358
  }
1335
1359
  destroy() {
@@ -1612,7 +1636,7 @@ async function startHybridDecoder(opts) {
1612
1636
  const variant = chunkF3LQJKXK_cjs.pickLibavVariant(opts.context);
1613
1637
  const libav = await chunkG4APZMCP_cjs.loadLibav(variant);
1614
1638
  const bridge = await loadBridge();
1615
- const { prepareLibavInput } = await import('./libav-http-reader-AZLE7YFS.cjs');
1639
+ const { prepareLibavInput } = await import('./libav-http-reader-Q356EO2K.cjs');
1616
1640
  const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source, opts.transport);
1617
1641
  const readPkt = await libav.av_packet_alloc();
1618
1642
  const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
@@ -1687,6 +1711,7 @@ async function startHybridDecoder(opts) {
1687
1711
  }
1688
1712
  let bsfCtx = null;
1689
1713
  let bsfPkt = null;
1714
+ let bsfRequiredButMissing = false;
1690
1715
  if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
1691
1716
  try {
1692
1717
  bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
@@ -1697,13 +1722,19 @@ async function startHybridDecoder(opts) {
1697
1722
  bsfPkt = await libav.av_packet_alloc();
1698
1723
  chunkG4APZMCP_cjs.dbg.info("bsf", "mpeg4_unpack_bframes BSF active (hybrid)");
1699
1724
  } else {
1700
- console.warn("[avbridge] mpeg4_unpack_bframes BSF not available in hybrid decoder");
1725
+ bsfRequiredButMissing = true;
1701
1726
  bsfCtx = null;
1702
1727
  }
1703
1728
  } catch (err) {
1704
- console.warn("[avbridge] hybrid: failed to init BSF:", err.message);
1729
+ bsfRequiredButMissing = true;
1705
1730
  bsfCtx = null;
1706
1731
  bsfPkt = null;
1732
+ chunkG4APZMCP_cjs.dbg.warn("bsf", `hybrid: mpeg4_unpack_bframes BSF init failed: ${err.message}`);
1733
+ }
1734
+ if (bsfRequiredButMissing) {
1735
+ console.error(
1736
+ "[avbridge] MPEG-4 Part 2 (DivX/Xvid) detected but mpeg4_unpack_bframes BSF is unavailable in this libav variant. Files with packed B-frames will play with incorrect frame ordering. Rebuild the libav variant with the `avbsf` fragment included."
1737
+ );
1707
1738
  }
1708
1739
  }
1709
1740
  async function applyBSF(packets) {
@@ -1713,7 +1744,6 @@ async function startHybridDecoder(opts) {
1713
1744
  await libav.ff_copyin_packet(bsfPkt, pkt);
1714
1745
  const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
1715
1746
  if (sendErr < 0) {
1716
- out.push(pkt);
1717
1747
  continue;
1718
1748
  }
1719
1749
  while (true) {
@@ -1727,10 +1757,13 @@ async function startHybridDecoder(opts) {
1727
1757
  async function flushBSF() {
1728
1758
  if (!bsfCtx || !bsfPkt) return;
1729
1759
  try {
1730
- await libav.av_bsf_send_packet(bsfCtx, 0);
1731
- while (true) {
1732
- const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
1733
- if (err < 0) break;
1760
+ if (libav.av_bsf_flush) {
1761
+ await libav.av_bsf_flush(bsfCtx);
1762
+ } else {
1763
+ while (true) {
1764
+ const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
1765
+ if (err < 0) break;
1766
+ }
1734
1767
  }
1735
1768
  } catch {
1736
1769
  }
@@ -1765,13 +1798,13 @@ async function startHybridDecoder(opts) {
1765
1798
  const audioPackets = audioStream ? packets[audioStream.index] : void 0;
1766
1799
  if (videoPackets && videoTimeBase) {
1767
1800
  for (const pkt of videoPackets) {
1768
- const sec = chunkL7A3ECI2_cjs.packetPtsSec(pkt, videoTimeBase);
1801
+ const sec = chunkHZUVMXBN_cjs.packetPtsSec(pkt, videoTimeBase);
1769
1802
  if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
1770
1803
  }
1771
1804
  }
1772
1805
  if (audioPackets && audioTimeBase) {
1773
1806
  for (const pkt of audioPackets) {
1774
- const sec = chunkL7A3ECI2_cjs.packetPtsSec(pkt, audioTimeBase);
1807
+ const sec = chunkHZUVMXBN_cjs.packetPtsSec(pkt, audioTimeBase);
1775
1808
  if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
1776
1809
  }
1777
1810
  }
@@ -1785,7 +1818,7 @@ async function startHybridDecoder(opts) {
1785
1818
  const processed = await applyBSF(videoPackets);
1786
1819
  for (const pkt of processed) {
1787
1820
  if (myToken !== pumpToken || destroyed) return;
1788
- chunkL7A3ECI2_cjs.sanitizePacketTimestamp(pkt, () => {
1821
+ chunkHZUVMXBN_cjs.sanitizePacketTimestamp(pkt, () => {
1789
1822
  const ts = syntheticVideoUs;
1790
1823
  syntheticVideoUs += videoFrameStepUs;
1791
1824
  return ts;
@@ -1864,7 +1897,7 @@ async function startHybridDecoder(opts) {
1864
1897
  const frames = allFrames;
1865
1898
  for (const f of frames) {
1866
1899
  if (myToken !== pumpToken || destroyed) return;
1867
- chunkL7A3ECI2_cjs.sanitizeFrameTimestamp(
1900
+ chunkHZUVMXBN_cjs.sanitizeFrameTimestamp(
1868
1901
  f,
1869
1902
  () => {
1870
1903
  const ts = syntheticAudioUs;
@@ -1875,7 +1908,7 @@ async function startHybridDecoder(opts) {
1875
1908
  },
1876
1909
  audioTimeBase
1877
1910
  );
1878
- const samples = chunkL7A3ECI2_cjs.libavFrameToInterleavedFloat32(f);
1911
+ const samples = chunkHZUVMXBN_cjs.libavFrameToInterleavedFloat32(f);
1879
1912
  if (samples) {
1880
1913
  opts.audio.schedule(samples.data, samples.channels, samples.sampleRate);
1881
1914
  audioFramesDecoded++;
@@ -2042,6 +2075,7 @@ async function startHybridDecoder(opts) {
2042
2075
  videoChunksFed,
2043
2076
  audioFramesDecoded,
2044
2077
  bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
2078
+ bsfMissing: bsfRequiredButMissing ? ["mpeg4_unpack_bframes"] : [],
2045
2079
  videoDecodeQueueSize: videoDecoder?.decodeQueueSize ?? 0,
2046
2080
  // Confirmed transport info — see fallback decoder for the pattern.
2047
2081
  _transport: inputHandle.transport === "http-range" ? "http-range" : "memory",
@@ -2282,7 +2316,7 @@ async function startDecoder(opts) {
2282
2316
  const variant = "avbridge";
2283
2317
  const libav = await chunkG4APZMCP_cjs.loadLibav(variant);
2284
2318
  const bridge = await loadBridge2();
2285
- const { prepareLibavInput } = await import('./libav-http-reader-AZLE7YFS.cjs');
2319
+ const { prepareLibavInput } = await import('./libav-http-reader-Q356EO2K.cjs');
2286
2320
  const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source, opts.transport);
2287
2321
  const readPkt = await libav.av_packet_alloc();
2288
2322
  const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
@@ -2341,6 +2375,7 @@ async function startDecoder(opts) {
2341
2375
  }
2342
2376
  let bsfCtx = null;
2343
2377
  let bsfPkt = null;
2378
+ let bsfRequiredButMissing = false;
2344
2379
  if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
2345
2380
  try {
2346
2381
  bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
@@ -2351,13 +2386,19 @@ async function startDecoder(opts) {
2351
2386
  bsfPkt = await libav.av_packet_alloc();
2352
2387
  chunkG4APZMCP_cjs.dbg.info("bsf", "mpeg4_unpack_bframes BSF active");
2353
2388
  } else {
2354
- console.warn("[avbridge] mpeg4_unpack_bframes BSF not available \u2014 decoding without it");
2389
+ bsfRequiredButMissing = true;
2355
2390
  bsfCtx = null;
2356
2391
  }
2357
2392
  } catch (err) {
2358
- console.warn("[avbridge] failed to init mpeg4_unpack_bframes BSF:", err.message);
2393
+ bsfRequiredButMissing = true;
2359
2394
  bsfCtx = null;
2360
2395
  bsfPkt = null;
2396
+ chunkG4APZMCP_cjs.dbg.warn("bsf", `mpeg4_unpack_bframes BSF init failed: ${err.message}`);
2397
+ }
2398
+ if (bsfRequiredButMissing) {
2399
+ console.error(
2400
+ "[avbridge] MPEG-4 Part 2 (DivX/Xvid) detected but mpeg4_unpack_bframes BSF is unavailable in this libav variant. Files with packed B-frames will play with incorrect frame ordering (backwards PTS jumps, heavy late-drop stuttering). Rebuild the libav variant with the `avbsf` fragment included. See docs/dev/POSTMORTEMS.md for details."
2401
+ );
2361
2402
  }
2362
2403
  }
2363
2404
  async function applyBSF(packets) {
@@ -2367,7 +2408,6 @@ async function startDecoder(opts) {
2367
2408
  await libav.ff_copyin_packet(bsfPkt, pkt);
2368
2409
  const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
2369
2410
  if (sendErr < 0) {
2370
- out.push(pkt);
2371
2411
  continue;
2372
2412
  }
2373
2413
  while (true) {
@@ -2381,10 +2421,13 @@ async function startDecoder(opts) {
2381
2421
  async function flushBSF() {
2382
2422
  if (!bsfCtx || !bsfPkt) return;
2383
2423
  try {
2384
- await libav.av_bsf_send_packet(bsfCtx, 0);
2385
- while (true) {
2386
- const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
2387
- if (err < 0) break;
2424
+ if (libav.av_bsf_flush) {
2425
+ await libav.av_bsf_flush(bsfCtx);
2426
+ } else {
2427
+ while (true) {
2428
+ const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
2429
+ if (err < 0) break;
2430
+ }
2388
2431
  }
2389
2432
  } catch {
2390
2433
  }
@@ -2402,6 +2445,19 @@ async function startDecoder(opts) {
2402
2445
  let watchdogOverflowWarned = false;
2403
2446
  let syntheticVideoUs = 0;
2404
2447
  let syntheticAudioUs = 0;
2448
+ let videoDecodeMsTotal = 0;
2449
+ let audioDecodeMsTotal = 0;
2450
+ let videoDecodeBatches = 0;
2451
+ let audioDecodeBatches = 0;
2452
+ let readMsTotal = 0;
2453
+ let readBatches = 0;
2454
+ let pumpThrottleMsTotal = 0;
2455
+ let pumpThrottleEntries = 0;
2456
+ let slowestVideoBatchMs = 0;
2457
+ let newestVideoPtsUs = 0;
2458
+ let lastEmittedPtsUs = -1;
2459
+ let ptsRegressions = 0;
2460
+ let worstPtsRegressionMs = 0;
2405
2461
  const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
2406
2462
  const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;
2407
2463
  const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
@@ -2410,9 +2466,12 @@ async function startDecoder(opts) {
2410
2466
  let readErr;
2411
2467
  let packets;
2412
2468
  try {
2469
+ const _readStart = performance.now();
2413
2470
  [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
2414
2471
  limit: 16 * 1024
2415
2472
  });
2473
+ readMsTotal += performance.now() - _readStart;
2474
+ readBatches++;
2416
2475
  } catch (err) {
2417
2476
  console.error("[avbridge] ff_read_frame_multi failed:", err);
2418
2477
  return;
@@ -2422,13 +2481,13 @@ async function startDecoder(opts) {
2422
2481
  const audioPackets = audioStream ? packets[audioStream.index] : void 0;
2423
2482
  if (videoPackets && videoTimeBase) {
2424
2483
  for (const pkt of videoPackets) {
2425
- const sec = chunkL7A3ECI2_cjs.packetPtsSec(pkt, videoTimeBase);
2484
+ const sec = chunkHZUVMXBN_cjs.packetPtsSec(pkt, videoTimeBase);
2426
2485
  if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
2427
2486
  }
2428
2487
  }
2429
2488
  if (audioPackets && audioTimeBase) {
2430
2489
  for (const pkt of audioPackets) {
2431
- const sec = chunkL7A3ECI2_cjs.packetPtsSec(pkt, audioTimeBase);
2490
+ const sec = chunkHZUVMXBN_cjs.packetPtsSec(pkt, audioTimeBase);
2432
2491
  if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
2433
2492
  }
2434
2493
  }
@@ -2474,8 +2533,17 @@ async function startDecoder(opts) {
2474
2533
  }
2475
2534
  }
2476
2535
  }
2477
- while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
2478
- await new Promise((r) => setTimeout(r, 50));
2536
+ {
2537
+ const _throttleStart = performance.now();
2538
+ let _throttled = false;
2539
+ while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
2540
+ _throttled = true;
2541
+ await new Promise((r) => setTimeout(r, 50));
2542
+ }
2543
+ if (_throttled) {
2544
+ pumpThrottleMsTotal += performance.now() - _throttleStart;
2545
+ pumpThrottleEntries++;
2546
+ }
2479
2547
  }
2480
2548
  if (readErr === libav.AVERROR_EOF) {
2481
2549
  if (videoDec) await decodeVideoBatch(
@@ -2501,6 +2569,7 @@ async function startDecoder(opts) {
2501
2569
  async function decodeVideoBatch(pkts, myToken, flush = false) {
2502
2570
  if (!videoDec || destroyed || myToken !== pumpToken) return;
2503
2571
  let frames;
2572
+ const _t0 = performance.now();
2504
2573
  try {
2505
2574
  frames = await libav.ff_decode_multi(
2506
2575
  videoDec.c,
@@ -2513,18 +2582,38 @@ async function startDecoder(opts) {
2513
2582
  console.error("[avbridge] video decode batch failed:", err);
2514
2583
  return;
2515
2584
  }
2585
+ {
2586
+ const _dt = performance.now() - _t0;
2587
+ videoDecodeMsTotal += _dt;
2588
+ videoDecodeBatches++;
2589
+ if (_dt > slowestVideoBatchMs) slowestVideoBatchMs = _dt;
2590
+ }
2516
2591
  if (myToken !== pumpToken || destroyed) return;
2517
2592
  for (const f of frames) {
2518
2593
  if (myToken !== pumpToken || destroyed) return;
2519
- chunkL7A3ECI2_cjs.sanitizeFrameTimestamp(
2594
+ chunkHZUVMXBN_cjs.sanitizeFrameTimestamp(
2520
2595
  f,
2521
2596
  () => {
2522
- const ts = syntheticVideoUs;
2523
- syntheticVideoUs += videoFrameStepUs;
2524
- return ts;
2597
+ const base = lastEmittedPtsUs >= 0 ? lastEmittedPtsUs + videoFrameStepUs : syntheticVideoUs;
2598
+ syntheticVideoUs = base + videoFrameStepUs;
2599
+ return base;
2525
2600
  },
2526
2601
  videoTimeBase
2527
2602
  );
2603
+ const _fPts = (f.ptshi ?? 0) * 4294967296 + (f.pts ?? 0);
2604
+ if (_fPts > newestVideoPtsUs) newestVideoPtsUs = _fPts;
2605
+ if (lastEmittedPtsUs >= 0 && _fPts < lastEmittedPtsUs) {
2606
+ ptsRegressions++;
2607
+ const regressMs = (lastEmittedPtsUs - _fPts) / 1e3;
2608
+ if (regressMs > worstPtsRegressionMs) worstPtsRegressionMs = regressMs;
2609
+ if (ptsRegressions <= 10) {
2610
+ console.warn(
2611
+ `[avbridge:decoder] dropped out-of-order frame #${ptsRegressions}: pts=${(_fPts / 1e3).toFixed(1)}ms < previous=${(lastEmittedPtsUs / 1e3).toFixed(1)}ms (regression=${regressMs.toFixed(1)}ms). Typically a post-seek B-frame reorder tail.`
2612
+ );
2613
+ }
2614
+ continue;
2615
+ }
2616
+ lastEmittedPtsUs = _fPts;
2528
2617
  try {
2529
2618
  const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
2530
2619
  opts.renderer.enqueue(vf);
@@ -2539,6 +2628,7 @@ async function startDecoder(opts) {
2539
2628
  async function decodeAudioBatch(pkts, myToken, flush = false) {
2540
2629
  if (!audioDec || destroyed || myToken !== pumpToken) return;
2541
2630
  let frames;
2631
+ const _t0 = performance.now();
2542
2632
  try {
2543
2633
  frames = await libav.ff_decode_multi(
2544
2634
  audioDec.c,
@@ -2551,10 +2641,12 @@ async function startDecoder(opts) {
2551
2641
  console.error("[avbridge] audio decode batch failed:", err);
2552
2642
  return;
2553
2643
  }
2644
+ audioDecodeMsTotal += performance.now() - _t0;
2645
+ audioDecodeBatches++;
2554
2646
  if (myToken !== pumpToken || destroyed) return;
2555
2647
  for (const f of frames) {
2556
2648
  if (myToken !== pumpToken || destroyed) return;
2557
- chunkL7A3ECI2_cjs.sanitizeFrameTimestamp(
2649
+ chunkHZUVMXBN_cjs.sanitizeFrameTimestamp(
2558
2650
  f,
2559
2651
  () => {
2560
2652
  const ts = syntheticAudioUs;
@@ -2565,7 +2657,7 @@ async function startDecoder(opts) {
2565
2657
  },
2566
2658
  audioTimeBase
2567
2659
  );
2568
- const samples = chunkL7A3ECI2_cjs.libavFrameToInterleavedFloat32(f);
2660
+ const samples = chunkHZUVMXBN_cjs.libavFrameToInterleavedFloat32(f);
2569
2661
  if (samples) {
2570
2662
  opts.audio.schedule(samples.data, samples.channels, samples.sampleRate);
2571
2663
  audioFramesDecoded++;
@@ -2672,6 +2764,7 @@ async function startDecoder(opts) {
2672
2764
  await flushBSF();
2673
2765
  syntheticVideoUs = Math.round(timeSec * 1e6);
2674
2766
  syntheticAudioUs = Math.round(timeSec * 1e6);
2767
+ lastEmittedPtsUs = -1;
2675
2768
  pumpRunning = pumpLoop(newToken).catch(
2676
2769
  (err) => console.error("[avbridge] fallback pump failed (post-setAudioTrack):", err)
2677
2770
  );
@@ -2709,6 +2802,7 @@ async function startDecoder(opts) {
2709
2802
  await flushBSF();
2710
2803
  syntheticVideoUs = Math.round(timeSec * 1e6);
2711
2804
  syntheticAudioUs = Math.round(timeSec * 1e6);
2805
+ lastEmittedPtsUs = -1;
2712
2806
  pumpRunning = pumpLoop(newToken).catch(
2713
2807
  (err) => console.error("[avbridge] decoder pump failed (post-seek):", err)
2714
2808
  );
@@ -2722,7 +2816,24 @@ async function startDecoder(opts) {
2722
2816
  packetsRead,
2723
2817
  videoFramesDecoded,
2724
2818
  audioFramesDecoded,
2819
+ // Throughput instrumentation — the stats panel turns these into
2820
+ // "decode fps actual / realtime target" and shows slowest batch
2821
+ // + producer throttle share.
2822
+ videoDecodeMsTotal,
2823
+ videoDecodeBatches,
2824
+ audioDecodeMsTotal,
2825
+ audioDecodeBatches,
2826
+ readMsTotal,
2827
+ readBatches,
2828
+ pumpThrottleMsTotal,
2829
+ pumpThrottleEntries,
2830
+ slowestVideoBatchMs,
2831
+ newestVideoPtsMs: Math.round(newestVideoPtsUs / 1e3),
2832
+ ptsRegressions,
2833
+ worstPtsRegressionMs,
2834
+ sourceFps: videoFps,
2725
2835
  bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
2836
+ bsfMissing: bsfRequiredButMissing ? ["mpeg4_unpack_bframes"] : [],
2726
2837
  // Confirmed transport info: once prepareLibavInput returns
2727
2838
  // successfully, we *know* whether the source is http-range (probe
2728
2839
  // succeeded and returned 206) or in-memory blob. Diagnostics hoists
@@ -3011,9 +3122,9 @@ var UnifiedPlayer = class _UnifiedPlayer {
3011
3122
  constructor(options, registry) {
3012
3123
  this.options = options;
3013
3124
  this.registry = registry;
3014
- const { requestInit, fetchFn } = options;
3015
- if (requestInit || fetchFn) {
3016
- this.transport = { requestInit, fetchFn };
3125
+ const { requestInit, fetchFn, cacheBytes } = options;
3126
+ if (requestInit || fetchFn || cacheBytes !== void 0) {
3127
+ this.transport = { requestInit, fetchFn, cacheBytes };
3017
3128
  }
3018
3129
  }
3019
3130
  options;
@@ -3081,7 +3192,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
3081
3192
  const bootstrapStart = performance.now();
3082
3193
  try {
3083
3194
  chunkG4APZMCP_cjs.dbg.info("bootstrap", "start");
3084
- const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => chunkBYGZN4Z5_cjs.probe(this.options.source, this.transport));
3195
+ const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => chunkVLI3Y6IJ_cjs.probe(this.options.source, this.transport));
3085
3196
  chunkG4APZMCP_cjs.dbg.info(
3086
3197
  "probe",
3087
3198
  `container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
@@ -3551,5 +3662,5 @@ exports.NATIVE_VIDEO_CODECS = NATIVE_VIDEO_CODECS;
3551
3662
  exports.UnifiedPlayer = UnifiedPlayer;
3552
3663
  exports.classifyContext = classifyContext;
3553
3664
  exports.createPlayer = createPlayer;
3554
- //# sourceMappingURL=chunk-37UOSAVI.cjs.map
3555
- //# sourceMappingURL=chunk-37UOSAVI.cjs.map
3665
+ //# sourceMappingURL=chunk-UM6WCSGL.cjs.map
3666
+ //# sourceMappingURL=chunk-UM6WCSGL.cjs.map