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/dist/player.d.cts
CHANGED
|
@@ -358,6 +358,19 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
358
358
|
private _updateStats;
|
|
359
359
|
private _toggleFullscreen;
|
|
360
360
|
private _updateFullscreenIcon;
|
|
361
|
+
/**
|
|
362
|
+
* Reveal the auto-hiding chrome (top toolbar + bottom controls) and
|
|
363
|
+
* re-start the auto-hide timer. Call this from app-level code to
|
|
364
|
+
* briefly surface the player UI — e.g. to confirm "you just swiped to
|
|
365
|
+
* this video" in a carousel, or to flash the title on focus change.
|
|
366
|
+
*
|
|
367
|
+
* @param durationMs How long the chrome stays visible before fading.
|
|
368
|
+
* Defaults to the player's normal 3 s auto-hide.
|
|
369
|
+
* Pointer movement or any other interaction resets
|
|
370
|
+
* the timer, so a user hovering during the flash
|
|
371
|
+
* sees no flicker.
|
|
372
|
+
*/
|
|
373
|
+
showControls(durationMs?: number): void;
|
|
361
374
|
private _showControls;
|
|
362
375
|
private _scheduleHide;
|
|
363
376
|
/** Track whether the last interaction was touch so click handler can skip. */
|
|
@@ -446,6 +459,13 @@ declare class UnifiedPlayer {
|
|
|
446
459
|
private stallTimer;
|
|
447
460
|
private lastProgressTime;
|
|
448
461
|
private lastProgressPosition;
|
|
462
|
+
/** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
|
|
463
|
+
* (or `webkitDecodedFrameCount` fallback). Used by the silent-video
|
|
464
|
+
* watchdog — catches cases where `currentTime` advances (audio plays)
|
|
465
|
+
* but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
|
|
466
|
+
* via MSE when the decoder actually can't decode HEVC. */
|
|
467
|
+
private lastVideoFrameCount;
|
|
468
|
+
private lastVideoFrameProgressTime;
|
|
449
469
|
private errorListener;
|
|
450
470
|
private endedListener;
|
|
451
471
|
private userIntent;
|
package/dist/player.d.ts
CHANGED
|
@@ -358,6 +358,19 @@ declare class AvbridgePlayerElement extends HTMLElement {
|
|
|
358
358
|
private _updateStats;
|
|
359
359
|
private _toggleFullscreen;
|
|
360
360
|
private _updateFullscreenIcon;
|
|
361
|
+
/**
|
|
362
|
+
* Reveal the auto-hiding chrome (top toolbar + bottom controls) and
|
|
363
|
+
* re-start the auto-hide timer. Call this from app-level code to
|
|
364
|
+
* briefly surface the player UI — e.g. to confirm "you just swiped to
|
|
365
|
+
* this video" in a carousel, or to flash the title on focus change.
|
|
366
|
+
*
|
|
367
|
+
* @param durationMs How long the chrome stays visible before fading.
|
|
368
|
+
* Defaults to the player's normal 3 s auto-hide.
|
|
369
|
+
* Pointer movement or any other interaction resets
|
|
370
|
+
* the timer, so a user hovering during the flash
|
|
371
|
+
* sees no flicker.
|
|
372
|
+
*/
|
|
373
|
+
showControls(durationMs?: number): void;
|
|
361
374
|
private _showControls;
|
|
362
375
|
private _scheduleHide;
|
|
363
376
|
/** Track whether the last interaction was touch so click handler can skip. */
|
|
@@ -446,6 +459,13 @@ declare class UnifiedPlayer {
|
|
|
446
459
|
private stallTimer;
|
|
447
460
|
private lastProgressTime;
|
|
448
461
|
private lastProgressPosition;
|
|
462
|
+
/** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
|
|
463
|
+
* (or `webkitDecodedFrameCount` fallback). Used by the silent-video
|
|
464
|
+
* watchdog — catches cases where `currentTime` advances (audio plays)
|
|
465
|
+
* but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
|
|
466
|
+
* via MSE when the decoder actually can't decode HEVC. */
|
|
467
|
+
private lastVideoFrameCount;
|
|
468
|
+
private lastVideoFrameProgressTime;
|
|
449
469
|
private errorListener;
|
|
450
470
|
private endedListener;
|
|
451
471
|
private userIntent;
|
package/dist/player.js
CHANGED
|
@@ -510,10 +510,12 @@ function classifyContext(ctx) {
|
|
|
510
510
|
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`
|
|
511
511
|
};
|
|
512
512
|
}
|
|
513
|
+
const fallbackChain = webCodecsAvailable() ? ["hybrid", "fallback"] : ["fallback"];
|
|
513
514
|
return {
|
|
514
515
|
class: "REMUX_CANDIDATE",
|
|
515
516
|
strategy: "remux",
|
|
516
|
-
reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback
|
|
517
|
+
reason: `${ctx.container} container with native-supported codecs \u2014 remux to fragmented MP4 for reliable playback`,
|
|
518
|
+
fallbackChain
|
|
517
519
|
};
|
|
518
520
|
}
|
|
519
521
|
if (webCodecsAvailable()) {
|
|
@@ -3258,6 +3260,29 @@ function registerBuiltins(registry) {
|
|
|
3258
3260
|
}
|
|
3259
3261
|
|
|
3260
3262
|
// src/player.ts
|
|
3263
|
+
function readDecodedFrameCount(target) {
|
|
3264
|
+
if (typeof HTMLVideoElement === "undefined" || !(target instanceof HTMLVideoElement)) return 0;
|
|
3265
|
+
const vq = target.getVideoPlaybackQuality;
|
|
3266
|
+
if (typeof vq === "function") {
|
|
3267
|
+
try {
|
|
3268
|
+
return vq.call(target).totalVideoFrames;
|
|
3269
|
+
} catch {
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
const legacy = target.webkitDecodedFrameCount;
|
|
3273
|
+
return typeof legacy === "number" ? legacy : 0;
|
|
3274
|
+
}
|
|
3275
|
+
function evaluateDecodeHealth(input) {
|
|
3276
|
+
const timeThreshold = input.timeStallThresholdMs ?? 5e3;
|
|
3277
|
+
const frameThreshold = input.frameStallThresholdMs ?? 3e3;
|
|
3278
|
+
if (!input.timeAdvanced && input.now - input.lastProgressTime > timeThreshold) {
|
|
3279
|
+
return { escalate: true, kind: "time-stall" };
|
|
3280
|
+
}
|
|
3281
|
+
if (input.hasVideoTrack && input.timeAdvanced && !input.framesAdvanced && input.now - input.lastFrameProgressTime > frameThreshold) {
|
|
3282
|
+
return { escalate: true, kind: "silent-video" };
|
|
3283
|
+
}
|
|
3284
|
+
return { escalate: false };
|
|
3285
|
+
}
|
|
3261
3286
|
var UnifiedPlayer = class _UnifiedPlayer {
|
|
3262
3287
|
/**
|
|
3263
3288
|
* @internal Use {@link createPlayer} or {@link UnifiedPlayer.create} instead.
|
|
@@ -3283,6 +3308,13 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3283
3308
|
stallTimer = null;
|
|
3284
3309
|
lastProgressTime = 0;
|
|
3285
3310
|
lastProgressPosition = -1;
|
|
3311
|
+
/** Last observed `HTMLVideoElement.getVideoPlaybackQuality().totalVideoFrames`
|
|
3312
|
+
* (or `webkitDecodedFrameCount` fallback). Used by the silent-video
|
|
3313
|
+
* watchdog — catches cases where `currentTime` advances (audio plays)
|
|
3314
|
+
* but the decoder produces no frames, e.g. Firefox claiming `hev1.*`
|
|
3315
|
+
* via MSE when the decoder actually can't decode HEVC. */
|
|
3316
|
+
lastVideoFrameCount = 0;
|
|
3317
|
+
lastVideoFrameProgressTime = 0;
|
|
3286
3318
|
errorListener = null;
|
|
3287
3319
|
// Bound so we can removeEventListener in destroy(); without this the
|
|
3288
3320
|
// listener outlives the player and accumulates on elements that swap
|
|
@@ -3527,22 +3559,41 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3527
3559
|
if (strategy === "native" || strategy === "remux") {
|
|
3528
3560
|
this.lastProgressPosition = this.options.target.currentTime;
|
|
3529
3561
|
this.lastProgressTime = performance.now();
|
|
3562
|
+
this.lastVideoFrameCount = readDecodedFrameCount(this.options.target);
|
|
3563
|
+
this.lastVideoFrameProgressTime = performance.now();
|
|
3564
|
+
const hasVideoTrack = (this.mediaContext?.videoTracks.length ?? 0) > 0;
|
|
3530
3565
|
this.stallTimer = setInterval(() => {
|
|
3531
3566
|
const t = this.options.target;
|
|
3567
|
+
const now = performance.now();
|
|
3532
3568
|
if (t.paused || t.ended || t.readyState < 2) {
|
|
3533
3569
|
this.lastProgressPosition = t.currentTime;
|
|
3534
|
-
this.lastProgressTime =
|
|
3570
|
+
this.lastProgressTime = now;
|
|
3571
|
+
this.lastVideoFrameCount = readDecodedFrameCount(t);
|
|
3572
|
+
this.lastVideoFrameProgressTime = now;
|
|
3535
3573
|
return;
|
|
3536
3574
|
}
|
|
3537
|
-
|
|
3575
|
+
const timeAdvanced = t.currentTime !== this.lastProgressPosition;
|
|
3576
|
+
const frames = readDecodedFrameCount(t);
|
|
3577
|
+
const framesAdvanced = frames > this.lastVideoFrameCount;
|
|
3578
|
+
const health = evaluateDecodeHealth({
|
|
3579
|
+
hasVideoTrack,
|
|
3580
|
+
timeAdvanced,
|
|
3581
|
+
framesAdvanced,
|
|
3582
|
+
now,
|
|
3583
|
+
lastProgressTime: this.lastProgressTime,
|
|
3584
|
+
lastFrameProgressTime: this.lastVideoFrameProgressTime
|
|
3585
|
+
});
|
|
3586
|
+
if (timeAdvanced) {
|
|
3538
3587
|
this.lastProgressPosition = t.currentTime;
|
|
3539
|
-
this.lastProgressTime =
|
|
3540
|
-
return;
|
|
3588
|
+
this.lastProgressTime = now;
|
|
3541
3589
|
}
|
|
3542
|
-
if (
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3590
|
+
if (framesAdvanced) {
|
|
3591
|
+
this.lastVideoFrameCount = frames;
|
|
3592
|
+
this.lastVideoFrameProgressTime = now;
|
|
3593
|
+
}
|
|
3594
|
+
if (health.escalate) {
|
|
3595
|
+
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`;
|
|
3596
|
+
void this.escalate(reason);
|
|
3546
3597
|
}
|
|
3547
3598
|
}, 1e3);
|
|
3548
3599
|
const onError = () => {
|
|
@@ -5646,12 +5697,27 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5646
5697
|
this._fullscreenBtn.innerHTML = fs ? ICON_FULLSCREEN_EXIT : ICON_FULLSCREEN;
|
|
5647
5698
|
}
|
|
5648
5699
|
// ── Controls: auto-hide ────────────────────────────────────────────────
|
|
5649
|
-
|
|
5700
|
+
/**
|
|
5701
|
+
* Reveal the auto-hiding chrome (top toolbar + bottom controls) and
|
|
5702
|
+
* re-start the auto-hide timer. Call this from app-level code to
|
|
5703
|
+
* briefly surface the player UI — e.g. to confirm "you just swiped to
|
|
5704
|
+
* this video" in a carousel, or to flash the title on focus change.
|
|
5705
|
+
*
|
|
5706
|
+
* @param durationMs How long the chrome stays visible before fading.
|
|
5707
|
+
* Defaults to the player's normal 3 s auto-hide.
|
|
5708
|
+
* Pointer movement or any other interaction resets
|
|
5709
|
+
* the timer, so a user hovering during the flash
|
|
5710
|
+
* sees no flicker.
|
|
5711
|
+
*/
|
|
5712
|
+
showControls(durationMs) {
|
|
5650
5713
|
this.removeAttribute("data-controls-hidden");
|
|
5651
5714
|
this._toolbarTop.setAttribute("data-visible", "true");
|
|
5652
|
-
this._scheduleHide();
|
|
5715
|
+
this._scheduleHide(durationMs);
|
|
5716
|
+
}
|
|
5717
|
+
_showControls() {
|
|
5718
|
+
this.showControls();
|
|
5653
5719
|
}
|
|
5654
|
-
_scheduleHide() {
|
|
5720
|
+
_scheduleHide(durationMs = CONTROLS_HIDE_MS) {
|
|
5655
5721
|
if (this._controlsTimer) clearTimeout(this._controlsTimer);
|
|
5656
5722
|
if (this._state !== "playing" && this._state !== "buffering") return;
|
|
5657
5723
|
if (this._settingsOpen) return;
|
|
@@ -5660,7 +5726,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
|
|
|
5660
5726
|
this.setAttribute("data-controls-hidden", "");
|
|
5661
5727
|
this._toolbarTop.setAttribute("data-visible", "false");
|
|
5662
5728
|
}
|
|
5663
|
-
},
|
|
5729
|
+
}, durationMs);
|
|
5664
5730
|
}
|
|
5665
5731
|
// Strategy is visible in Stats for Nerds, no badge in controls bar.
|
|
5666
5732
|
// ── Click / tap handling (YouTube delayed-tap pattern) ──────────────────
|