avbridge 2.8.1 → 2.8.3
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 +31 -0
- package/README.md +14 -2
- package/dist/player.cjs +32 -1
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +2 -1
- package/dist/player.d.ts +2 -1
- package/dist/player.js +32 -1
- package/dist/player.js.map +1 -1
- package/package.json +1 -1
- package/src/element/avbridge-player.ts +63 -6
package/package.json
CHANGED
|
@@ -61,10 +61,16 @@ const PROXY_ATTRIBUTES = [
|
|
|
61
61
|
"fit",
|
|
62
62
|
] as const;
|
|
63
63
|
|
|
64
|
+
/** Player-only attributes that don't forward to <avbridge-video>. */
|
|
65
|
+
const PLAYER_ATTRIBUTES = ["show-fit"] as const;
|
|
66
|
+
|
|
67
|
+
const FIT_MODES = ["contain", "cover", "fill"] as const;
|
|
68
|
+
type FitMode = (typeof FIT_MODES)[number];
|
|
69
|
+
|
|
64
70
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
65
71
|
|
|
66
72
|
export class AvbridgePlayerElement extends HTMLElement {
|
|
67
|
-
static readonly observedAttributes = [...PROXY_ATTRIBUTES];
|
|
73
|
+
static readonly observedAttributes = [...PROXY_ATTRIBUTES, ...PLAYER_ATTRIBUTES];
|
|
68
74
|
|
|
69
75
|
// ── Internal DOM refs ──────────────────────────────────────────────────
|
|
70
76
|
|
|
@@ -104,6 +110,7 @@ export class AvbridgePlayerElement extends HTMLElement {
|
|
|
104
110
|
private _statsInterval: ReturnType<typeof setInterval> | null = null;
|
|
105
111
|
private _eventCleanup: (() => void)[] = [];
|
|
106
112
|
private _updateToolbarEmpty: () => void = () => { /* wired in constructor */ };
|
|
113
|
+
private _toolbarTop!: HTMLDivElement;
|
|
107
114
|
|
|
108
115
|
// ── Constructor ────────────────────────────────────────────────────────
|
|
109
116
|
|
|
@@ -133,6 +140,9 @@ export class AvbridgePlayerElement extends HTMLElement {
|
|
|
133
140
|
this._statsEl = shadow.querySelector(".avp-stats") as HTMLDivElement;
|
|
134
141
|
this._rippleLeft = shadow.querySelector(".avp-ripple-left") as HTMLDivElement;
|
|
135
142
|
this._rippleRight = shadow.querySelector(".avp-ripple-right") as HTMLDivElement;
|
|
143
|
+
this._toolbarTop = shadow.querySelector('[part="toolbar-top"]') as HTMLDivElement;
|
|
144
|
+
// Start visible — controls are shown until the auto-hide timer fires.
|
|
145
|
+
this._toolbarTop.setAttribute("data-visible", "true");
|
|
136
146
|
|
|
137
147
|
// Track whether the top toolbar has any slotted content. Used to hide
|
|
138
148
|
// its gradient when empty (see data-toolbar-empty in player-styles.ts).
|
|
@@ -365,8 +375,15 @@ export class AvbridgePlayerElement extends HTMLElement {
|
|
|
365
375
|
}
|
|
366
376
|
|
|
367
377
|
attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
|
|
368
|
-
// Proxy attributes down to inner avbridge-video
|
|
369
378
|
if (!this._video) return;
|
|
379
|
+
// Player-only attributes — not forwarded to the inner <avbridge-video>.
|
|
380
|
+
if ((PLAYER_ATTRIBUTES as readonly string[]).includes(name)) {
|
|
381
|
+
if (name === "show-fit" && this._settingsOpen) {
|
|
382
|
+
this._buildSettingsMenu();
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
// Proxy everything else down.
|
|
370
387
|
if (value == null) this._video.removeAttribute(name);
|
|
371
388
|
else this._video.setAttribute(name, value);
|
|
372
389
|
}
|
|
@@ -520,6 +537,20 @@ export class AvbridgePlayerElement extends HTMLElement {
|
|
|
520
537
|
private _buildSettingsMenu(): void {
|
|
521
538
|
const sections: string[] = [];
|
|
522
539
|
|
|
540
|
+
// Fit mode — opt-in via the `show-fit` attribute. Off by default so
|
|
541
|
+
// chromeless consumers don't get a surprise entry they have to theme
|
|
542
|
+
// around.
|
|
543
|
+
if (this.hasAttribute("show-fit")) {
|
|
544
|
+
const currentFit = (this._video.fit ?? "contain") as FitMode;
|
|
545
|
+
let fitItems = "";
|
|
546
|
+
for (const mode of FIT_MODES) {
|
|
547
|
+
const active = mode === currentFit;
|
|
548
|
+
const label = mode[0].toUpperCase() + mode.slice(1);
|
|
549
|
+
fitItems += `<div class="avp-settings-item${active ? " active" : ""}" data-fit="${mode}">${label}</div>`;
|
|
550
|
+
}
|
|
551
|
+
sections.push(`<div class="avp-settings-section"><div class="avp-settings-label">Fit</div>${fitItems}</div>`);
|
|
552
|
+
}
|
|
553
|
+
|
|
523
554
|
// Playback speed
|
|
524
555
|
const currentRate = this._video.playbackRate ?? 1;
|
|
525
556
|
let speedItems = "";
|
|
@@ -556,6 +587,14 @@ export class AvbridgePlayerElement extends HTMLElement {
|
|
|
556
587
|
this._settingsMenu.innerHTML = sections.join("");
|
|
557
588
|
|
|
558
589
|
// Bind click handlers
|
|
590
|
+
for (const item of this._settingsMenu.querySelectorAll("[data-fit]")) {
|
|
591
|
+
item.addEventListener("click", (e) => {
|
|
592
|
+
e.stopPropagation();
|
|
593
|
+
const mode = (item as HTMLElement).dataset.fit as FitMode;
|
|
594
|
+
this.setAttribute("fit", mode);
|
|
595
|
+
this._buildSettingsMenu();
|
|
596
|
+
});
|
|
597
|
+
}
|
|
559
598
|
for (const item of this._settingsMenu.querySelectorAll("[data-speed]")) {
|
|
560
599
|
item.addEventListener("click", (e) => {
|
|
561
600
|
e.stopPropagation();
|
|
@@ -641,20 +680,38 @@ export class AvbridgePlayerElement extends HTMLElement {
|
|
|
641
680
|
|
|
642
681
|
// ── Controls: auto-hide ────────────────────────────────────────────────
|
|
643
682
|
|
|
644
|
-
|
|
683
|
+
/**
|
|
684
|
+
* Reveal the auto-hiding chrome (top toolbar + bottom controls) and
|
|
685
|
+
* re-start the auto-hide timer. Call this from app-level code to
|
|
686
|
+
* briefly surface the player UI — e.g. to confirm "you just swiped to
|
|
687
|
+
* this video" in a carousel, or to flash the title on focus change.
|
|
688
|
+
*
|
|
689
|
+
* @param durationMs How long the chrome stays visible before fading.
|
|
690
|
+
* Defaults to the player's normal 3 s auto-hide.
|
|
691
|
+
* Pointer movement or any other interaction resets
|
|
692
|
+
* the timer, so a user hovering during the flash
|
|
693
|
+
* sees no flicker.
|
|
694
|
+
*/
|
|
695
|
+
showControls(durationMs?: number): void {
|
|
645
696
|
this.removeAttribute("data-controls-hidden");
|
|
646
|
-
this.
|
|
697
|
+
this._toolbarTop.setAttribute("data-visible", "true");
|
|
698
|
+
this._scheduleHide(durationMs);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
private _showControls(): void {
|
|
702
|
+
this.showControls();
|
|
647
703
|
}
|
|
648
704
|
|
|
649
|
-
private _scheduleHide(): void {
|
|
705
|
+
private _scheduleHide(durationMs: number = CONTROLS_HIDE_MS): void {
|
|
650
706
|
if (this._controlsTimer) clearTimeout(this._controlsTimer);
|
|
651
707
|
if (this._state !== "playing" && this._state !== "buffering") return;
|
|
652
708
|
if (this._settingsOpen) return;
|
|
653
709
|
this._controlsTimer = setTimeout(() => {
|
|
654
710
|
if (this._state === "playing") {
|
|
655
711
|
this.setAttribute("data-controls-hidden", "");
|
|
712
|
+
this._toolbarTop.setAttribute("data-visible", "false");
|
|
656
713
|
}
|
|
657
|
-
},
|
|
714
|
+
}, durationMs);
|
|
658
715
|
}
|
|
659
716
|
|
|
660
717
|
// Strategy is visible in Stats for Nerds, no badge in controls bar.
|