avbridge 2.8.1 → 2.8.2

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.2",
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();
@@ -643,6 +682,7 @@ export class AvbridgePlayerElement extends HTMLElement {
643
682
 
644
683
  private _showControls(): void {
645
684
  this.removeAttribute("data-controls-hidden");
685
+ this._toolbarTop.setAttribute("data-visible", "true");
646
686
  this._scheduleHide();
647
687
  }
648
688
 
@@ -653,6 +693,7 @@ export class AvbridgePlayerElement extends HTMLElement {
653
693
  this._controlsTimer = setTimeout(() => {
654
694
  if (this._state === "playing") {
655
695
  this.setAttribute("data-controls-hidden", "");
696
+ this._toolbarTop.setAttribute("data-visible", "false");
656
697
  }
657
698
  }, CONTROLS_HIDE_MS);
658
699
  }