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
package/dist/element-browser.js
CHANGED
|
@@ -29513,7 +29513,8 @@ async function prepareLibavInput(libav, filename, source, transport) {
|
|
|
29513
29513
|
if (source.kind === "url") {
|
|
29514
29514
|
const handle = await attachLibavHttpReader(libav, filename, source.url, {
|
|
29515
29515
|
requestInit: transport?.requestInit,
|
|
29516
|
-
fetchFn: transport?.fetchFn
|
|
29516
|
+
fetchFn: transport?.fetchFn,
|
|
29517
|
+
cacheBytes: transport?.cacheBytes
|
|
29517
29518
|
});
|
|
29518
29519
|
return {
|
|
29519
29520
|
filename,
|
|
@@ -29575,7 +29576,9 @@ async function attachLibavHttpReader(libav, filename, url2, options = {}) {
|
|
|
29575
29576
|
}
|
|
29576
29577
|
await libav.mkblockreaderdev(filename, size);
|
|
29577
29578
|
let detached = false;
|
|
29578
|
-
|
|
29579
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
29580
|
+
let cacheBytes = 0;
|
|
29581
|
+
const cacheBudget = Math.max(0, options.cacheBytes ?? DEFAULT_CACHE_BYTES);
|
|
29579
29582
|
let inflight = null;
|
|
29580
29583
|
function clampReadLength(requested) {
|
|
29581
29584
|
const doubled = requested * 2;
|
|
@@ -29583,14 +29586,33 @@ async function attachLibavHttpReader(libav, filename, url2, options = {}) {
|
|
|
29583
29586
|
if (doubled > MAX_READ) return MAX_READ;
|
|
29584
29587
|
return doubled;
|
|
29585
29588
|
}
|
|
29586
|
-
function
|
|
29587
|
-
|
|
29588
|
-
|
|
29589
|
+
function cacheLookup(pos, length) {
|
|
29590
|
+
for (const [blockPos, bytes2] of cache2) {
|
|
29591
|
+
if (pos >= blockPos && pos + length <= blockPos + bytes2.byteLength) {
|
|
29592
|
+
cache2.delete(blockPos);
|
|
29593
|
+
cache2.set(blockPos, bytes2);
|
|
29594
|
+
const offset = pos - blockPos;
|
|
29595
|
+
return bytes2.subarray(offset, offset + length);
|
|
29596
|
+
}
|
|
29597
|
+
}
|
|
29598
|
+
return null;
|
|
29589
29599
|
}
|
|
29590
|
-
function
|
|
29591
|
-
|
|
29592
|
-
|
|
29593
|
-
|
|
29600
|
+
function cacheInsert(pos, bytes2) {
|
|
29601
|
+
const existing = cache2.get(pos);
|
|
29602
|
+
if (existing) {
|
|
29603
|
+
cacheBytes -= existing.byteLength;
|
|
29604
|
+
cache2.delete(pos);
|
|
29605
|
+
}
|
|
29606
|
+
cache2.set(pos, bytes2);
|
|
29607
|
+
cacheBytes += bytes2.byteLength;
|
|
29608
|
+
while (cacheBytes > cacheBudget && cache2.size > 0) {
|
|
29609
|
+
const oldestKey = cache2.keys().next().value;
|
|
29610
|
+
if (oldestKey === void 0) break;
|
|
29611
|
+
const oldest = cache2.get(oldestKey);
|
|
29612
|
+
if (!oldest) break;
|
|
29613
|
+
cache2.delete(oldestKey);
|
|
29614
|
+
cacheBytes -= oldest.byteLength;
|
|
29615
|
+
}
|
|
29594
29616
|
}
|
|
29595
29617
|
async function fetchRange(pos, length) {
|
|
29596
29618
|
const end = Math.min(pos + length - 1, size - 1);
|
|
@@ -29607,7 +29629,7 @@ async function attachLibavHttpReader(libav, filename, url2, options = {}) {
|
|
|
29607
29629
|
);
|
|
29608
29630
|
}
|
|
29609
29631
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
29610
|
-
|
|
29632
|
+
cacheInsert(pos, buf);
|
|
29611
29633
|
return buf;
|
|
29612
29634
|
}
|
|
29613
29635
|
async function handleRead(name, pos, length) {
|
|
@@ -29618,10 +29640,10 @@ async function attachLibavHttpReader(libav, filename, url2, options = {}) {
|
|
|
29618
29640
|
}
|
|
29619
29641
|
}
|
|
29620
29642
|
if (detached) return;
|
|
29621
|
-
|
|
29622
|
-
|
|
29643
|
+
const hit = cacheLookup(pos, length);
|
|
29644
|
+
if (hit) {
|
|
29623
29645
|
try {
|
|
29624
|
-
await libav.ff_block_reader_dev_send(name, pos,
|
|
29646
|
+
await libav.ff_block_reader_dev_send(name, pos, hit);
|
|
29625
29647
|
} catch {
|
|
29626
29648
|
}
|
|
29627
29649
|
return;
|
|
@@ -29674,7 +29696,8 @@ async function attachLibavHttpReader(libav, filename, url2, options = {}) {
|
|
|
29674
29696
|
} catch {
|
|
29675
29697
|
}
|
|
29676
29698
|
}
|
|
29677
|
-
|
|
29699
|
+
cache2.clear();
|
|
29700
|
+
cacheBytes = 0;
|
|
29678
29701
|
try {
|
|
29679
29702
|
await libav.unlinkreadaheadfile(filename);
|
|
29680
29703
|
} catch {
|
|
@@ -29682,11 +29705,12 @@ async function attachLibavHttpReader(libav, filename, url2, options = {}) {
|
|
|
29682
29705
|
}
|
|
29683
29706
|
};
|
|
29684
29707
|
}
|
|
29685
|
-
var MIN_READ, MAX_READ;
|
|
29708
|
+
var MIN_READ, MAX_READ, DEFAULT_CACHE_BYTES;
|
|
29686
29709
|
var init_libav_http_reader = __esm({
|
|
29687
29710
|
"src/util/libav-http-reader.ts"() {
|
|
29688
29711
|
MIN_READ = 256 * 1024;
|
|
29689
29712
|
MAX_READ = 1 * 1024 * 1024;
|
|
29713
|
+
DEFAULT_CACHE_BYTES = 8 * 1024 * 1024;
|
|
29690
29714
|
}
|
|
29691
29715
|
});
|
|
29692
29716
|
|
|
@@ -29933,7 +29957,7 @@ async function probeWithLibav(source, sniffed) {
|
|
|
29933
29957
|
codec: ffmpegToAvbridgeVideo(codecName),
|
|
29934
29958
|
width: codecpar?.width ?? 0,
|
|
29935
29959
|
height: codecpar?.height ?? 0,
|
|
29936
|
-
fps: framerate(stream)
|
|
29960
|
+
fps: await framerate(libav, stream)
|
|
29937
29961
|
});
|
|
29938
29962
|
} else if (stream.codec_type === libav.AVMEDIA_TYPE_AUDIO) {
|
|
29939
29963
|
audioTracks.push({
|
|
@@ -29961,7 +29985,7 @@ async function probeWithLibav(source, sniffed) {
|
|
|
29961
29985
|
duration
|
|
29962
29986
|
};
|
|
29963
29987
|
}
|
|
29964
|
-
function framerate(stream) {
|
|
29988
|
+
async function framerate(libav, stream) {
|
|
29965
29989
|
if (typeof stream.avg_frame_rate_num === "number" && stream.avg_frame_rate_den) {
|
|
29966
29990
|
return stream.avg_frame_rate_num / stream.avg_frame_rate_den;
|
|
29967
29991
|
}
|
|
@@ -29969,6 +29993,14 @@ function framerate(stream) {
|
|
|
29969
29993
|
if (stream.avg_frame_rate.den === 0) return void 0;
|
|
29970
29994
|
return stream.avg_frame_rate.num / stream.avg_frame_rate.den;
|
|
29971
29995
|
}
|
|
29996
|
+
try {
|
|
29997
|
+
const num = await libav.AVCodecParameters_framerate_num?.(stream.codecpar);
|
|
29998
|
+
const den = await libav.AVCodecParameters_framerate_den?.(stream.codecpar);
|
|
29999
|
+
if (typeof num === "number" && typeof den === "number" && den > 0 && num > 0) {
|
|
30000
|
+
return num / den;
|
|
30001
|
+
}
|
|
30002
|
+
} catch {
|
|
30003
|
+
}
|
|
29972
30004
|
return void 0;
|
|
29973
30005
|
}
|
|
29974
30006
|
async function safeDuration(libav, fmt_ctx) {
|
|
@@ -32537,10 +32569,20 @@ var VideoRenderer = class {
|
|
|
32537
32569
|
/** Resolves once the first decoded frame has been enqueued. */
|
|
32538
32570
|
firstFrameReady;
|
|
32539
32571
|
resolveFirstFrame;
|
|
32540
|
-
/**
|
|
32572
|
+
/**
|
|
32573
|
+
* True once at least one frame has been enqueued *since the last flush*.
|
|
32574
|
+
* Used by `readyState` — initial cold-start reports HAVE_NOTHING until
|
|
32575
|
+
* any frame has arrived, and after a seek we want the same semantics
|
|
32576
|
+
* (HAVE_NOTHING until post-seek frames arrive), so the cumulative
|
|
32577
|
+
* `framesPainted > 0` that used to live here was wrong: it kept the
|
|
32578
|
+
* state "true forever" after the first frame ever, so post-seek
|
|
32579
|
+
* `waitForBuffer()` would exit immediately with an empty queue and
|
|
32580
|
+
* leave video frozen while audio kept going.
|
|
32581
|
+
*/
|
|
32541
32582
|
hasFrames() {
|
|
32542
|
-
return this.queue.length > 0 || this.
|
|
32583
|
+
return this.queue.length > 0 || this.hasEverEnqueuedSinceFlush;
|
|
32543
32584
|
}
|
|
32585
|
+
hasEverEnqueuedSinceFlush = false;
|
|
32544
32586
|
/** Current depth of the frame queue. Used by the decoder for backpressure. */
|
|
32545
32587
|
queueDepth() {
|
|
32546
32588
|
return this.queue.length;
|
|
@@ -32559,6 +32601,7 @@ var VideoRenderer = class {
|
|
|
32559
32601
|
return;
|
|
32560
32602
|
}
|
|
32561
32603
|
this.queue.push(frame);
|
|
32604
|
+
this.hasEverEnqueuedSinceFlush = true;
|
|
32562
32605
|
if (this.queue.length === 1 && this.framesPainted === 0) {
|
|
32563
32606
|
this.resolveFirstFrame();
|
|
32564
32607
|
}
|
|
@@ -32692,7 +32735,8 @@ var VideoRenderer = class {
|
|
|
32692
32735
|
}
|
|
32693
32736
|
return;
|
|
32694
32737
|
}
|
|
32695
|
-
const
|
|
32738
|
+
const _relaxDrop = globalThis.AVBRIDGE_RELAX_DROP === true;
|
|
32739
|
+
const dropThresholdUs = _relaxDrop ? audioNowUs - 60 * 1e6 : audioNowUs - frameDurationUs * 2;
|
|
32696
32740
|
let dropped = 0;
|
|
32697
32741
|
while (bestIdx > 0) {
|
|
32698
32742
|
const ts = this.queue[0].timestamp ?? 0;
|
|
@@ -32753,16 +32797,28 @@ var VideoRenderer = class {
|
|
|
32753
32797
|
while (this.queue.length > 0) this.queue.shift()?.close();
|
|
32754
32798
|
this.prerolled = false;
|
|
32755
32799
|
this.ptsCalibrated = false;
|
|
32800
|
+
this.hasEverEnqueuedSinceFlush = false;
|
|
32756
32801
|
if (isDebug() && count > 0) {
|
|
32757
32802
|
console.log(`[avbridge:renderer] FLUSH discarded=${count} painted=${this.framesPainted} drops=${this.framesDroppedLate}`);
|
|
32758
32803
|
}
|
|
32759
32804
|
}
|
|
32760
32805
|
stats() {
|
|
32806
|
+
let queueSpanMs = 0;
|
|
32807
|
+
let queueHeadMs = 0;
|
|
32808
|
+
let queueTailMs = 0;
|
|
32809
|
+
if (this.queue.length > 0) {
|
|
32810
|
+
queueHeadMs = Math.round((this.queue[0].timestamp ?? 0) / 1e3);
|
|
32811
|
+
queueTailMs = Math.round((this.queue[this.queue.length - 1].timestamp ?? 0) / 1e3);
|
|
32812
|
+
queueSpanMs = Math.max(0, queueTailMs - queueHeadMs);
|
|
32813
|
+
}
|
|
32761
32814
|
return {
|
|
32762
32815
|
framesPainted: this.framesPainted,
|
|
32763
32816
|
framesDroppedLate: this.framesDroppedLate,
|
|
32764
32817
|
framesDroppedOverflow: this.framesDroppedOverflow,
|
|
32765
|
-
queueDepth: this.queue.length
|
|
32818
|
+
queueDepth: this.queue.length,
|
|
32819
|
+
queueHeadMs,
|
|
32820
|
+
queueTailMs,
|
|
32821
|
+
queueSpanMs
|
|
32766
32822
|
};
|
|
32767
32823
|
}
|
|
32768
32824
|
destroy() {
|
|
@@ -33304,6 +33360,7 @@ async function startHybridDecoder(opts) {
|
|
|
33304
33360
|
}
|
|
33305
33361
|
let bsfCtx = null;
|
|
33306
33362
|
let bsfPkt = null;
|
|
33363
|
+
let bsfRequiredButMissing = false;
|
|
33307
33364
|
if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
|
|
33308
33365
|
try {
|
|
33309
33366
|
bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
@@ -33314,13 +33371,19 @@ async function startHybridDecoder(opts) {
|
|
|
33314
33371
|
bsfPkt = await libav.av_packet_alloc();
|
|
33315
33372
|
dbg.info("bsf", "mpeg4_unpack_bframes BSF active (hybrid)");
|
|
33316
33373
|
} else {
|
|
33317
|
-
|
|
33374
|
+
bsfRequiredButMissing = true;
|
|
33318
33375
|
bsfCtx = null;
|
|
33319
33376
|
}
|
|
33320
33377
|
} catch (err) {
|
|
33321
|
-
|
|
33378
|
+
bsfRequiredButMissing = true;
|
|
33322
33379
|
bsfCtx = null;
|
|
33323
33380
|
bsfPkt = null;
|
|
33381
|
+
dbg.warn("bsf", `hybrid: mpeg4_unpack_bframes BSF init failed: ${err.message}`);
|
|
33382
|
+
}
|
|
33383
|
+
if (bsfRequiredButMissing) {
|
|
33384
|
+
console.error(
|
|
33385
|
+
"[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."
|
|
33386
|
+
);
|
|
33324
33387
|
}
|
|
33325
33388
|
}
|
|
33326
33389
|
async function applyBSF(packets) {
|
|
@@ -33330,7 +33393,6 @@ async function startHybridDecoder(opts) {
|
|
|
33330
33393
|
await libav.ff_copyin_packet(bsfPkt, pkt);
|
|
33331
33394
|
const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
|
|
33332
33395
|
if (sendErr < 0) {
|
|
33333
|
-
out.push(pkt);
|
|
33334
33396
|
continue;
|
|
33335
33397
|
}
|
|
33336
33398
|
while (true) {
|
|
@@ -33344,10 +33406,13 @@ async function startHybridDecoder(opts) {
|
|
|
33344
33406
|
async function flushBSF() {
|
|
33345
33407
|
if (!bsfCtx || !bsfPkt) return;
|
|
33346
33408
|
try {
|
|
33347
|
-
|
|
33348
|
-
|
|
33349
|
-
|
|
33350
|
-
|
|
33409
|
+
if (libav.av_bsf_flush) {
|
|
33410
|
+
await libav.av_bsf_flush(bsfCtx);
|
|
33411
|
+
} else {
|
|
33412
|
+
while (true) {
|
|
33413
|
+
const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
33414
|
+
if (err < 0) break;
|
|
33415
|
+
}
|
|
33351
33416
|
}
|
|
33352
33417
|
} catch {
|
|
33353
33418
|
}
|
|
@@ -33659,6 +33724,7 @@ async function startHybridDecoder(opts) {
|
|
|
33659
33724
|
videoChunksFed,
|
|
33660
33725
|
audioFramesDecoded,
|
|
33661
33726
|
bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
|
|
33727
|
+
bsfMissing: bsfRequiredButMissing ? ["mpeg4_unpack_bframes"] : [],
|
|
33662
33728
|
videoDecodeQueueSize: videoDecoder?.decodeQueueSize ?? 0,
|
|
33663
33729
|
// Confirmed transport info — see fallback decoder for the pattern.
|
|
33664
33730
|
_transport: inputHandle.transport === "http-range" ? "http-range" : "memory",
|
|
@@ -33960,6 +34026,7 @@ async function startDecoder(opts) {
|
|
|
33960
34026
|
}
|
|
33961
34027
|
let bsfCtx = null;
|
|
33962
34028
|
let bsfPkt = null;
|
|
34029
|
+
let bsfRequiredButMissing = false;
|
|
33963
34030
|
if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
|
|
33964
34031
|
try {
|
|
33965
34032
|
bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
@@ -33970,13 +34037,19 @@ async function startDecoder(opts) {
|
|
|
33970
34037
|
bsfPkt = await libav.av_packet_alloc();
|
|
33971
34038
|
dbg.info("bsf", "mpeg4_unpack_bframes BSF active");
|
|
33972
34039
|
} else {
|
|
33973
|
-
|
|
34040
|
+
bsfRequiredButMissing = true;
|
|
33974
34041
|
bsfCtx = null;
|
|
33975
34042
|
}
|
|
33976
34043
|
} catch (err) {
|
|
33977
|
-
|
|
34044
|
+
bsfRequiredButMissing = true;
|
|
33978
34045
|
bsfCtx = null;
|
|
33979
34046
|
bsfPkt = null;
|
|
34047
|
+
dbg.warn("bsf", `mpeg4_unpack_bframes BSF init failed: ${err.message}`);
|
|
34048
|
+
}
|
|
34049
|
+
if (bsfRequiredButMissing) {
|
|
34050
|
+
console.error(
|
|
34051
|
+
"[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."
|
|
34052
|
+
);
|
|
33980
34053
|
}
|
|
33981
34054
|
}
|
|
33982
34055
|
async function applyBSF(packets) {
|
|
@@ -33986,7 +34059,6 @@ async function startDecoder(opts) {
|
|
|
33986
34059
|
await libav.ff_copyin_packet(bsfPkt, pkt);
|
|
33987
34060
|
const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
|
|
33988
34061
|
if (sendErr < 0) {
|
|
33989
|
-
out.push(pkt);
|
|
33990
34062
|
continue;
|
|
33991
34063
|
}
|
|
33992
34064
|
while (true) {
|
|
@@ -34000,10 +34072,13 @@ async function startDecoder(opts) {
|
|
|
34000
34072
|
async function flushBSF() {
|
|
34001
34073
|
if (!bsfCtx || !bsfPkt) return;
|
|
34002
34074
|
try {
|
|
34003
|
-
|
|
34004
|
-
|
|
34005
|
-
|
|
34006
|
-
|
|
34075
|
+
if (libav.av_bsf_flush) {
|
|
34076
|
+
await libav.av_bsf_flush(bsfCtx);
|
|
34077
|
+
} else {
|
|
34078
|
+
while (true) {
|
|
34079
|
+
const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
34080
|
+
if (err < 0) break;
|
|
34081
|
+
}
|
|
34007
34082
|
}
|
|
34008
34083
|
} catch {
|
|
34009
34084
|
}
|
|
@@ -34021,6 +34096,19 @@ async function startDecoder(opts) {
|
|
|
34021
34096
|
let watchdogOverflowWarned = false;
|
|
34022
34097
|
let syntheticVideoUs = 0;
|
|
34023
34098
|
let syntheticAudioUs = 0;
|
|
34099
|
+
let videoDecodeMsTotal = 0;
|
|
34100
|
+
let audioDecodeMsTotal = 0;
|
|
34101
|
+
let videoDecodeBatches = 0;
|
|
34102
|
+
let audioDecodeBatches = 0;
|
|
34103
|
+
let readMsTotal = 0;
|
|
34104
|
+
let readBatches = 0;
|
|
34105
|
+
let pumpThrottleMsTotal = 0;
|
|
34106
|
+
let pumpThrottleEntries = 0;
|
|
34107
|
+
let slowestVideoBatchMs = 0;
|
|
34108
|
+
let newestVideoPtsUs = 0;
|
|
34109
|
+
let lastEmittedPtsUs = -1;
|
|
34110
|
+
let ptsRegressions = 0;
|
|
34111
|
+
let worstPtsRegressionMs = 0;
|
|
34024
34112
|
const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
|
|
34025
34113
|
const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;
|
|
34026
34114
|
const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
|
|
@@ -34029,9 +34117,12 @@ async function startDecoder(opts) {
|
|
|
34029
34117
|
let readErr;
|
|
34030
34118
|
let packets;
|
|
34031
34119
|
try {
|
|
34120
|
+
const _readStart = performance.now();
|
|
34032
34121
|
[readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
|
|
34033
34122
|
limit: 16 * 1024
|
|
34034
34123
|
});
|
|
34124
|
+
readMsTotal += performance.now() - _readStart;
|
|
34125
|
+
readBatches++;
|
|
34035
34126
|
} catch (err) {
|
|
34036
34127
|
console.error("[avbridge] ff_read_frame_multi failed:", err);
|
|
34037
34128
|
return;
|
|
@@ -34093,8 +34184,17 @@ async function startDecoder(opts) {
|
|
|
34093
34184
|
}
|
|
34094
34185
|
}
|
|
34095
34186
|
}
|
|
34096
|
-
|
|
34097
|
-
|
|
34187
|
+
{
|
|
34188
|
+
const _throttleStart = performance.now();
|
|
34189
|
+
let _throttled = false;
|
|
34190
|
+
while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
|
|
34191
|
+
_throttled = true;
|
|
34192
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
34193
|
+
}
|
|
34194
|
+
if (_throttled) {
|
|
34195
|
+
pumpThrottleMsTotal += performance.now() - _throttleStart;
|
|
34196
|
+
pumpThrottleEntries++;
|
|
34197
|
+
}
|
|
34098
34198
|
}
|
|
34099
34199
|
if (readErr === libav.AVERROR_EOF) {
|
|
34100
34200
|
if (videoDec) await decodeVideoBatch(
|
|
@@ -34120,6 +34220,7 @@ async function startDecoder(opts) {
|
|
|
34120
34220
|
async function decodeVideoBatch(pkts, myToken, flush = false) {
|
|
34121
34221
|
if (!videoDec || destroyed || myToken !== pumpToken) return;
|
|
34122
34222
|
let frames;
|
|
34223
|
+
const _t0 = performance.now();
|
|
34123
34224
|
try {
|
|
34124
34225
|
frames = await libav.ff_decode_multi(
|
|
34125
34226
|
videoDec.c,
|
|
@@ -34132,18 +34233,38 @@ async function startDecoder(opts) {
|
|
|
34132
34233
|
console.error("[avbridge] video decode batch failed:", err);
|
|
34133
34234
|
return;
|
|
34134
34235
|
}
|
|
34236
|
+
{
|
|
34237
|
+
const _dt = performance.now() - _t0;
|
|
34238
|
+
videoDecodeMsTotal += _dt;
|
|
34239
|
+
videoDecodeBatches++;
|
|
34240
|
+
if (_dt > slowestVideoBatchMs) slowestVideoBatchMs = _dt;
|
|
34241
|
+
}
|
|
34135
34242
|
if (myToken !== pumpToken || destroyed) return;
|
|
34136
34243
|
for (const f of frames) {
|
|
34137
34244
|
if (myToken !== pumpToken || destroyed) return;
|
|
34138
34245
|
sanitizeFrameTimestamp(
|
|
34139
34246
|
f,
|
|
34140
34247
|
() => {
|
|
34141
|
-
const
|
|
34142
|
-
syntheticVideoUs
|
|
34143
|
-
return
|
|
34248
|
+
const base = lastEmittedPtsUs >= 0 ? lastEmittedPtsUs + videoFrameStepUs : syntheticVideoUs;
|
|
34249
|
+
syntheticVideoUs = base + videoFrameStepUs;
|
|
34250
|
+
return base;
|
|
34144
34251
|
},
|
|
34145
34252
|
videoTimeBase
|
|
34146
34253
|
);
|
|
34254
|
+
const _fPts = (f.ptshi ?? 0) * 4294967296 + (f.pts ?? 0);
|
|
34255
|
+
if (_fPts > newestVideoPtsUs) newestVideoPtsUs = _fPts;
|
|
34256
|
+
if (lastEmittedPtsUs >= 0 && _fPts < lastEmittedPtsUs) {
|
|
34257
|
+
ptsRegressions++;
|
|
34258
|
+
const regressMs = (lastEmittedPtsUs - _fPts) / 1e3;
|
|
34259
|
+
if (regressMs > worstPtsRegressionMs) worstPtsRegressionMs = regressMs;
|
|
34260
|
+
if (ptsRegressions <= 10) {
|
|
34261
|
+
console.warn(
|
|
34262
|
+
`[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.`
|
|
34263
|
+
);
|
|
34264
|
+
}
|
|
34265
|
+
continue;
|
|
34266
|
+
}
|
|
34267
|
+
lastEmittedPtsUs = _fPts;
|
|
34147
34268
|
try {
|
|
34148
34269
|
const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
|
|
34149
34270
|
opts.renderer.enqueue(vf);
|
|
@@ -34158,6 +34279,7 @@ async function startDecoder(opts) {
|
|
|
34158
34279
|
async function decodeAudioBatch(pkts, myToken, flush = false) {
|
|
34159
34280
|
if (!audioDec || destroyed || myToken !== pumpToken) return;
|
|
34160
34281
|
let frames;
|
|
34282
|
+
const _t0 = performance.now();
|
|
34161
34283
|
try {
|
|
34162
34284
|
frames = await libav.ff_decode_multi(
|
|
34163
34285
|
audioDec.c,
|
|
@@ -34170,6 +34292,8 @@ async function startDecoder(opts) {
|
|
|
34170
34292
|
console.error("[avbridge] audio decode batch failed:", err);
|
|
34171
34293
|
return;
|
|
34172
34294
|
}
|
|
34295
|
+
audioDecodeMsTotal += performance.now() - _t0;
|
|
34296
|
+
audioDecodeBatches++;
|
|
34173
34297
|
if (myToken !== pumpToken || destroyed) return;
|
|
34174
34298
|
for (const f of frames) {
|
|
34175
34299
|
if (myToken !== pumpToken || destroyed) return;
|
|
@@ -34291,6 +34415,7 @@ async function startDecoder(opts) {
|
|
|
34291
34415
|
await flushBSF();
|
|
34292
34416
|
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
34293
34417
|
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
34418
|
+
lastEmittedPtsUs = -1;
|
|
34294
34419
|
pumpRunning = pumpLoop(newToken).catch(
|
|
34295
34420
|
(err) => console.error("[avbridge] fallback pump failed (post-setAudioTrack):", err)
|
|
34296
34421
|
);
|
|
@@ -34328,6 +34453,7 @@ async function startDecoder(opts) {
|
|
|
34328
34453
|
await flushBSF();
|
|
34329
34454
|
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
34330
34455
|
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
34456
|
+
lastEmittedPtsUs = -1;
|
|
34331
34457
|
pumpRunning = pumpLoop(newToken).catch(
|
|
34332
34458
|
(err) => console.error("[avbridge] decoder pump failed (post-seek):", err)
|
|
34333
34459
|
);
|
|
@@ -34341,7 +34467,24 @@ async function startDecoder(opts) {
|
|
|
34341
34467
|
packetsRead,
|
|
34342
34468
|
videoFramesDecoded,
|
|
34343
34469
|
audioFramesDecoded,
|
|
34470
|
+
// Throughput instrumentation — the stats panel turns these into
|
|
34471
|
+
// "decode fps actual / realtime target" and shows slowest batch
|
|
34472
|
+
// + producer throttle share.
|
|
34473
|
+
videoDecodeMsTotal,
|
|
34474
|
+
videoDecodeBatches,
|
|
34475
|
+
audioDecodeMsTotal,
|
|
34476
|
+
audioDecodeBatches,
|
|
34477
|
+
readMsTotal,
|
|
34478
|
+
readBatches,
|
|
34479
|
+
pumpThrottleMsTotal,
|
|
34480
|
+
pumpThrottleEntries,
|
|
34481
|
+
slowestVideoBatchMs,
|
|
34482
|
+
newestVideoPtsMs: Math.round(newestVideoPtsUs / 1e3),
|
|
34483
|
+
ptsRegressions,
|
|
34484
|
+
worstPtsRegressionMs,
|
|
34485
|
+
sourceFps: videoFps,
|
|
34344
34486
|
bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
|
|
34487
|
+
bsfMissing: bsfRequiredButMissing ? ["mpeg4_unpack_bframes"] : [],
|
|
34345
34488
|
// Confirmed transport info: once prepareLibavInput returns
|
|
34346
34489
|
// successfully, we *know* whether the source is http-range (probe
|
|
34347
34490
|
// succeeded and returned 206) or in-memory blob. Diagnostics hoists
|
|
@@ -34634,9 +34777,9 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
34634
34777
|
constructor(options, registry) {
|
|
34635
34778
|
this.options = options;
|
|
34636
34779
|
this.registry = registry;
|
|
34637
|
-
const { requestInit, fetchFn } = options;
|
|
34638
|
-
if (requestInit || fetchFn) {
|
|
34639
|
-
this.transport = { requestInit, fetchFn };
|
|
34780
|
+
const { requestInit, fetchFn, cacheBytes } = options;
|
|
34781
|
+
if (requestInit || fetchFn || cacheBytes !== void 0) {
|
|
34782
|
+
this.transport = { requestInit, fetchFn, cacheBytes };
|
|
34640
34783
|
}
|
|
34641
34784
|
}
|
|
34642
34785
|
options;
|