avbridge 2.8.2 → 2.8.4
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 +44 -0
- package/dist/{chunk-JSQOBUQB.js → chunk-KBWQRGHS.js} +62 -11
- package/dist/chunk-KBWQRGHS.js.map +1 -0
- package/dist/{chunk-IUSFLVLJ.cjs → chunk-YX4AGLNF.cjs} +62 -11
- package/dist/chunk-YX4AGLNF.cjs.map +1 -0
- package/dist/element-browser.js +60 -9
- 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 +8 -8
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{player-DXEKOky8.d.cts → player-BptSJPfn.d.cts} +7 -0
- package/dist/{player-DXEKOky8.d.ts → player-BptSJPfn.d.ts} +7 -0
- package/dist/player.cjs +79 -13
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +20 -0
- package/dist/player.d.ts +20 -0
- package/dist/player.js +79 -13
- package/dist/player.js.map +1 -1
- package/package.json +1 -1
- package/src/classify/rules.ts +9 -0
- package/src/element/avbridge-player.ts +20 -4
- package/src/player.ts +96 -8
- package/dist/chunk-IUSFLVLJ.cjs.map +0 -1
- package/dist/chunk-JSQOBUQB.js.map +0 -1
|
@@ -233,10 +233,12 @@ function classifyContext(ctx) {
|
|
|
233
233
|
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
234
|
};
|
|
235
235
|
}
|
|
236
|
+
const fallbackChain = webCodecsAvailable() ? ["hybrid", "fallback"] : ["fallback"];
|
|
236
237
|
return {
|
|
237
238
|
class: "REMUX_CANDIDATE",
|
|
238
239
|
strategy: "remux",
|
|
239
|
-
reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback
|
|
240
|
+
reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback`,
|
|
241
|
+
fallbackChain
|
|
240
242
|
};
|
|
241
243
|
}
|
|
242
244
|
if (webCodecsAvailable()) {
|
|
@@ -2860,6 +2862,29 @@ function registerBuiltins(registry) {
|
|
|
2860
2862
|
}
|
|
2861
2863
|
|
|
2862
2864
|
// src/player.ts
|
|
2865
|
+
function readDecodedFrameCount(target) {
|
|
2866
|
+
if (typeof HTMLVideoElement === "undefined" || !(target instanceof HTMLVideoElement)) return 0;
|
|
2867
|
+
const vq = target.getVideoPlaybackQuality;
|
|
2868
|
+
if (typeof vq === "function") {
|
|
2869
|
+
try {
|
|
2870
|
+
return vq.call(target).totalVideoFrames;
|
|
2871
|
+
} catch {
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
const legacy = target.webkitDecodedFrameCount;
|
|
2875
|
+
return typeof legacy === "number" ? legacy : 0;
|
|
2876
|
+
}
|
|
2877
|
+
function evaluateDecodeHealth(input) {
|
|
2878
|
+
const timeThreshold = input.timeStallThresholdMs ?? 5e3;
|
|
2879
|
+
const frameThreshold = input.frameStallThresholdMs ?? 3e3;
|
|
2880
|
+
if (!input.timeAdvanced && input.now - input.lastProgressTime > timeThreshold) {
|
|
2881
|
+
return { escalate: true, kind: "time-stall" };
|
|
2882
|
+
}
|
|
2883
|
+
if (input.hasVideoTrack && input.timeAdvanced && !input.framesAdvanced && input.now - input.lastFrameProgressTime > frameThreshold) {
|
|
2884
|
+
return { escalate: true, kind: "silent-video" };
|
|
2885
|
+
}
|
|
2886
|
+
return { escalate: false };
|
|
2887
|
+
}
|
|
2863
2888
|
var UnifiedPlayer = class _UnifiedPlayer {
|
|
2864
2889
|
/**
|
|
2865
2890
|
* @internal Use {@link createPlayer} or {@link UnifiedPlayer.create} instead.
|
|
@@ -2885,6 +2910,13 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2885
2910
|
stallTimer = null;
|
|
2886
2911
|
lastProgressTime = 0;
|
|
2887
2912
|
lastProgressPosition = -1;
|
|
2913
|
+
/** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
|
|
2914
|
+
* (or `webkitDecodedFrameCount` fallback). Used by the silent-video
|
|
2915
|
+
* watchdog — catches cases where `currentTime` advances (audio plays)
|
|
2916
|
+
* but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
|
|
2917
|
+
* via MSE when the decoder actually can't decode HEVC. */
|
|
2918
|
+
lastVideoFrameCount = 0;
|
|
2919
|
+
lastVideoFrameProgressTime = 0;
|
|
2888
2920
|
errorListener = null;
|
|
2889
2921
|
// Bound so we can removeEventListener in destroy(); without this the
|
|
2890
2922
|
// listener outlives the player and accumulates on elements that swap
|
|
@@ -3129,22 +3161,41 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3129
3161
|
if (strategy === "native" || strategy === "remux") {
|
|
3130
3162
|
this.lastProgressPosition = this.options.target.currentTime;
|
|
3131
3163
|
this.lastProgressTime = performance.now();
|
|
3164
|
+
this.lastVideoFrameCount = readDecodedFrameCount(this.options.target);
|
|
3165
|
+
this.lastVideoFrameProgressTime = performance.now();
|
|
3166
|
+
const hasVideoTrack = (this.mediaContext?.videoTracks.length ?? 0) > 0;
|
|
3132
3167
|
this.stallTimer = setInterval(() => {
|
|
3133
3168
|
const t = this.options.target;
|
|
3169
|
+
const now = performance.now();
|
|
3134
3170
|
if (t.paused || t.ended || t.readyState < 2) {
|
|
3135
3171
|
this.lastProgressPosition = t.currentTime;
|
|
3136
|
-
this.lastProgressTime =
|
|
3172
|
+
this.lastProgressTime = now;
|
|
3173
|
+
this.lastVideoFrameCount = readDecodedFrameCount(t);
|
|
3174
|
+
this.lastVideoFrameProgressTime = now;
|
|
3137
3175
|
return;
|
|
3138
3176
|
}
|
|
3139
|
-
|
|
3177
|
+
const timeAdvanced = t.currentTime !== this.lastProgressPosition;
|
|
3178
|
+
const frames = readDecodedFrameCount(t);
|
|
3179
|
+
const framesAdvanced = frames > this.lastVideoFrameCount;
|
|
3180
|
+
const health = evaluateDecodeHealth({
|
|
3181
|
+
hasVideoTrack,
|
|
3182
|
+
timeAdvanced,
|
|
3183
|
+
framesAdvanced,
|
|
3184
|
+
now,
|
|
3185
|
+
lastProgressTime: this.lastProgressTime,
|
|
3186
|
+
lastFrameProgressTime: this.lastVideoFrameProgressTime
|
|
3187
|
+
});
|
|
3188
|
+
if (timeAdvanced) {
|
|
3140
3189
|
this.lastProgressPosition = t.currentTime;
|
|
3141
|
-
this.lastProgressTime =
|
|
3142
|
-
return;
|
|
3190
|
+
this.lastProgressTime = now;
|
|
3143
3191
|
}
|
|
3144
|
-
if (
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3192
|
+
if (framesAdvanced) {
|
|
3193
|
+
this.lastVideoFrameCount = frames;
|
|
3194
|
+
this.lastVideoFrameProgressTime = now;
|
|
3195
|
+
}
|
|
3196
|
+
if (health.escalate) {
|
|
3197
|
+
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`;
|
|
3198
|
+
void this.escalate(reason);
|
|
3148
3199
|
}
|
|
3149
3200
|
}, 1e3);
|
|
3150
3201
|
const onError = () => {
|
|
@@ -3381,5 +3432,5 @@ exports.NATIVE_VIDEO_CODECS = NATIVE_VIDEO_CODECS;
|
|
|
3381
3432
|
exports.UnifiedPlayer = UnifiedPlayer;
|
|
3382
3433
|
exports.classifyContext = classifyContext;
|
|
3383
3434
|
exports.createPlayer = createPlayer;
|
|
3384
|
-
//# sourceMappingURL=chunk-
|
|
3385
|
-
//# sourceMappingURL=chunk-
|
|
3435
|
+
//# sourceMappingURL=chunk-YX4AGLNF.cjs.map
|
|
3436
|
+
//# sourceMappingURL=chunk-YX4AGLNF.cjs.map
|