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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avbridge",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
4
4
  "description": "Play and convert arbitrary video files in the browser. Native, remux, hybrid, fallback, and transcode — one API.",
5
5
  "license": "MIT",
6
6
  "author": "Keishi Hattori",
@@ -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
- private _showControls(): void {
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._scheduleHide();
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
- }, CONTROLS_HIDE_MS);
714
+ }, durationMs);
658
715
  }
659
716
 
660
717
  // Strategy is visible in Stats for Nerds, no badge in controls bar.