avbridge 2.8.3 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +165 -0
- package/README.md +74 -1
- package/dist/{avi-F6WZJK5T.cjs → avi-2ILLBNPQ.cjs} +8 -2
- package/dist/avi-2ILLBNPQ.cjs.map +1 -0
- package/dist/{avi-W6L3BTWU.cjs → avi-B5CQYB7L.cjs} +8 -2
- package/dist/avi-B5CQYB7L.cjs.map +1 -0
- package/dist/{avi-2JPBSHGA.js → avi-JXU4GQL2.js} +8 -2
- package/dist/avi-JXU4GQL2.js.map +1 -0
- package/dist/{avi-NJXAXUXK.js → avi-RWWPN2PR.js} +8 -2
- package/dist/avi-RWWPN2PR.js.map +1 -0
- package/dist/{chunk-X2K3GIWE.js → chunk-2NSOOMXW.js} +14 -3
- package/dist/chunk-2NSOOMXW.js.map +1 -0
- package/dist/{chunk-ZCUXHW55.cjs → chunk-BYGZN4Z5.cjs} +5 -5
- package/dist/{chunk-ZCUXHW55.cjs.map → chunk-BYGZN4Z5.cjs.map} +1 -1
- package/dist/{chunk-SMH6IOP2.js → chunk-CL6UEUQF.js} +4 -4
- package/dist/{chunk-SMH6IOP2.js.map → chunk-CL6UEUQF.js.map} +1 -1
- package/dist/{chunk-IUSFLVLJ.cjs → chunk-EY6DZEDT.cjs} +149 -24
- package/dist/chunk-EY6DZEDT.cjs.map +1 -0
- package/dist/{chunk-SR3MPV4D.js → chunk-GYIJU44C.js} +5 -5
- package/dist/{chunk-SR3MPV4D.js.map → chunk-GYIJU44C.js.map} +1 -1
- package/dist/{chunk-CPZ7PXAM.cjs → chunk-L7A3ECI2.cjs} +14 -2
- package/dist/chunk-L7A3ECI2.cjs.map +1 -0
- package/dist/{chunk-Q2VUO52Z.cjs → chunk-OTFS7DC4.cjs} +12 -12
- package/dist/{chunk-Q2VUO52Z.cjs.map → chunk-OTFS7DC4.cjs.map} +1 -1
- package/dist/{chunk-JSQOBUQB.js → chunk-SN4WZE24.js} +139 -14
- package/dist/chunk-SN4WZE24.js.map +1 -0
- package/dist/element-browser.js +164 -16
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +16 -10
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +11 -6
- package/dist/element.d.ts +11 -6
- package/dist/element.js +15 -9
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +20 -20
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -8
- package/dist/libav-demux-3N5Y3VQA.cjs +31 -0
- package/dist/{libav-demux-H2GS46GH.cjs.map → libav-demux-3N5Y3VQA.cjs.map} +1 -1
- package/dist/libav-demux-JXD4OTLM.js +6 -0
- package/dist/{libav-demux-OWZ4T2YW.js.map → libav-demux-JXD4OTLM.js.map} +1 -1
- package/dist/{player-DXEKOky8.d.cts → player-DEcidWk6.d.cts} +8 -1
- package/dist/{player-DXEKOky8.d.ts → player-DEcidWk6.d.ts} +8 -1
- package/dist/player.cjs +266 -36
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +37 -11
- package/dist/player.d.ts +37 -11
- package/dist/player.js +266 -36
- package/dist/player.js.map +1 -1
- package/dist/{remux-WBYIZBBX.js → remux-56V7LDAD.js} +5 -5
- package/dist/{remux-WBYIZBBX.js.map → remux-56V7LDAD.js.map} +1 -1
- package/dist/{remux-OBSMIENG.cjs → remux-KUS5GIL6.cjs} +10 -10
- package/dist/{remux-OBSMIENG.cjs.map → remux-KUS5GIL6.cjs.map} +1 -1
- package/package.json +1 -1
- package/src/classify/rules.ts +11 -0
- package/src/element/avbridge-player.ts +22 -11
- package/src/element/avbridge-video.ts +22 -6
- package/src/element/player-styles.ts +68 -3
- package/src/player.ts +96 -8
- package/src/probe/avi.ts +2 -0
- package/src/strategies/fallback/decoder.ts +30 -0
- package/src/strategies/fallback/index.ts +30 -0
- package/src/strategies/hybrid/decoder.ts +35 -0
- package/src/strategies/hybrid/index.ts +17 -0
- package/src/strategies/remux/index.ts +8 -0
- package/src/types.ts +6 -0
- package/src/util/libav-demux.ts +26 -0
- package/dist/avi-2JPBSHGA.js.map +0 -1
- package/dist/avi-F6WZJK5T.cjs.map +0 -1
- package/dist/avi-NJXAXUXK.js.map +0 -1
- package/dist/avi-W6L3BTWU.cjs.map +0 -1
- package/dist/chunk-CPZ7PXAM.cjs.map +0 -1
- package/dist/chunk-IUSFLVLJ.cjs.map +0 -1
- package/dist/chunk-JSQOBUQB.js.map +0 -1
- package/dist/chunk-X2K3GIWE.js.map +0 -1
- package/dist/libav-demux-H2GS46GH.cjs +0 -27
- package/dist/libav-demux-OWZ4T2YW.js +0 -6
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkS4WAZC2T_cjs = require('./chunk-S4WAZC2T.cjs');
|
|
4
|
-
var
|
|
4
|
+
var chunkBYGZN4Z5_cjs = require('./chunk-BYGZN4Z5.cjs');
|
|
5
5
|
var chunk2IJ66NTD_cjs = require('./chunk-2IJ66NTD.cjs');
|
|
6
|
-
var
|
|
6
|
+
var chunkL7A3ECI2_cjs = require('./chunk-L7A3ECI2.cjs');
|
|
7
7
|
var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
|
|
8
8
|
var chunkF3LQJKXK_cjs = require('./chunk-F3LQJKXK.cjs');
|
|
9
9
|
|
|
@@ -92,7 +92,13 @@ var FALLBACK_VIDEO_CODECS = /* @__PURE__ */ new Set([
|
|
|
92
92
|
"rv40",
|
|
93
93
|
"mpeg2",
|
|
94
94
|
"mpeg1",
|
|
95
|
-
"theora"
|
|
95
|
+
"theora",
|
|
96
|
+
"dv",
|
|
97
|
+
"hq_hqa",
|
|
98
|
+
"rawvideo",
|
|
99
|
+
"qtrle",
|
|
100
|
+
"png",
|
|
101
|
+
"vp6f"
|
|
96
102
|
]);
|
|
97
103
|
var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set([
|
|
98
104
|
"wmav2",
|
|
@@ -233,10 +239,12 @@ function classifyContext(ctx) {
|
|
|
233
239
|
reason: `${ctx.container} container with ${video.codec}${audio ? "/" + audio.codec : ""}; MSE rejects the remux target mime and WebCodecs is unavailable \u2014 falling back to WASM decode`
|
|
234
240
|
};
|
|
235
241
|
}
|
|
242
|
+
const fallbackChain = webCodecsAvailable() ? ["hybrid", "fallback"] : ["fallback"];
|
|
236
243
|
return {
|
|
237
244
|
class: "REMUX_CANDIDATE",
|
|
238
245
|
strategy: "remux",
|
|
239
|
-
reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback
|
|
246
|
+
reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback`,
|
|
247
|
+
fallbackChain
|
|
240
248
|
};
|
|
241
249
|
}
|
|
242
250
|
if (webCodecsAvailable()) {
|
|
@@ -726,12 +734,12 @@ async function createRemuxPipeline(ctx, video) {
|
|
|
726
734
|
const mb = await import('mediabunny');
|
|
727
735
|
const videoTrackInfo = ctx.videoTracks[0];
|
|
728
736
|
if (!videoTrackInfo) throw new Error("remux: source has no video track");
|
|
729
|
-
const mbVideoCodec =
|
|
737
|
+
const mbVideoCodec = chunkBYGZN4Z5_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec);
|
|
730
738
|
if (!mbVideoCodec) {
|
|
731
739
|
throw new Error(`remux: video codec "${videoTrackInfo.codec}" is not supported by mediabunny output`);
|
|
732
740
|
}
|
|
733
741
|
const input = new mb.Input({
|
|
734
|
-
source: await
|
|
742
|
+
source: await chunkBYGZN4Z5_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
|
|
735
743
|
formats: mb.ALL_FORMATS
|
|
736
744
|
});
|
|
737
745
|
const allTracks = await input.getTracks();
|
|
@@ -763,7 +771,7 @@ async function createRemuxPipeline(ctx, video) {
|
|
|
763
771
|
throw new Error("remux: audio track not found in input");
|
|
764
772
|
}
|
|
765
773
|
inputAudio = newInput;
|
|
766
|
-
mbAudioCodec =
|
|
774
|
+
mbAudioCodec = chunkBYGZN4Z5_cjs.avbridgeAudioToMediabunny(trackInfo.codec);
|
|
767
775
|
audioSink = new mb.EncodedPacketSink(newInput);
|
|
768
776
|
audioConfig = await newInput.getDecoderConfig();
|
|
769
777
|
}
|
|
@@ -974,6 +982,12 @@ async function createRemuxSession(context, video) {
|
|
|
974
982
|
}
|
|
975
983
|
const wasPlaying = !video.paused;
|
|
976
984
|
await pipeline.seek(time, wasPlaying || wantPlay);
|
|
985
|
+
queueMicrotask(() => {
|
|
986
|
+
try {
|
|
987
|
+
video.dispatchEvent(new Event("seeked"));
|
|
988
|
+
} catch {
|
|
989
|
+
}
|
|
990
|
+
});
|
|
977
991
|
},
|
|
978
992
|
async setAudioTrack(id) {
|
|
979
993
|
if (!context.audioTracks.some((t) => t.id === id)) {
|
|
@@ -1701,6 +1715,7 @@ async function startHybridDecoder(opts) {
|
|
|
1701
1715
|
let videoFramesDecoded = 0;
|
|
1702
1716
|
let audioFramesDecoded = 0;
|
|
1703
1717
|
let videoChunksFed = 0;
|
|
1718
|
+
let bufferedUntilSec = 0;
|
|
1704
1719
|
let syntheticVideoUs = 0;
|
|
1705
1720
|
let syntheticAudioUs = 0;
|
|
1706
1721
|
const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
|
|
@@ -1721,6 +1736,18 @@ async function startHybridDecoder(opts) {
|
|
|
1721
1736
|
if (myToken !== pumpToken || destroyed) return;
|
|
1722
1737
|
const videoPackets = videoStream ? packets[videoStream.index] : void 0;
|
|
1723
1738
|
const audioPackets = audioStream ? packets[audioStream.index] : void 0;
|
|
1739
|
+
if (videoPackets && videoTimeBase) {
|
|
1740
|
+
for (const pkt of videoPackets) {
|
|
1741
|
+
const sec = chunkL7A3ECI2_cjs.packetPtsSec(pkt, videoTimeBase);
|
|
1742
|
+
if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
if (audioPackets && audioTimeBase) {
|
|
1746
|
+
for (const pkt of audioPackets) {
|
|
1747
|
+
const sec = chunkL7A3ECI2_cjs.packetPtsSec(pkt, audioTimeBase);
|
|
1748
|
+
if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1724
1751
|
if (audioDec && audioPackets && audioPackets.length > 0) {
|
|
1725
1752
|
await decodeAudioBatch(audioPackets, myToken);
|
|
1726
1753
|
}
|
|
@@ -1731,7 +1758,7 @@ async function startHybridDecoder(opts) {
|
|
|
1731
1758
|
const processed = await applyBSF(videoPackets);
|
|
1732
1759
|
for (const pkt of processed) {
|
|
1733
1760
|
if (myToken !== pumpToken || destroyed) return;
|
|
1734
|
-
|
|
1761
|
+
chunkL7A3ECI2_cjs.sanitizePacketTimestamp(pkt, () => {
|
|
1735
1762
|
const ts = syntheticVideoUs;
|
|
1736
1763
|
syntheticVideoUs += videoFrameStepUs;
|
|
1737
1764
|
return ts;
|
|
@@ -1810,7 +1837,7 @@ async function startHybridDecoder(opts) {
|
|
|
1810
1837
|
const frames = allFrames;
|
|
1811
1838
|
for (const f of frames) {
|
|
1812
1839
|
if (myToken !== pumpToken || destroyed) return;
|
|
1813
|
-
|
|
1840
|
+
chunkL7A3ECI2_cjs.sanitizeFrameTimestamp(
|
|
1814
1841
|
f,
|
|
1815
1842
|
() => {
|
|
1816
1843
|
const ts = syntheticAudioUs;
|
|
@@ -1821,7 +1848,7 @@ async function startHybridDecoder(opts) {
|
|
|
1821
1848
|
},
|
|
1822
1849
|
audioTimeBase
|
|
1823
1850
|
);
|
|
1824
|
-
const samples =
|
|
1851
|
+
const samples = chunkL7A3ECI2_cjs.libavFrameToInterleavedFloat32(f);
|
|
1825
1852
|
if (samples) {
|
|
1826
1853
|
opts.audio.schedule(samples.data, samples.channels, samples.sampleRate);
|
|
1827
1854
|
audioFramesDecoded++;
|
|
@@ -1977,6 +2004,9 @@ async function startHybridDecoder(opts) {
|
|
|
1977
2004
|
(err) => console.error("[avbridge] hybrid pump failed (post-seek):", err)
|
|
1978
2005
|
);
|
|
1979
2006
|
},
|
|
2007
|
+
bufferedUntilSec() {
|
|
2008
|
+
return bufferedUntilSec;
|
|
2009
|
+
},
|
|
1980
2010
|
stats() {
|
|
1981
2011
|
return {
|
|
1982
2012
|
decoderType: "webcodecs-hybrid",
|
|
@@ -2104,6 +2134,13 @@ async function createHybridSession(ctx, target, transport) {
|
|
|
2104
2134
|
configurable: true,
|
|
2105
2135
|
get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
|
|
2106
2136
|
});
|
|
2137
|
+
Object.defineProperty(target, "buffered", {
|
|
2138
|
+
configurable: true,
|
|
2139
|
+
get: () => {
|
|
2140
|
+
const end = handles.bufferedUntilSec();
|
|
2141
|
+
return makeTimeRanges(end > 0 ? [[0, end]] : []);
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2107
2144
|
async function waitForBuffer() {
|
|
2108
2145
|
const start = performance.now();
|
|
2109
2146
|
while (true) {
|
|
@@ -2117,6 +2154,7 @@ async function createHybridSession(ctx, target, transport) {
|
|
|
2117
2154
|
}
|
|
2118
2155
|
async function doSeek(timeSec) {
|
|
2119
2156
|
const wasPlaying = audio.isPlaying();
|
|
2157
|
+
target.dispatchEvent(new Event("seeking"));
|
|
2120
2158
|
await audio.pause().catch(() => {
|
|
2121
2159
|
});
|
|
2122
2160
|
await handles.seek(timeSec).catch(
|
|
@@ -2128,7 +2166,14 @@ async function createHybridSession(ctx, target, transport) {
|
|
|
2128
2166
|
await waitForBuffer();
|
|
2129
2167
|
await audio.start();
|
|
2130
2168
|
}
|
|
2169
|
+
target.dispatchEvent(new Event("seeked"));
|
|
2131
2170
|
}
|
|
2171
|
+
queueMicrotask(() => {
|
|
2172
|
+
try {
|
|
2173
|
+
target.dispatchEvent(new Event("loadedmetadata"));
|
|
2174
|
+
} catch {
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2132
2177
|
let fatalErrorHandler = null;
|
|
2133
2178
|
handles.onFatalError((reason) => fatalErrorHandler?.(reason));
|
|
2134
2179
|
return {
|
|
@@ -2313,6 +2358,7 @@ async function startDecoder(opts) {
|
|
|
2313
2358
|
let pumpRunning = null;
|
|
2314
2359
|
let packetsRead = 0;
|
|
2315
2360
|
let videoFramesDecoded = 0;
|
|
2361
|
+
let bufferedUntilSec = 0;
|
|
2316
2362
|
let audioFramesDecoded = 0;
|
|
2317
2363
|
let watchdogFirstFrameMs = 0;
|
|
2318
2364
|
let watchdogSlowSinceMs = 0;
|
|
@@ -2338,6 +2384,18 @@ async function startDecoder(opts) {
|
|
|
2338
2384
|
if (myToken !== pumpToken || destroyed) return;
|
|
2339
2385
|
const videoPackets = videoStream ? packets[videoStream.index] : void 0;
|
|
2340
2386
|
const audioPackets = audioStream ? packets[audioStream.index] : void 0;
|
|
2387
|
+
if (videoPackets && videoTimeBase) {
|
|
2388
|
+
for (const pkt of videoPackets) {
|
|
2389
|
+
const sec = chunkL7A3ECI2_cjs.packetPtsSec(pkt, videoTimeBase);
|
|
2390
|
+
if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
if (audioPackets && audioTimeBase) {
|
|
2394
|
+
for (const pkt of audioPackets) {
|
|
2395
|
+
const sec = chunkL7A3ECI2_cjs.packetPtsSec(pkt, audioTimeBase);
|
|
2396
|
+
if (sec != null && sec > bufferedUntilSec) bufferedUntilSec = sec;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2341
2399
|
if (audioDec && audioPackets && audioPackets.length > 0) {
|
|
2342
2400
|
await decodeAudioBatch(audioPackets, myToken);
|
|
2343
2401
|
}
|
|
@@ -2422,7 +2480,7 @@ async function startDecoder(opts) {
|
|
|
2422
2480
|
if (myToken !== pumpToken || destroyed) return;
|
|
2423
2481
|
for (const f of frames) {
|
|
2424
2482
|
if (myToken !== pumpToken || destroyed) return;
|
|
2425
|
-
|
|
2483
|
+
chunkL7A3ECI2_cjs.sanitizeFrameTimestamp(
|
|
2426
2484
|
f,
|
|
2427
2485
|
() => {
|
|
2428
2486
|
const ts = syntheticVideoUs;
|
|
@@ -2460,7 +2518,7 @@ async function startDecoder(opts) {
|
|
|
2460
2518
|
if (myToken !== pumpToken || destroyed) return;
|
|
2461
2519
|
for (const f of frames) {
|
|
2462
2520
|
if (myToken !== pumpToken || destroyed) return;
|
|
2463
|
-
|
|
2521
|
+
chunkL7A3ECI2_cjs.sanitizeFrameTimestamp(
|
|
2464
2522
|
f,
|
|
2465
2523
|
() => {
|
|
2466
2524
|
const ts = syntheticAudioUs;
|
|
@@ -2471,7 +2529,7 @@ async function startDecoder(opts) {
|
|
|
2471
2529
|
},
|
|
2472
2530
|
audioTimeBase
|
|
2473
2531
|
);
|
|
2474
|
-
const samples =
|
|
2532
|
+
const samples = chunkL7A3ECI2_cjs.libavFrameToInterleavedFloat32(f);
|
|
2475
2533
|
if (samples) {
|
|
2476
2534
|
opts.audio.schedule(samples.data, samples.channels, samples.sampleRate);
|
|
2477
2535
|
audioFramesDecoded++;
|
|
@@ -2619,6 +2677,9 @@ async function startDecoder(opts) {
|
|
|
2619
2677
|
(err) => console.error("[avbridge] decoder pump failed (post-seek):", err)
|
|
2620
2678
|
);
|
|
2621
2679
|
},
|
|
2680
|
+
bufferedUntilSec() {
|
|
2681
|
+
return bufferedUntilSec;
|
|
2682
|
+
},
|
|
2622
2683
|
stats() {
|
|
2623
2684
|
return {
|
|
2624
2685
|
decoderType: "libav-wasm",
|
|
@@ -2718,6 +2779,13 @@ async function createFallbackSession(ctx, target, transport) {
|
|
|
2718
2779
|
configurable: true,
|
|
2719
2780
|
get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
|
|
2720
2781
|
});
|
|
2782
|
+
Object.defineProperty(target, "buffered", {
|
|
2783
|
+
configurable: true,
|
|
2784
|
+
get: () => {
|
|
2785
|
+
const end = handles.bufferedUntilSec();
|
|
2786
|
+
return makeTimeRanges(end > 0 ? [[0, end]] : []);
|
|
2787
|
+
}
|
|
2788
|
+
});
|
|
2721
2789
|
async function waitForBuffer() {
|
|
2722
2790
|
const start = performance.now();
|
|
2723
2791
|
let firstFrameAtMs = 0;
|
|
@@ -2757,6 +2825,7 @@ async function createFallbackSession(ctx, target, transport) {
|
|
|
2757
2825
|
}
|
|
2758
2826
|
async function doSeek(timeSec) {
|
|
2759
2827
|
const wasPlaying = audio.isPlaying();
|
|
2828
|
+
target.dispatchEvent(new Event("seeking"));
|
|
2760
2829
|
await audio.pause().catch(() => {
|
|
2761
2830
|
});
|
|
2762
2831
|
await handles.seek(timeSec).catch(
|
|
@@ -2768,7 +2837,14 @@ async function createFallbackSession(ctx, target, transport) {
|
|
|
2768
2837
|
await waitForBuffer();
|
|
2769
2838
|
await audio.start();
|
|
2770
2839
|
}
|
|
2840
|
+
target.dispatchEvent(new Event("seeked"));
|
|
2771
2841
|
}
|
|
2842
|
+
queueMicrotask(() => {
|
|
2843
|
+
try {
|
|
2844
|
+
target.dispatchEvent(new Event("loadedmetadata"));
|
|
2845
|
+
} catch {
|
|
2846
|
+
}
|
|
2847
|
+
});
|
|
2772
2848
|
return {
|
|
2773
2849
|
strategy: "fallback",
|
|
2774
2850
|
async play() {
|
|
@@ -2860,6 +2936,29 @@ function registerBuiltins(registry) {
|
|
|
2860
2936
|
}
|
|
2861
2937
|
|
|
2862
2938
|
// src/player.ts
|
|
2939
|
+
function readDecodedFrameCount(target) {
|
|
2940
|
+
if (typeof HTMLVideoElement === "undefined" || !(target instanceof HTMLVideoElement)) return 0;
|
|
2941
|
+
const vq = target.getVideoPlaybackQuality;
|
|
2942
|
+
if (typeof vq === "function") {
|
|
2943
|
+
try {
|
|
2944
|
+
return vq.call(target).totalVideoFrames;
|
|
2945
|
+
} catch {
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
const legacy = target.webkitDecodedFrameCount;
|
|
2949
|
+
return typeof legacy === "number" ? legacy : 0;
|
|
2950
|
+
}
|
|
2951
|
+
function evaluateDecodeHealth(input) {
|
|
2952
|
+
const timeThreshold = input.timeStallThresholdMs ?? 5e3;
|
|
2953
|
+
const frameThreshold = input.frameStallThresholdMs ?? 3e3;
|
|
2954
|
+
if (!input.timeAdvanced && input.now - input.lastProgressTime > timeThreshold) {
|
|
2955
|
+
return { escalate: true, kind: "time-stall" };
|
|
2956
|
+
}
|
|
2957
|
+
if (input.hasVideoTrack && input.timeAdvanced && !input.framesAdvanced && input.now - input.lastFrameProgressTime > frameThreshold) {
|
|
2958
|
+
return { escalate: true, kind: "silent-video" };
|
|
2959
|
+
}
|
|
2960
|
+
return { escalate: false };
|
|
2961
|
+
}
|
|
2863
2962
|
var UnifiedPlayer = class _UnifiedPlayer {
|
|
2864
2963
|
/**
|
|
2865
2964
|
* @internal Use {@link createPlayer} or {@link UnifiedPlayer.create} instead.
|
|
@@ -2885,6 +2984,13 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2885
2984
|
stallTimer = null;
|
|
2886
2985
|
lastProgressTime = 0;
|
|
2887
2986
|
lastProgressPosition = -1;
|
|
2987
|
+
/** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
|
|
2988
|
+
* (or `webkitDecodedFrameCount` fallback). Used by the silent-video
|
|
2989
|
+
* watchdog — catches cases where `currentTime` advances (audio plays)
|
|
2990
|
+
* but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
|
|
2991
|
+
* via MSE when the decoder actually can't decode HEVC. */
|
|
2992
|
+
lastVideoFrameCount = 0;
|
|
2993
|
+
lastVideoFrameProgressTime = 0;
|
|
2888
2994
|
errorListener = null;
|
|
2889
2995
|
// Bound so we can removeEventListener in destroy(); without this the
|
|
2890
2996
|
// listener outlives the player and accumulates on elements that swap
|
|
@@ -2930,7 +3036,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2930
3036
|
const bootstrapStart = performance.now();
|
|
2931
3037
|
try {
|
|
2932
3038
|
chunkG4APZMCP_cjs.dbg.info("bootstrap", "start");
|
|
2933
|
-
const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () =>
|
|
3039
|
+
const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => chunkBYGZN4Z5_cjs.probe(this.options.source, this.transport));
|
|
2934
3040
|
chunkG4APZMCP_cjs.dbg.info(
|
|
2935
3041
|
"probe",
|
|
2936
3042
|
`container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
|
|
@@ -3129,22 +3235,41 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3129
3235
|
if (strategy === "native" || strategy === "remux") {
|
|
3130
3236
|
this.lastProgressPosition = this.options.target.currentTime;
|
|
3131
3237
|
this.lastProgressTime = performance.now();
|
|
3238
|
+
this.lastVideoFrameCount = readDecodedFrameCount(this.options.target);
|
|
3239
|
+
this.lastVideoFrameProgressTime = performance.now();
|
|
3240
|
+
const hasVideoTrack = (this.mediaContext?.videoTracks.length ?? 0) > 0;
|
|
3132
3241
|
this.stallTimer = setInterval(() => {
|
|
3133
3242
|
const t = this.options.target;
|
|
3243
|
+
const now = performance.now();
|
|
3134
3244
|
if (t.paused || t.ended || t.readyState < 2) {
|
|
3135
3245
|
this.lastProgressPosition = t.currentTime;
|
|
3136
|
-
this.lastProgressTime =
|
|
3246
|
+
this.lastProgressTime = now;
|
|
3247
|
+
this.lastVideoFrameCount = readDecodedFrameCount(t);
|
|
3248
|
+
this.lastVideoFrameProgressTime = now;
|
|
3137
3249
|
return;
|
|
3138
3250
|
}
|
|
3139
|
-
|
|
3251
|
+
const timeAdvanced = t.currentTime !== this.lastProgressPosition;
|
|
3252
|
+
const frames = readDecodedFrameCount(t);
|
|
3253
|
+
const framesAdvanced = frames > this.lastVideoFrameCount;
|
|
3254
|
+
const health = evaluateDecodeHealth({
|
|
3255
|
+
hasVideoTrack,
|
|
3256
|
+
timeAdvanced,
|
|
3257
|
+
framesAdvanced,
|
|
3258
|
+
now,
|
|
3259
|
+
lastProgressTime: this.lastProgressTime,
|
|
3260
|
+
lastFrameProgressTime: this.lastVideoFrameProgressTime
|
|
3261
|
+
});
|
|
3262
|
+
if (timeAdvanced) {
|
|
3140
3263
|
this.lastProgressPosition = t.currentTime;
|
|
3141
|
-
this.lastProgressTime =
|
|
3142
|
-
return;
|
|
3264
|
+
this.lastProgressTime = now;
|
|
3143
3265
|
}
|
|
3144
|
-
if (
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3266
|
+
if (framesAdvanced) {
|
|
3267
|
+
this.lastVideoFrameCount = frames;
|
|
3268
|
+
this.lastVideoFrameProgressTime = now;
|
|
3269
|
+
}
|
|
3270
|
+
if (health.escalate) {
|
|
3271
|
+
const reason = health.kind === "time-stall" ? `${strategy} strategy stalled for 5s at ${t.currentTime.toFixed(1)}s` : `${strategy} strategy: audio is advancing but the video decoder has produced no new frames for 3s \u2014 likely a silent codec failure`;
|
|
3272
|
+
void this.escalate(reason);
|
|
3148
3273
|
}
|
|
3149
3274
|
}, 1e3);
|
|
3150
3275
|
const onError = () => {
|
|
@@ -3381,5 +3506,5 @@ exports.NATIVE_VIDEO_CODECS = NATIVE_VIDEO_CODECS;
|
|
|
3381
3506
|
exports.UnifiedPlayer = UnifiedPlayer;
|
|
3382
3507
|
exports.classifyContext = classifyContext;
|
|
3383
3508
|
exports.createPlayer = createPlayer;
|
|
3384
|
-
//# sourceMappingURL=chunk-
|
|
3385
|
-
//# sourceMappingURL=chunk-
|
|
3509
|
+
//# sourceMappingURL=chunk-EY6DZEDT.cjs.map
|
|
3510
|
+
//# sourceMappingURL=chunk-EY6DZEDT.cjs.map
|