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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ All notable changes to **avbridge.js** are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.8.4]
|
|
8
|
+
|
|
9
|
+
Decode-stall detection — the robustness follow-up that addresses the
|
|
10
|
+
v2.8.1 "Known deferred" item.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Silent-video watchdog in the stall supervisor.** Catches the class
|
|
15
|
+
of bug where MSE reports a codec as supported but the decoder can't
|
|
16
|
+
actually decode it — audio plays, `currentTime` advances, but the
|
|
17
|
+
video decoder never produces frames. The supervisor now samples
|
|
18
|
+
`HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames` (or
|
|
19
|
+
`webkitDecodedFrameCount` on older Safari) and triggers strategy
|
|
20
|
+
escalation when audio is advancing but frames haven't for 3 s. The
|
|
21
|
+
Firefox HEVC case (MSE lies about `hev1.*`) is the motivating
|
|
22
|
+
example, but the watchdog is codec- and browser-agnostic.
|
|
23
|
+
- **`fallbackChain` on `REMUX_CANDIDATE` classifications.** Previously
|
|
24
|
+
the supervisor had nowhere to escalate to from a plain remux
|
|
25
|
+
classification, so stalls sat there forever. The chain is
|
|
26
|
+
`["hybrid", "fallback"]` (or `["fallback"]` without WebCodecs) —
|
|
27
|
+
initial strategy is still remux; the chain only engages on stall.
|
|
28
|
+
- **`evaluateDecodeHealth(input)` and `readDecodedFrameCount(target)`
|
|
29
|
+
extracted as pure helpers** in `src/player.ts` (not re-exported from
|
|
30
|
+
the package root; same pattern as `buildInitialDecision`) so the
|
|
31
|
+
supervisor's decision logic is unit-testable without a browser.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **Firefox HEVC** in `tests/browser/playback.spec.ts` should now
|
|
36
|
+
un-skip once this ships — the watchdog gives Firefox a mechanism to
|
|
37
|
+
escalate off the lying MSE path to hybrid / fallback automatically.
|
|
38
|
+
|
|
39
|
+
## [2.8.3]
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **`showControls(durationMs?)` on `<avbridge-player>`.** Public method
|
|
44
|
+
to reveal the auto-hiding chrome (top toolbar + bottom controls)
|
|
45
|
+
and re-start the auto-hide timer. Intended for app-level "flash the
|
|
46
|
+
UI" moments like a carousel slide change or focus handoff — one call
|
|
47
|
+
instead of reaching into `data-controls-hidden`. Custom duration
|
|
48
|
+
overrides the default 3 s; pointer movement during the flash resets
|
|
49
|
+
the timer so there's no flicker if the user interacts mid-flash.
|
|
50
|
+
|
|
7
51
|
## [2.8.2]
|
|
8
52
|
|
|
9
53
|
Small ergonomics release driven by downstream `<avbridge-player>`
|
|
@@ -231,10 +231,12 @@ function classifyContext(ctx) {
|
|
|
231
231
|
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`
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
|
+
const fallbackChain = webCodecsAvailable() ? ["hybrid", "fallback"] : ["fallback"];
|
|
234
235
|
return {
|
|
235
236
|
class: "REMUX_CANDIDATE",
|
|
236
237
|
strategy: "remux",
|
|
237
|
-
reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback
|
|
238
|
+
reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback`,
|
|
239
|
+
fallbackChain
|
|
238
240
|
};
|
|
239
241
|
}
|
|
240
242
|
if (webCodecsAvailable()) {
|
|
@@ -2858,6 +2860,29 @@ function registerBuiltins(registry) {
|
|
|
2858
2860
|
}
|
|
2859
2861
|
|
|
2860
2862
|
// src/player.ts
|
|
2863
|
+
function readDecodedFrameCount(target) {
|
|
2864
|
+
if (typeof HTMLVideoElement === "undefined" || !(target instanceof HTMLVideoElement)) return 0;
|
|
2865
|
+
const vq = target.getVideoPlaybackQuality;
|
|
2866
|
+
if (typeof vq === "function") {
|
|
2867
|
+
try {
|
|
2868
|
+
return vq.call(target).totalVideoFrames;
|
|
2869
|
+
} catch {
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
const legacy = target.webkitDecodedFrameCount;
|
|
2873
|
+
return typeof legacy === "number" ? legacy : 0;
|
|
2874
|
+
}
|
|
2875
|
+
function evaluateDecodeHealth(input) {
|
|
2876
|
+
const timeThreshold = input.timeStallThresholdMs ?? 5e3;
|
|
2877
|
+
const frameThreshold = input.frameStallThresholdMs ?? 3e3;
|
|
2878
|
+
if (!input.timeAdvanced && input.now - input.lastProgressTime > timeThreshold) {
|
|
2879
|
+
return { escalate: true, kind: "time-stall" };
|
|
2880
|
+
}
|
|
2881
|
+
if (input.hasVideoTrack && input.timeAdvanced && !input.framesAdvanced && input.now - input.lastFrameProgressTime > frameThreshold) {
|
|
2882
|
+
return { escalate: true, kind: "silent-video" };
|
|
2883
|
+
}
|
|
2884
|
+
return { escalate: false };
|
|
2885
|
+
}
|
|
2861
2886
|
var UnifiedPlayer = class _UnifiedPlayer {
|
|
2862
2887
|
/**
|
|
2863
2888
|
* @internal Use {@link createPlayer} or {@link UnifiedPlayer.create} instead.
|
|
@@ -2883,6 +2908,13 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2883
2908
|
stallTimer = null;
|
|
2884
2909
|
lastProgressTime = 0;
|
|
2885
2910
|
lastProgressPosition = -1;
|
|
2911
|
+
/** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
|
|
2912
|
+
* (or `webkitDecodedFrameCount` fallback). Used by the silent-video
|
|
2913
|
+
* watchdog — catches cases where `currentTime` advances (audio plays)
|
|
2914
|
+
* but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
|
|
2915
|
+
* via MSE when the decoder actually can't decode HEVC. */
|
|
2916
|
+
lastVideoFrameCount = 0;
|
|
2917
|
+
lastVideoFrameProgressTime = 0;
|
|
2886
2918
|
errorListener = null;
|
|
2887
2919
|
// Bound so we can removeEventListener in destroy(); without this the
|
|
2888
2920
|
// listener outlives the player and accumulates on elements that swap
|
|
@@ -3127,22 +3159,41 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3127
3159
|
if (strategy === "native" || strategy === "remux") {
|
|
3128
3160
|
this.lastProgressPosition = this.options.target.currentTime;
|
|
3129
3161
|
this.lastProgressTime = performance.now();
|
|
3162
|
+
this.lastVideoFrameCount = readDecodedFrameCount(this.options.target);
|
|
3163
|
+
this.lastVideoFrameProgressTime = performance.now();
|
|
3164
|
+
const hasVideoTrack = (this.mediaContext?.videoTracks.length ?? 0) > 0;
|
|
3130
3165
|
this.stallTimer = setInterval(() => {
|
|
3131
3166
|
const t = this.options.target;
|
|
3167
|
+
const now = performance.now();
|
|
3132
3168
|
if (t.paused || t.ended || t.readyState < 2) {
|
|
3133
3169
|
this.lastProgressPosition = t.currentTime;
|
|
3134
|
-
this.lastProgressTime =
|
|
3170
|
+
this.lastProgressTime = now;
|
|
3171
|
+
this.lastVideoFrameCount = readDecodedFrameCount(t);
|
|
3172
|
+
this.lastVideoFrameProgressTime = now;
|
|
3135
3173
|
return;
|
|
3136
3174
|
}
|
|
3137
|
-
|
|
3175
|
+
const timeAdvanced = t.currentTime !== this.lastProgressPosition;
|
|
3176
|
+
const frames = readDecodedFrameCount(t);
|
|
3177
|
+
const framesAdvanced = frames > this.lastVideoFrameCount;
|
|
3178
|
+
const health = evaluateDecodeHealth({
|
|
3179
|
+
hasVideoTrack,
|
|
3180
|
+
timeAdvanced,
|
|
3181
|
+
framesAdvanced,
|
|
3182
|
+
now,
|
|
3183
|
+
lastProgressTime: this.lastProgressTime,
|
|
3184
|
+
lastFrameProgressTime: this.lastVideoFrameProgressTime
|
|
3185
|
+
});
|
|
3186
|
+
if (timeAdvanced) {
|
|
3138
3187
|
this.lastProgressPosition = t.currentTime;
|
|
3139
|
-
this.lastProgressTime =
|
|
3140
|
-
return;
|
|
3188
|
+
this.lastProgressTime = now;
|
|
3141
3189
|
}
|
|
3142
|
-
if (
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3190
|
+
if (framesAdvanced) {
|
|
3191
|
+
this.lastVideoFrameCount = frames;
|
|
3192
|
+
this.lastVideoFrameProgressTime = now;
|
|
3193
|
+
}
|
|
3194
|
+
if (health.escalate) {
|
|
3195
|
+
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`;
|
|
3196
|
+
void this.escalate(reason);
|
|
3146
3197
|
}
|
|
3147
3198
|
}, 1e3);
|
|
3148
3199
|
const onError = () => {
|
|
@@ -3373,5 +3424,5 @@ function defaultFallbackChain(strategy) {
|
|
|
3373
3424
|
}
|
|
3374
3425
|
|
|
3375
3426
|
export { FALLBACK_AUDIO_CODECS, FALLBACK_VIDEO_CODECS, NATIVE_AUDIO_CODECS, NATIVE_VIDEO_CODECS, UnifiedPlayer, classifyContext, createPlayer };
|
|
3376
|
-
//# sourceMappingURL=chunk-
|
|
3377
|
-
//# sourceMappingURL=chunk-
|
|
3427
|
+
//# sourceMappingURL=chunk-KBWQRGHS.js.map
|
|
3428
|
+
//# sourceMappingURL=chunk-KBWQRGHS.js.map
|