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,7 +1,7 @@
|
|
|
1
1
|
import { SubtitleResourceBag, discoverSidecars, attachSubtitleTracks, SubtitleOverlay } from './chunk-EDDWAN2L.js';
|
|
2
|
-
import { probe, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, avbridgeAudioToMediabunny } from './chunk-
|
|
2
|
+
import { probe, avbridgeVideoToMediabunny, buildMediabunnySourceFromInput, avbridgeAudioToMediabunny } from './chunk-5CX7BVVV.js';
|
|
3
3
|
import { AvbridgeError, ERR_ALL_STRATEGIES_EXHAUSTED, ERR_PLAYER_NOT_READY, ERR_MSE_NOT_SUPPORTED, ERR_MSE_CODEC_NOT_SUPPORTED } from './chunk-CPJLFFCC.js';
|
|
4
|
-
import { packetPtsSec, sanitizePacketTimestamp, sanitizeFrameTimestamp, libavFrameToInterleavedFloat32 } from './chunk-
|
|
4
|
+
import { packetPtsSec, sanitizePacketTimestamp, sanitizeFrameTimestamp, libavFrameToInterleavedFloat32 } from './chunk-3YKWU4FM.js';
|
|
5
5
|
import { dbg, loadLibav } from './chunk-5DMTJVIU.js';
|
|
6
6
|
import { pickLibavVariant } from './chunk-5YAWWKA3.js';
|
|
7
7
|
|
|
@@ -1102,10 +1102,20 @@ var VideoRenderer = class {
|
|
|
1102
1102
|
/** Resolves once the first decoded frame has been enqueued. */
|
|
1103
1103
|
firstFrameReady;
|
|
1104
1104
|
resolveFirstFrame;
|
|
1105
|
-
/**
|
|
1105
|
+
/**
|
|
1106
|
+
* True once at least one frame has been enqueued *since the last flush*.
|
|
1107
|
+
* Used by `readyState` — initial cold-start reports HAVE_NOTHING until
|
|
1108
|
+
* any frame has arrived, and after a seek we want the same semantics
|
|
1109
|
+
* (HAVE_NOTHING until post-seek frames arrive), so the cumulative
|
|
1110
|
+
* `framesPainted > 0` that used to live here was wrong: it kept the
|
|
1111
|
+
* state "true forever" after the first frame ever, so post-seek
|
|
1112
|
+
* `waitForBuffer()` would exit immediately with an empty queue and
|
|
1113
|
+
* leave video frozen while audio kept going.
|
|
1114
|
+
*/
|
|
1106
1115
|
hasFrames() {
|
|
1107
|
-
return this.queue.length > 0 || this.
|
|
1116
|
+
return this.queue.length > 0 || this.hasEverEnqueuedSinceFlush;
|
|
1108
1117
|
}
|
|
1118
|
+
hasEverEnqueuedSinceFlush = false;
|
|
1109
1119
|
/** Current depth of the frame queue. Used by the decoder for backpressure. */
|
|
1110
1120
|
queueDepth() {
|
|
1111
1121
|
return this.queue.length;
|
|
@@ -1124,6 +1134,7 @@ var VideoRenderer = class {
|
|
|
1124
1134
|
return;
|
|
1125
1135
|
}
|
|
1126
1136
|
this.queue.push(frame);
|
|
1137
|
+
this.hasEverEnqueuedSinceFlush = true;
|
|
1127
1138
|
if (this.queue.length === 1 && this.framesPainted === 0) {
|
|
1128
1139
|
this.resolveFirstFrame();
|
|
1129
1140
|
}
|
|
@@ -1257,7 +1268,8 @@ var VideoRenderer = class {
|
|
|
1257
1268
|
}
|
|
1258
1269
|
return;
|
|
1259
1270
|
}
|
|
1260
|
-
const
|
|
1271
|
+
const _relaxDrop = globalThis.AVBRIDGE_RELAX_DROP === true;
|
|
1272
|
+
const dropThresholdUs = _relaxDrop ? audioNowUs - 60 * 1e6 : audioNowUs - frameDurationUs * 2;
|
|
1261
1273
|
let dropped = 0;
|
|
1262
1274
|
while (bestIdx > 0) {
|
|
1263
1275
|
const ts = this.queue[0].timestamp ?? 0;
|
|
@@ -1318,16 +1330,28 @@ var VideoRenderer = class {
|
|
|
1318
1330
|
while (this.queue.length > 0) this.queue.shift()?.close();
|
|
1319
1331
|
this.prerolled = false;
|
|
1320
1332
|
this.ptsCalibrated = false;
|
|
1333
|
+
this.hasEverEnqueuedSinceFlush = false;
|
|
1321
1334
|
if (isDebug() && count > 0) {
|
|
1322
1335
|
console.log(`[avbridge:renderer] FLUSH discarded=${count} painted=${this.framesPainted} drops=${this.framesDroppedLate}`);
|
|
1323
1336
|
}
|
|
1324
1337
|
}
|
|
1325
1338
|
stats() {
|
|
1339
|
+
let queueSpanMs = 0;
|
|
1340
|
+
let queueHeadMs = 0;
|
|
1341
|
+
let queueTailMs = 0;
|
|
1342
|
+
if (this.queue.length > 0) {
|
|
1343
|
+
queueHeadMs = Math.round((this.queue[0].timestamp ?? 0) / 1e3);
|
|
1344
|
+
queueTailMs = Math.round((this.queue[this.queue.length - 1].timestamp ?? 0) / 1e3);
|
|
1345
|
+
queueSpanMs = Math.max(0, queueTailMs - queueHeadMs);
|
|
1346
|
+
}
|
|
1326
1347
|
return {
|
|
1327
1348
|
framesPainted: this.framesPainted,
|
|
1328
1349
|
framesDroppedLate: this.framesDroppedLate,
|
|
1329
1350
|
framesDroppedOverflow: this.framesDroppedOverflow,
|
|
1330
|
-
queueDepth: this.queue.length
|
|
1351
|
+
queueDepth: this.queue.length,
|
|
1352
|
+
queueHeadMs,
|
|
1353
|
+
queueTailMs,
|
|
1354
|
+
queueSpanMs
|
|
1331
1355
|
};
|
|
1332
1356
|
}
|
|
1333
1357
|
destroy() {
|
|
@@ -1610,7 +1634,7 @@ async function startHybridDecoder(opts) {
|
|
|
1610
1634
|
const variant = pickLibavVariant(opts.context);
|
|
1611
1635
|
const libav = await loadLibav(variant);
|
|
1612
1636
|
const bridge = await loadBridge();
|
|
1613
|
-
const { prepareLibavInput } = await import('./libav-http-reader-
|
|
1637
|
+
const { prepareLibavInput } = await import('./libav-http-reader-2S5HAHW4.js');
|
|
1614
1638
|
const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source, opts.transport);
|
|
1615
1639
|
const readPkt = await libav.av_packet_alloc();
|
|
1616
1640
|
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
|
|
@@ -1685,6 +1709,7 @@ async function startHybridDecoder(opts) {
|
|
|
1685
1709
|
}
|
|
1686
1710
|
let bsfCtx = null;
|
|
1687
1711
|
let bsfPkt = null;
|
|
1712
|
+
let bsfRequiredButMissing = false;
|
|
1688
1713
|
if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
|
|
1689
1714
|
try {
|
|
1690
1715
|
bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
@@ -1695,13 +1720,19 @@ async function startHybridDecoder(opts) {
|
|
|
1695
1720
|
bsfPkt = await libav.av_packet_alloc();
|
|
1696
1721
|
dbg.info("bsf", "mpeg4_unpack_bframes BSF active (hybrid)");
|
|
1697
1722
|
} else {
|
|
1698
|
-
|
|
1723
|
+
bsfRequiredButMissing = true;
|
|
1699
1724
|
bsfCtx = null;
|
|
1700
1725
|
}
|
|
1701
1726
|
} catch (err) {
|
|
1702
|
-
|
|
1727
|
+
bsfRequiredButMissing = true;
|
|
1703
1728
|
bsfCtx = null;
|
|
1704
1729
|
bsfPkt = null;
|
|
1730
|
+
dbg.warn("bsf", `hybrid: mpeg4_unpack_bframes BSF init failed: ${err.message}`);
|
|
1731
|
+
}
|
|
1732
|
+
if (bsfRequiredButMissing) {
|
|
1733
|
+
console.error(
|
|
1734
|
+
"[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."
|
|
1735
|
+
);
|
|
1705
1736
|
}
|
|
1706
1737
|
}
|
|
1707
1738
|
async function applyBSF(packets) {
|
|
@@ -1711,7 +1742,6 @@ async function startHybridDecoder(opts) {
|
|
|
1711
1742
|
await libav.ff_copyin_packet(bsfPkt, pkt);
|
|
1712
1743
|
const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
|
|
1713
1744
|
if (sendErr < 0) {
|
|
1714
|
-
out.push(pkt);
|
|
1715
1745
|
continue;
|
|
1716
1746
|
}
|
|
1717
1747
|
while (true) {
|
|
@@ -1725,10 +1755,13 @@ async function startHybridDecoder(opts) {
|
|
|
1725
1755
|
async function flushBSF() {
|
|
1726
1756
|
if (!bsfCtx || !bsfPkt) return;
|
|
1727
1757
|
try {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1758
|
+
if (libav.av_bsf_flush) {
|
|
1759
|
+
await libav.av_bsf_flush(bsfCtx);
|
|
1760
|
+
} else {
|
|
1761
|
+
while (true) {
|
|
1762
|
+
const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
1763
|
+
if (err < 0) break;
|
|
1764
|
+
}
|
|
1732
1765
|
}
|
|
1733
1766
|
} catch {
|
|
1734
1767
|
}
|
|
@@ -2040,6 +2073,7 @@ async function startHybridDecoder(opts) {
|
|
|
2040
2073
|
videoChunksFed,
|
|
2041
2074
|
audioFramesDecoded,
|
|
2042
2075
|
bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
|
|
2076
|
+
bsfMissing: bsfRequiredButMissing ? ["mpeg4_unpack_bframes"] : [],
|
|
2043
2077
|
videoDecodeQueueSize: videoDecoder?.decodeQueueSize ?? 0,
|
|
2044
2078
|
// Confirmed transport info — see fallback decoder for the pattern.
|
|
2045
2079
|
_transport: inputHandle.transport === "http-range" ? "http-range" : "memory",
|
|
@@ -2280,7 +2314,7 @@ async function startDecoder(opts) {
|
|
|
2280
2314
|
const variant = "avbridge";
|
|
2281
2315
|
const libav = await loadLibav(variant);
|
|
2282
2316
|
const bridge = await loadBridge2();
|
|
2283
|
-
const { prepareLibavInput } = await import('./libav-http-reader-
|
|
2317
|
+
const { prepareLibavInput } = await import('./libav-http-reader-2S5HAHW4.js');
|
|
2284
2318
|
const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source, opts.transport);
|
|
2285
2319
|
const readPkt = await libav.av_packet_alloc();
|
|
2286
2320
|
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
|
|
@@ -2339,6 +2373,7 @@ async function startDecoder(opts) {
|
|
|
2339
2373
|
}
|
|
2340
2374
|
let bsfCtx = null;
|
|
2341
2375
|
let bsfPkt = null;
|
|
2376
|
+
let bsfRequiredButMissing = false;
|
|
2342
2377
|
if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
|
|
2343
2378
|
try {
|
|
2344
2379
|
bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
@@ -2349,13 +2384,19 @@ async function startDecoder(opts) {
|
|
|
2349
2384
|
bsfPkt = await libav.av_packet_alloc();
|
|
2350
2385
|
dbg.info("bsf", "mpeg4_unpack_bframes BSF active");
|
|
2351
2386
|
} else {
|
|
2352
|
-
|
|
2387
|
+
bsfRequiredButMissing = true;
|
|
2353
2388
|
bsfCtx = null;
|
|
2354
2389
|
}
|
|
2355
2390
|
} catch (err) {
|
|
2356
|
-
|
|
2391
|
+
bsfRequiredButMissing = true;
|
|
2357
2392
|
bsfCtx = null;
|
|
2358
2393
|
bsfPkt = null;
|
|
2394
|
+
dbg.warn("bsf", `mpeg4_unpack_bframes BSF init failed: ${err.message}`);
|
|
2395
|
+
}
|
|
2396
|
+
if (bsfRequiredButMissing) {
|
|
2397
|
+
console.error(
|
|
2398
|
+
"[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."
|
|
2399
|
+
);
|
|
2359
2400
|
}
|
|
2360
2401
|
}
|
|
2361
2402
|
async function applyBSF(packets) {
|
|
@@ -2365,7 +2406,6 @@ async function startDecoder(opts) {
|
|
|
2365
2406
|
await libav.ff_copyin_packet(bsfPkt, pkt);
|
|
2366
2407
|
const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
|
|
2367
2408
|
if (sendErr < 0) {
|
|
2368
|
-
out.push(pkt);
|
|
2369
2409
|
continue;
|
|
2370
2410
|
}
|
|
2371
2411
|
while (true) {
|
|
@@ -2379,10 +2419,13 @@ async function startDecoder(opts) {
|
|
|
2379
2419
|
async function flushBSF() {
|
|
2380
2420
|
if (!bsfCtx || !bsfPkt) return;
|
|
2381
2421
|
try {
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2422
|
+
if (libav.av_bsf_flush) {
|
|
2423
|
+
await libav.av_bsf_flush(bsfCtx);
|
|
2424
|
+
} else {
|
|
2425
|
+
while (true) {
|
|
2426
|
+
const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
2427
|
+
if (err < 0) break;
|
|
2428
|
+
}
|
|
2386
2429
|
}
|
|
2387
2430
|
} catch {
|
|
2388
2431
|
}
|
|
@@ -2400,6 +2443,19 @@ async function startDecoder(opts) {
|
|
|
2400
2443
|
let watchdogOverflowWarned = false;
|
|
2401
2444
|
let syntheticVideoUs = 0;
|
|
2402
2445
|
let syntheticAudioUs = 0;
|
|
2446
|
+
let videoDecodeMsTotal = 0;
|
|
2447
|
+
let audioDecodeMsTotal = 0;
|
|
2448
|
+
let videoDecodeBatches = 0;
|
|
2449
|
+
let audioDecodeBatches = 0;
|
|
2450
|
+
let readMsTotal = 0;
|
|
2451
|
+
let readBatches = 0;
|
|
2452
|
+
let pumpThrottleMsTotal = 0;
|
|
2453
|
+
let pumpThrottleEntries = 0;
|
|
2454
|
+
let slowestVideoBatchMs = 0;
|
|
2455
|
+
let newestVideoPtsUs = 0;
|
|
2456
|
+
let lastEmittedPtsUs = -1;
|
|
2457
|
+
let ptsRegressions = 0;
|
|
2458
|
+
let worstPtsRegressionMs = 0;
|
|
2403
2459
|
const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
|
|
2404
2460
|
const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;
|
|
2405
2461
|
const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
|
|
@@ -2408,9 +2464,12 @@ async function startDecoder(opts) {
|
|
|
2408
2464
|
let readErr;
|
|
2409
2465
|
let packets;
|
|
2410
2466
|
try {
|
|
2467
|
+
const _readStart = performance.now();
|
|
2411
2468
|
[readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
|
|
2412
2469
|
limit: 16 * 1024
|
|
2413
2470
|
});
|
|
2471
|
+
readMsTotal += performance.now() - _readStart;
|
|
2472
|
+
readBatches++;
|
|
2414
2473
|
} catch (err) {
|
|
2415
2474
|
console.error("[avbridge] ff_read_frame_multi failed:", err);
|
|
2416
2475
|
return;
|
|
@@ -2472,8 +2531,17 @@ async function startDecoder(opts) {
|
|
|
2472
2531
|
}
|
|
2473
2532
|
}
|
|
2474
2533
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2534
|
+
{
|
|
2535
|
+
const _throttleStart = performance.now();
|
|
2536
|
+
let _throttled = false;
|
|
2537
|
+
while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
|
|
2538
|
+
_throttled = true;
|
|
2539
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
2540
|
+
}
|
|
2541
|
+
if (_throttled) {
|
|
2542
|
+
pumpThrottleMsTotal += performance.now() - _throttleStart;
|
|
2543
|
+
pumpThrottleEntries++;
|
|
2544
|
+
}
|
|
2477
2545
|
}
|
|
2478
2546
|
if (readErr === libav.AVERROR_EOF) {
|
|
2479
2547
|
if (videoDec) await decodeVideoBatch(
|
|
@@ -2499,6 +2567,7 @@ async function startDecoder(opts) {
|
|
|
2499
2567
|
async function decodeVideoBatch(pkts, myToken, flush = false) {
|
|
2500
2568
|
if (!videoDec || destroyed || myToken !== pumpToken) return;
|
|
2501
2569
|
let frames;
|
|
2570
|
+
const _t0 = performance.now();
|
|
2502
2571
|
try {
|
|
2503
2572
|
frames = await libav.ff_decode_multi(
|
|
2504
2573
|
videoDec.c,
|
|
@@ -2511,18 +2580,38 @@ async function startDecoder(opts) {
|
|
|
2511
2580
|
console.error("[avbridge] video decode batch failed:", err);
|
|
2512
2581
|
return;
|
|
2513
2582
|
}
|
|
2583
|
+
{
|
|
2584
|
+
const _dt = performance.now() - _t0;
|
|
2585
|
+
videoDecodeMsTotal += _dt;
|
|
2586
|
+
videoDecodeBatches++;
|
|
2587
|
+
if (_dt > slowestVideoBatchMs) slowestVideoBatchMs = _dt;
|
|
2588
|
+
}
|
|
2514
2589
|
if (myToken !== pumpToken || destroyed) return;
|
|
2515
2590
|
for (const f of frames) {
|
|
2516
2591
|
if (myToken !== pumpToken || destroyed) return;
|
|
2517
2592
|
sanitizeFrameTimestamp(
|
|
2518
2593
|
f,
|
|
2519
2594
|
() => {
|
|
2520
|
-
const
|
|
2521
|
-
syntheticVideoUs
|
|
2522
|
-
return
|
|
2595
|
+
const base = lastEmittedPtsUs >= 0 ? lastEmittedPtsUs + videoFrameStepUs : syntheticVideoUs;
|
|
2596
|
+
syntheticVideoUs = base + videoFrameStepUs;
|
|
2597
|
+
return base;
|
|
2523
2598
|
},
|
|
2524
2599
|
videoTimeBase
|
|
2525
2600
|
);
|
|
2601
|
+
const _fPts = (f.ptshi ?? 0) * 4294967296 + (f.pts ?? 0);
|
|
2602
|
+
if (_fPts > newestVideoPtsUs) newestVideoPtsUs = _fPts;
|
|
2603
|
+
if (lastEmittedPtsUs >= 0 && _fPts < lastEmittedPtsUs) {
|
|
2604
|
+
ptsRegressions++;
|
|
2605
|
+
const regressMs = (lastEmittedPtsUs - _fPts) / 1e3;
|
|
2606
|
+
if (regressMs > worstPtsRegressionMs) worstPtsRegressionMs = regressMs;
|
|
2607
|
+
if (ptsRegressions <= 10) {
|
|
2608
|
+
console.warn(
|
|
2609
|
+
`[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.`
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
continue;
|
|
2613
|
+
}
|
|
2614
|
+
lastEmittedPtsUs = _fPts;
|
|
2526
2615
|
try {
|
|
2527
2616
|
const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
|
|
2528
2617
|
opts.renderer.enqueue(vf);
|
|
@@ -2537,6 +2626,7 @@ async function startDecoder(opts) {
|
|
|
2537
2626
|
async function decodeAudioBatch(pkts, myToken, flush = false) {
|
|
2538
2627
|
if (!audioDec || destroyed || myToken !== pumpToken) return;
|
|
2539
2628
|
let frames;
|
|
2629
|
+
const _t0 = performance.now();
|
|
2540
2630
|
try {
|
|
2541
2631
|
frames = await libav.ff_decode_multi(
|
|
2542
2632
|
audioDec.c,
|
|
@@ -2549,6 +2639,8 @@ async function startDecoder(opts) {
|
|
|
2549
2639
|
console.error("[avbridge] audio decode batch failed:", err);
|
|
2550
2640
|
return;
|
|
2551
2641
|
}
|
|
2642
|
+
audioDecodeMsTotal += performance.now() - _t0;
|
|
2643
|
+
audioDecodeBatches++;
|
|
2552
2644
|
if (myToken !== pumpToken || destroyed) return;
|
|
2553
2645
|
for (const f of frames) {
|
|
2554
2646
|
if (myToken !== pumpToken || destroyed) return;
|
|
@@ -2670,6 +2762,7 @@ async function startDecoder(opts) {
|
|
|
2670
2762
|
await flushBSF();
|
|
2671
2763
|
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
2672
2764
|
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
2765
|
+
lastEmittedPtsUs = -1;
|
|
2673
2766
|
pumpRunning = pumpLoop(newToken).catch(
|
|
2674
2767
|
(err) => console.error("[avbridge] fallback pump failed (post-setAudioTrack):", err)
|
|
2675
2768
|
);
|
|
@@ -2707,6 +2800,7 @@ async function startDecoder(opts) {
|
|
|
2707
2800
|
await flushBSF();
|
|
2708
2801
|
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
2709
2802
|
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
2803
|
+
lastEmittedPtsUs = -1;
|
|
2710
2804
|
pumpRunning = pumpLoop(newToken).catch(
|
|
2711
2805
|
(err) => console.error("[avbridge] decoder pump failed (post-seek):", err)
|
|
2712
2806
|
);
|
|
@@ -2720,7 +2814,24 @@ async function startDecoder(opts) {
|
|
|
2720
2814
|
packetsRead,
|
|
2721
2815
|
videoFramesDecoded,
|
|
2722
2816
|
audioFramesDecoded,
|
|
2817
|
+
// Throughput instrumentation — the stats panel turns these into
|
|
2818
|
+
// "decode fps actual / realtime target" and shows slowest batch
|
|
2819
|
+
// + producer throttle share.
|
|
2820
|
+
videoDecodeMsTotal,
|
|
2821
|
+
videoDecodeBatches,
|
|
2822
|
+
audioDecodeMsTotal,
|
|
2823
|
+
audioDecodeBatches,
|
|
2824
|
+
readMsTotal,
|
|
2825
|
+
readBatches,
|
|
2826
|
+
pumpThrottleMsTotal,
|
|
2827
|
+
pumpThrottleEntries,
|
|
2828
|
+
slowestVideoBatchMs,
|
|
2829
|
+
newestVideoPtsMs: Math.round(newestVideoPtsUs / 1e3),
|
|
2830
|
+
ptsRegressions,
|
|
2831
|
+
worstPtsRegressionMs,
|
|
2832
|
+
sourceFps: videoFps,
|
|
2723
2833
|
bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
|
|
2834
|
+
bsfMissing: bsfRequiredButMissing ? ["mpeg4_unpack_bframes"] : [],
|
|
2724
2835
|
// Confirmed transport info: once prepareLibavInput returns
|
|
2725
2836
|
// successfully, we *know* whether the source is http-range (probe
|
|
2726
2837
|
// succeeded and returned 206) or in-memory blob. Diagnostics hoists
|
|
@@ -3009,9 +3120,9 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3009
3120
|
constructor(options, registry) {
|
|
3010
3121
|
this.options = options;
|
|
3011
3122
|
this.registry = registry;
|
|
3012
|
-
const { requestInit, fetchFn } = options;
|
|
3013
|
-
if (requestInit || fetchFn) {
|
|
3014
|
-
this.transport = { requestInit, fetchFn };
|
|
3123
|
+
const { requestInit, fetchFn, cacheBytes } = options;
|
|
3124
|
+
if (requestInit || fetchFn || cacheBytes !== void 0) {
|
|
3125
|
+
this.transport = { requestInit, fetchFn, cacheBytes };
|
|
3015
3126
|
}
|
|
3016
3127
|
}
|
|
3017
3128
|
options;
|
|
@@ -3543,5 +3654,5 @@ function defaultFallbackChain(strategy) {
|
|
|
3543
3654
|
}
|
|
3544
3655
|
|
|
3545
3656
|
export { FALLBACK_AUDIO_CODECS, FALLBACK_VIDEO_CODECS, NATIVE_AUDIO_CODECS, NATIVE_VIDEO_CODECS, UnifiedPlayer, classifyContext, createPlayer };
|
|
3546
|
-
//# sourceMappingURL=chunk-
|
|
3547
|
-
//# sourceMappingURL=chunk-
|
|
3657
|
+
//# sourceMappingURL=chunk-BN7BRTLY.js.map
|
|
3658
|
+
//# sourceMappingURL=chunk-BN7BRTLY.js.map
|