avbridge 2.2.0 → 2.2.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 +45 -0
- package/README.md +98 -71
- package/dist/{chunk-C5VA5U5O.js → chunk-DMWARSEF.js} +76 -21
- package/dist/chunk-DMWARSEF.js.map +1 -0
- package/dist/{chunk-OE66B34H.cjs → chunk-UF2N5L63.cjs} +76 -21
- package/dist/chunk-UF2N5L63.cjs.map +1 -0
- package/dist/element-browser.js +74 -19
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +2 -2
- package/dist/element.d.cts +1 -1
- package/dist/element.d.ts +1 -1
- package/dist/element.js +1 -1
- package/dist/index.cjs +14 -14
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{player-DUyvltvy.d.cts → player-U2NPmFvA.d.cts} +1 -0
- package/dist/{player-DUyvltvy.d.ts → player-U2NPmFvA.d.ts} +1 -0
- package/package.json +1 -1
- package/src/player.ts +24 -16
- package/src/strategies/fallback/index.ts +8 -0
- package/src/strategies/fallback/video-renderer.ts +5 -1
- package/src/strategies/hybrid/index.ts +9 -1
- package/src/strategies/remux/index.ts +13 -1
- package/src/strategies/remux/pipeline.ts +6 -0
- package/dist/chunk-C5VA5U5O.js.map +0 -1
- package/dist/chunk-OE66B34H.cjs.map +0 -1
|
@@ -1072,6 +1072,10 @@ async function createRemuxPipeline(ctx, video) {
|
|
|
1072
1072
|
console.error("[avbridge] remux pipeline reseek failed:", err);
|
|
1073
1073
|
});
|
|
1074
1074
|
},
|
|
1075
|
+
setAutoPlay(autoPlay) {
|
|
1076
|
+
pendingAutoPlay = autoPlay;
|
|
1077
|
+
if (sink) sink.setPlayOnSeek(autoPlay);
|
|
1078
|
+
},
|
|
1075
1079
|
async destroy() {
|
|
1076
1080
|
destroyed = true;
|
|
1077
1081
|
pumpToken++;
|
|
@@ -1112,7 +1116,11 @@ async function createRemuxSession(context, video) {
|
|
|
1112
1116
|
await pipeline.start(video.currentTime || 0, true);
|
|
1113
1117
|
return;
|
|
1114
1118
|
}
|
|
1115
|
-
|
|
1119
|
+
pipeline.setAutoPlay(true);
|
|
1120
|
+
try {
|
|
1121
|
+
await video.play();
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1116
1124
|
},
|
|
1117
1125
|
pause() {
|
|
1118
1126
|
wantPlay = false;
|
|
@@ -1160,7 +1168,7 @@ var VideoRenderer = class {
|
|
|
1160
1168
|
this.resolveFirstFrame = resolve;
|
|
1161
1169
|
});
|
|
1162
1170
|
this.canvas = document.createElement("canvas");
|
|
1163
|
-
this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;";
|
|
1171
|
+
this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;object-fit:contain;";
|
|
1164
1172
|
const parent = target.parentElement ?? target.parentNode;
|
|
1165
1173
|
if (parent && parent instanceof HTMLElement) {
|
|
1166
1174
|
if (getComputedStyle(parent).position === "static") {
|
|
@@ -1402,9 +1410,13 @@ var AudioOutput = class {
|
|
|
1402
1410
|
const node = this.ctx.createBufferSource();
|
|
1403
1411
|
node.buffer = buffer;
|
|
1404
1412
|
node.connect(this.gain);
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1413
|
+
let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
|
|
1414
|
+
if (ctxStart < this.ctx.currentTime) {
|
|
1415
|
+
this.ctxTimeAtAnchor = this.ctx.currentTime;
|
|
1416
|
+
this.mediaTimeOfAnchor = this.mediaTimeOfNext;
|
|
1417
|
+
ctxStart = this.ctx.currentTime;
|
|
1418
|
+
}
|
|
1419
|
+
node.start(ctxStart);
|
|
1408
1420
|
this.mediaTimeOfNext += frameCount / sampleRate;
|
|
1409
1421
|
this.framesScheduled++;
|
|
1410
1422
|
}
|
|
@@ -1972,6 +1984,10 @@ async function createHybridSession(ctx, target) {
|
|
|
1972
1984
|
void doSeek(v);
|
|
1973
1985
|
}
|
|
1974
1986
|
});
|
|
1987
|
+
Object.defineProperty(target, "paused", {
|
|
1988
|
+
configurable: true,
|
|
1989
|
+
get: () => !audio.isPlaying()
|
|
1990
|
+
});
|
|
1975
1991
|
if (ctx.duration && Number.isFinite(ctx.duration)) {
|
|
1976
1992
|
Object.defineProperty(target, "duration", {
|
|
1977
1993
|
configurable: true,
|
|
@@ -2036,6 +2052,7 @@ async function createHybridSession(ctx, target) {
|
|
|
2036
2052
|
try {
|
|
2037
2053
|
delete target.currentTime;
|
|
2038
2054
|
delete target.duration;
|
|
2055
|
+
delete target.paused;
|
|
2039
2056
|
} catch {
|
|
2040
2057
|
}
|
|
2041
2058
|
},
|
|
@@ -2115,7 +2132,8 @@ async function startDecoder(opts) {
|
|
|
2115
2132
|
let audioFramesDecoded = 0;
|
|
2116
2133
|
let watchdogFirstFrameMs = 0;
|
|
2117
2134
|
let watchdogSlowSinceMs = 0;
|
|
2118
|
-
let
|
|
2135
|
+
let watchdogSlowWarned = false;
|
|
2136
|
+
let watchdogOverflowWarned = false;
|
|
2119
2137
|
let syntheticVideoUs = 0;
|
|
2120
2138
|
let syntheticAudioUs = 0;
|
|
2121
2139
|
const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
|
|
@@ -2127,7 +2145,7 @@ async function startDecoder(opts) {
|
|
|
2127
2145
|
let packets;
|
|
2128
2146
|
try {
|
|
2129
2147
|
[readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
|
|
2130
|
-
limit:
|
|
2148
|
+
limit: 16 * 1024
|
|
2131
2149
|
});
|
|
2132
2150
|
} catch (err) {
|
|
2133
2151
|
console.error("[avbridge] ff_read_frame_multi failed:", err);
|
|
@@ -2136,26 +2154,26 @@ async function startDecoder(opts) {
|
|
|
2136
2154
|
if (myToken !== pumpToken || destroyed) return;
|
|
2137
2155
|
const videoPackets = videoStream ? packets[videoStream.index] : void 0;
|
|
2138
2156
|
const audioPackets = audioStream ? packets[audioStream.index] : void 0;
|
|
2139
|
-
if (videoDec && videoPackets && videoPackets.length > 0) {
|
|
2140
|
-
await decodeVideoBatch(videoPackets, myToken);
|
|
2141
|
-
}
|
|
2142
|
-
if (myToken !== pumpToken || destroyed) return;
|
|
2143
2157
|
if (audioDec && audioPackets && audioPackets.length > 0) {
|
|
2144
2158
|
await decodeAudioBatch(audioPackets, myToken);
|
|
2145
2159
|
}
|
|
2160
|
+
if (myToken !== pumpToken || destroyed) return;
|
|
2161
|
+
if (videoDec && videoPackets && videoPackets.length > 0) {
|
|
2162
|
+
await decodeVideoBatch(videoPackets, myToken);
|
|
2163
|
+
}
|
|
2146
2164
|
packetsRead += (videoPackets?.length ?? 0) + (audioPackets?.length ?? 0);
|
|
2147
2165
|
if (videoFramesDecoded > 0) {
|
|
2148
2166
|
if (watchdogFirstFrameMs === 0) {
|
|
2149
2167
|
watchdogFirstFrameMs = performance.now();
|
|
2150
2168
|
}
|
|
2151
2169
|
const elapsedSinceFirst = (performance.now() - watchdogFirstFrameMs) / 1e3;
|
|
2152
|
-
if (elapsedSinceFirst > 1 && !
|
|
2170
|
+
if (elapsedSinceFirst > 1 && !watchdogSlowWarned) {
|
|
2153
2171
|
const expectedFrames = elapsedSinceFirst * videoFps;
|
|
2154
2172
|
const ratio = videoFramesDecoded / expectedFrames;
|
|
2155
2173
|
if (ratio < 0.6) {
|
|
2156
2174
|
if (watchdogSlowSinceMs === 0) watchdogSlowSinceMs = performance.now();
|
|
2157
2175
|
if ((performance.now() - watchdogSlowSinceMs) / 1e3 > 5) {
|
|
2158
|
-
|
|
2176
|
+
watchdogSlowWarned = true;
|
|
2159
2177
|
console.warn(
|
|
2160
2178
|
"[avbridge:decode-rate]",
|
|
2161
2179
|
`decoder is running slower than realtime: ${videoFramesDecoded} frames in ${elapsedSinceFirst.toFixed(1)}s (${(videoFramesDecoded / elapsedSinceFirst).toFixed(1)} fps vs ${videoFps} fps source \u2014 ${(ratio * 100).toFixed(0)}% of realtime). Playback will stutter. Typical causes: software decode of a codec with no WebCodecs support (rv40, mpeg4 @ 720p+, wmv3), or a resolution too large for single-threaded WASM to keep up with.`
|
|
@@ -2165,6 +2183,17 @@ async function startDecoder(opts) {
|
|
|
2165
2183
|
watchdogSlowSinceMs = 0;
|
|
2166
2184
|
}
|
|
2167
2185
|
}
|
|
2186
|
+
if (!watchdogOverflowWarned && videoFramesDecoded > 100) {
|
|
2187
|
+
const rendererStats = opts.renderer.stats();
|
|
2188
|
+
const overflow = rendererStats.framesDroppedOverflow ?? 0;
|
|
2189
|
+
if (overflow / videoFramesDecoded > 0.1) {
|
|
2190
|
+
watchdogOverflowWarned = true;
|
|
2191
|
+
console.warn(
|
|
2192
|
+
"[avbridge:overflow-drop]",
|
|
2193
|
+
`renderer is dropping ${overflow}/${videoFramesDecoded} frames (${(overflow / videoFramesDecoded * 100).toFixed(0)}%) because the decoder is producing bursts faster than the canvas can drain. Symptom: choppy playback despite decoder keeping up on average. Fix would be smaller read batches in the pump loop or a lower queueHighWater cap \u2014 see src/strategies/fallback/decoder.ts.`
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2168
2197
|
}
|
|
2169
2198
|
while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
|
|
2170
2199
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -2521,6 +2550,10 @@ async function createFallbackSession(ctx, target) {
|
|
|
2521
2550
|
void doSeek(v);
|
|
2522
2551
|
}
|
|
2523
2552
|
});
|
|
2553
|
+
Object.defineProperty(target, "paused", {
|
|
2554
|
+
configurable: true,
|
|
2555
|
+
get: () => !audio.isPlaying()
|
|
2556
|
+
});
|
|
2524
2557
|
if (ctx.duration && Number.isFinite(ctx.duration)) {
|
|
2525
2558
|
Object.defineProperty(target, "duration", {
|
|
2526
2559
|
configurable: true,
|
|
@@ -2529,25 +2562,35 @@ async function createFallbackSession(ctx, target) {
|
|
|
2529
2562
|
}
|
|
2530
2563
|
async function waitForBuffer() {
|
|
2531
2564
|
const start = performance.now();
|
|
2565
|
+
let firstFrameAtMs = 0;
|
|
2532
2566
|
chunkG4APZMCP_cjs.dbg.info(
|
|
2533
2567
|
"cold-start",
|
|
2534
|
-
`gate entry:
|
|
2568
|
+
`gate entry: want audio \u2265 ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms + 1 frame`
|
|
2535
2569
|
);
|
|
2536
2570
|
while (true) {
|
|
2537
2571
|
const audioAhead = audio.isNoAudio() ? Infinity : audio.bufferAhead();
|
|
2538
2572
|
const audioReady = audio.isNoAudio() || audioAhead >= READY_AUDIO_BUFFER_SECONDS2;
|
|
2539
2573
|
const hasFrames = renderer.hasFrames();
|
|
2574
|
+
const nowMs = performance.now();
|
|
2575
|
+
if (hasFrames && firstFrameAtMs === 0) firstFrameAtMs = nowMs;
|
|
2540
2576
|
if (audioReady && hasFrames) {
|
|
2541
2577
|
chunkG4APZMCP_cjs.dbg.info(
|
|
2542
2578
|
"cold-start",
|
|
2543
|
-
`gate satisfied in ${(
|
|
2579
|
+
`gate satisfied in ${(nowMs - start).toFixed(0)}ms (audio=${(audioAhead * 1e3).toFixed(0)}ms, frames=${renderer.queueDepth()})`
|
|
2544
2580
|
);
|
|
2545
2581
|
return;
|
|
2546
2582
|
}
|
|
2547
|
-
if (
|
|
2583
|
+
if (hasFrames && firstFrameAtMs > 0 && nowMs - firstFrameAtMs >= 500) {
|
|
2584
|
+
chunkG4APZMCP_cjs.dbg.info(
|
|
2585
|
+
"cold-start",
|
|
2586
|
+
`gate released on video-only grace at ${(nowMs - start).toFixed(0)}ms (frames=${renderer.queueDepth()}, audio=${(audioAhead * 1e3).toFixed(0)}ms \u2014 demuxer hasn't delivered audio packets yet, starting anyway and letting the audio scheduler catch up at its media-time anchor)`
|
|
2587
|
+
);
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
if ((nowMs - start) / 1e3 > READY_TIMEOUT_SECONDS2) {
|
|
2548
2591
|
chunkG4APZMCP_cjs.dbg.diag(
|
|
2549
2592
|
"cold-start",
|
|
2550
|
-
`gate TIMEOUT after ${READY_TIMEOUT_SECONDS2}s \u2014 audio=${(audioAhead * 1e3).toFixed(0)}ms (needed ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms), frames=${renderer.queueDepth()} (needed \u22651).
|
|
2593
|
+
`gate TIMEOUT after ${READY_TIMEOUT_SECONDS2}s \u2014 audio=${(audioAhead * 1e3).toFixed(0)}ms (needed ${READY_AUDIO_BUFFER_SECONDS2 * 1e3}ms), frames=${renderer.queueDepth()} (needed \u22651). Decoder produced nothing in ${READY_TIMEOUT_SECONDS2}s \u2014 either a corrupt source, a missing codec, or WASM is catastrophically slow on this file. Check getDiagnostics().runtime for decode counters.`
|
|
2551
2594
|
);
|
|
2552
2595
|
return;
|
|
2553
2596
|
}
|
|
@@ -2596,6 +2639,7 @@ async function createFallbackSession(ctx, target) {
|
|
|
2596
2639
|
try {
|
|
2597
2640
|
delete target.currentTime;
|
|
2598
2641
|
delete target.duration;
|
|
2642
|
+
delete target.paused;
|
|
2599
2643
|
} catch {
|
|
2600
2644
|
}
|
|
2601
2645
|
},
|
|
@@ -2738,6 +2782,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2738
2782
|
lastProgressTime = 0;
|
|
2739
2783
|
lastProgressPosition = -1;
|
|
2740
2784
|
errorListener = null;
|
|
2785
|
+
// Bound so we can removeEventListener in destroy(); without this the
|
|
2786
|
+
// listener outlives the player and accumulates on elements that swap
|
|
2787
|
+
// source (e.g. <avbridge-video>).
|
|
2788
|
+
endedListener = null;
|
|
2741
2789
|
// Serializes escalation / setStrategy calls
|
|
2742
2790
|
switchingPromise = Promise.resolve();
|
|
2743
2791
|
// Owns blob URLs created during sidecar discovery + SRT->VTT conversion.
|
|
@@ -2823,7 +2871,8 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2823
2871
|
subtitle: ctx.subtitleTracks
|
|
2824
2872
|
});
|
|
2825
2873
|
this.startTimeupdateLoop();
|
|
2826
|
-
this.
|
|
2874
|
+
this.endedListener = () => this.emitter.emit("ended", void 0);
|
|
2875
|
+
this.options.target.addEventListener("ended", this.endedListener);
|
|
2827
2876
|
this.emitter.emitSticky("ready", void 0);
|
|
2828
2877
|
const bootstrapElapsed = performance.now() - bootstrapStart;
|
|
2829
2878
|
chunkG4APZMCP_cjs.dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
|
|
@@ -3109,6 +3158,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3109
3158
|
this.timeupdateInterval = null;
|
|
3110
3159
|
}
|
|
3111
3160
|
this.clearSupervisor();
|
|
3161
|
+
if (this.endedListener) {
|
|
3162
|
+
this.options.target.removeEventListener("ended", this.endedListener);
|
|
3163
|
+
this.endedListener = null;
|
|
3164
|
+
}
|
|
3112
3165
|
if (this.session) {
|
|
3113
3166
|
await this.session.destroy();
|
|
3114
3167
|
this.session = null;
|
|
@@ -3123,11 +3176,13 @@ async function createPlayer(options) {
|
|
|
3123
3176
|
function buildInitialDecision(initial, ctx) {
|
|
3124
3177
|
const natural = classifyContext(ctx);
|
|
3125
3178
|
const cls = strategyToClass(initial, natural);
|
|
3179
|
+
const inherited = natural.fallbackChain ?? defaultFallbackChain(initial);
|
|
3180
|
+
const fallbackChain = inherited.filter((s) => s !== initial);
|
|
3126
3181
|
return {
|
|
3127
3182
|
class: cls,
|
|
3128
3183
|
strategy: initial,
|
|
3129
3184
|
reason: `initial strategy "${initial}" requested via options.initialStrategy`,
|
|
3130
|
-
fallbackChain
|
|
3185
|
+
fallbackChain
|
|
3131
3186
|
};
|
|
3132
3187
|
}
|
|
3133
3188
|
function strategyToClass(strategy, natural) {
|
|
@@ -3164,5 +3219,5 @@ exports.classifyContext = classifyContext;
|
|
|
3164
3219
|
exports.createPlayer = createPlayer;
|
|
3165
3220
|
exports.probe = probe;
|
|
3166
3221
|
exports.srtToVtt = srtToVtt;
|
|
3167
|
-
//# sourceMappingURL=chunk-
|
|
3168
|
-
//# sourceMappingURL=chunk-
|
|
3222
|
+
//# sourceMappingURL=chunk-UF2N5L63.cjs.map
|
|
3223
|
+
//# sourceMappingURL=chunk-UF2N5L63.cjs.map
|