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.
- package/CHANGELOG.md +111 -0
- package/dist/{avi-B5CQYB7L.cjs → avi-32UABODO.cjs} +14 -6
- package/dist/avi-32UABODO.cjs.map +1 -0
- package/dist/{avi-2ILLBNPQ.cjs → avi-5BPR6QUX.cjs} +14 -6
- package/dist/avi-5BPR6QUX.cjs.map +1 -0
- package/dist/{avi-RWWPN2PR.js → avi-BLIH7KKV.js} +13 -5
- package/dist/avi-BLIH7KKV.js.map +1 -0
- package/dist/{avi-JXU4GQL2.js → avi-GX2H34IQ.js} +13 -5
- package/dist/avi-GX2H34IQ.js.map +1 -0
- package/dist/{chunk-DCSOQH2N.js → chunk-3AI5WFFN.js} +40 -16
- package/dist/chunk-3AI5WFFN.js.map +1 -0
- package/dist/{chunk-2NSOOMXW.js → chunk-3YKWU4FM.js} +3 -3
- package/dist/{chunk-2NSOOMXW.js.map → chunk-3YKWU4FM.js.map} +1 -1
- package/dist/{chunk-GYIJU44C.js → chunk-5CX7BVVV.js} +5 -5
- package/dist/{chunk-GYIJU44C.js.map → chunk-5CX7BVVV.js.map} +1 -1
- package/dist/{chunk-CL6UEUQF.js → chunk-B76QWPFM.js} +5 -5
- package/dist/{chunk-CL6UEUQF.js.map → chunk-B76QWPFM.js.map} +1 -1
- package/dist/{chunk-IHNHHEA2.js → chunk-BN7BRTLY.js} +143 -32
- package/dist/chunk-BN7BRTLY.js.map +1 -0
- package/dist/{chunk-OTFS7DC4.cjs → chunk-E5MAM2P4.cjs} +14 -14
- package/dist/{chunk-OTFS7DC4.cjs.map → chunk-E5MAM2P4.cjs.map} +1 -1
- package/dist/{chunk-L7A3ECI2.cjs → chunk-HZUVMXBN.cjs} +4 -4
- package/dist/{chunk-L7A3ECI2.cjs.map → chunk-HZUVMXBN.cjs.map} +1 -1
- package/dist/{chunk-37UOSAVI.cjs → chunk-UM6WCSGL.cjs} +157 -46
- package/dist/chunk-UM6WCSGL.cjs.map +1 -0
- package/dist/{chunk-BYGZN4Z5.cjs → chunk-VLI3Y6IJ.cjs} +5 -5
- package/dist/{chunk-BYGZN4Z5.cjs.map → chunk-VLI3Y6IJ.cjs.map} +1 -1
- package/dist/{chunk-Z33SBWL5.cjs → chunk-YPZFGJV3.cjs} +40 -16
- package/dist/chunk-YPZFGJV3.cjs.map +1 -0
- package/dist/element-browser.js +186 -43
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +5 -5
- package/dist/element.d.cts +1 -1
- package/dist/element.d.ts +1 -1
- package/dist/element.js +4 -4
- package/dist/index.cjs +21 -21
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +9 -9
- package/dist/{libav-demux-3N5Y3VQA.cjs → libav-demux-575OYCT2.cjs} +9 -9
- package/dist/{libav-demux-3N5Y3VQA.cjs.map → libav-demux-575OYCT2.cjs.map} +1 -1
- package/dist/{libav-demux-JXD4OTLM.js → libav-demux-SXZDLC7W.js} +4 -4
- package/dist/{libav-demux-JXD4OTLM.js.map → libav-demux-SXZDLC7W.js.map} +1 -1
- package/dist/libav-http-reader-2S5HAHW4.js +3 -0
- package/dist/{libav-http-reader-WXG3Z7AI.js.map → libav-http-reader-2S5HAHW4.js.map} +1 -1
- package/dist/libav-http-reader-Q356EO2K.cjs +16 -0
- package/dist/{libav-http-reader-AZLE7YFS.cjs.map → libav-http-reader-Q356EO2K.cjs.map} +1 -1
- package/dist/{player-DDdNVFDv.d.cts → player-bQ6n4hVp.d.cts} +15 -0
- package/dist/{player-DDdNVFDv.d.ts → player-bQ6n4hVp.d.ts} +15 -0
- package/dist/player.cjs +264 -53
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +22 -0
- package/dist/player.d.ts +22 -0
- package/dist/player.js +264 -53
- package/dist/player.js.map +1 -1
- package/dist/remux-NSBJFMLG.cjs +35 -0
- package/dist/{remux-KUS5GIL6.cjs.map → remux-NSBJFMLG.cjs.map} +1 -1
- package/dist/remux-PHUHO3VV.js +10 -0
- package/dist/{remux-56V7LDAD.js.map → remux-PHUHO3VV.js.map} +1 -1
- package/package.json +1 -1
- package/src/element/avbridge-player.ts +123 -23
- package/src/element/player-styles.ts +13 -1
- package/src/player.ts +3 -3
- package/src/probe/avi.ts +34 -2
- package/src/strategies/fallback/decoder.ts +148 -19
- package/src/strategies/fallback/video-renderer.ts +41 -3
- package/src/strategies/hybrid/decoder.ts +34 -9
- package/src/types.ts +15 -0
- package/src/util/libav-http-reader.ts +58 -19
- 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/vendor/libav/avbridge/libav-avbridge.mjs +1 -1
- package/dist/avi-2ILLBNPQ.cjs.map +0 -1
- package/dist/avi-B5CQYB7L.cjs.map +0 -1
- package/dist/avi-JXU4GQL2.js.map +0 -1
- package/dist/avi-RWWPN2PR.js.map +0 -1
- package/dist/chunk-37UOSAVI.cjs.map +0 -1
- package/dist/chunk-DCSOQH2N.js.map +0 -1
- package/dist/chunk-IHNHHEA2.js.map +0 -1
- package/dist/chunk-Z33SBWL5.cjs.map +0 -1
- package/dist/libav-http-reader-AZLE7YFS.cjs +0 -16
- package/dist/libav-http-reader-WXG3Z7AI.js +0 -3
- package/dist/remux-56V7LDAD.js +0 -10
- 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
|
|
4
|
+
var chunkVLI3Y6IJ_cjs = require('./chunk-VLI3Y6IJ.cjs');
|
|
5
5
|
var chunk2IJ66NTD_cjs = require('./chunk-2IJ66NTD.cjs');
|
|
6
|
-
var
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
/**
|
|
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.
|
|
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
|
|
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-
|
|
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
|
-
|
|
1725
|
+
bsfRequiredButMissing = true;
|
|
1701
1726
|
bsfCtx = null;
|
|
1702
1727
|
}
|
|
1703
1728
|
} catch (err) {
|
|
1704
|
-
|
|
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
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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-
|
|
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
|
-
|
|
2389
|
+
bsfRequiredButMissing = true;
|
|
2355
2390
|
bsfCtx = null;
|
|
2356
2391
|
}
|
|
2357
2392
|
} catch (err) {
|
|
2358
|
-
|
|
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
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
2478
|
-
|
|
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
|
-
|
|
2594
|
+
chunkHZUVMXBN_cjs.sanitizeFrameTimestamp(
|
|
2520
2595
|
f,
|
|
2521
2596
|
() => {
|
|
2522
|
-
const
|
|
2523
|
-
syntheticVideoUs
|
|
2524
|
-
return
|
|
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
|
-
|
|
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 =
|
|
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, () =>
|
|
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-
|
|
3555
|
-
//# sourceMappingURL=chunk-
|
|
3665
|
+
//# sourceMappingURL=chunk-UM6WCSGL.cjs.map
|
|
3666
|
+
//# sourceMappingURL=chunk-UM6WCSGL.cjs.map
|