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/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ 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.2]
8
+
9
+ Small ergonomics release driven by downstream `<avbridge-player>`
10
+ consumer feedback.
11
+
12
+ ### Added
13
+
14
+ - **Opt-in "Fit" entry in the `<avbridge-player>` settings menu.** Set
15
+ the new `show-fit` attribute on `<avbridge-player>` and the settings
16
+ menu gains a Fit section with Contain / Cover / Fill choices; picking
17
+ one writes the `fit` attribute (which proxies through to the inner
18
+ `<avbridge-video>`). Off by default — chromeless consumers don't get
19
+ a surprise entry.
20
+ - **`data-visible="true|false"` on `part="toolbar-top"`.** Mirrors the
21
+ controls auto-hide state so slotted toolbar buttons can drive JS
22
+ behavior (focus management, screen-reader announcements, disabling
23
+ clicks) without listening to the host's `data-controls-hidden`. The
24
+ existing CSS fade on opacity is unchanged.
25
+
7
26
  ## [2.8.1]
8
27
 
9
28
  Cross-browser playback validation — second slice of the v2.7.0 Playwright
package/README.md CHANGED
@@ -343,15 +343,21 @@ Both support:
343
343
 
344
344
  `<avbridge-player>` also exposes `top-left` and `top-right` slots
345
345
  inside its auto-hiding top chrome for consumer buttons (back, title,
346
- translate, etc.):
346
+ translate, etc.), and an opt-in `show-fit` attribute that adds a
347
+ Contain / Cover / Fill entry to the settings menu:
347
348
 
348
349
  ```html
349
- <avbridge-player src="/video.mkv" fit="cover">
350
+ <avbridge-player src="/video.mkv" fit="cover" show-fit>
350
351
  <button slot="top-left">← Back</button>
351
352
  <button slot="top-right">Translate</button>
352
353
  </avbridge-player>
353
354
  ```
354
355
 
356
+ The toolbar-top `part` exposes a `data-visible="true|false"`
357
+ attribute mirroring the controls auto-hide state — useful if slotted
358
+ buttons need to drive JS behavior (focus, announcements) in sync with
359
+ the fade, not just CSS opacity.
360
+
355
361
  This is a second tsup entry (`dist/element-browser.js`) that inlines
356
362
  mediabunny + libavjs-webcodecs-bridge into a single ~1.3 MB file with
357
363
  zero bare specifiers at runtime. Perfect for self-hosted tools or static
@@ -401,6 +407,12 @@ LGPL compliance — see [`NOTICE.md`](./NOTICE.md) and
401
407
 
402
408
  ## Demos
403
409
 
410
+ Try it live: **https://keishi.github.io/avbridge/** — player + converter
411
+ running against the latest release, served from GitHub Pages with the
412
+ COOP/COEP headers needed for SharedArrayBuffer.
413
+
414
+ Or run locally:
415
+
404
416
  ```bash
405
417
  npm install
406
418
  npm run demo
package/dist/player.cjs CHANGED
@@ -5144,8 +5144,10 @@ var PROXY_ATTRIBUTES = [
5144
5144
  "preferstrategy",
5145
5145
  "fit"
5146
5146
  ];
5147
+ var PLAYER_ATTRIBUTES = ["show-fit"];
5148
+ var FIT_MODES = ["contain", "cover", "fill"];
5147
5149
  var AvbridgePlayerElement = class extends HTMLElement {
5148
- static observedAttributes = [...PROXY_ATTRIBUTES];
5150
+ static observedAttributes = [...PROXY_ATTRIBUTES, ...PLAYER_ATTRIBUTES];
5149
5151
  // ── Internal DOM refs ──────────────────────────────────────────────────
5150
5152
  _video;
5151
5153
  _playBtn;
@@ -5182,6 +5184,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
5182
5184
  _eventCleanup = [];
5183
5185
  _updateToolbarEmpty = () => {
5184
5186
  };
5187
+ _toolbarTop;
5185
5188
  // ── Constructor ────────────────────────────────────────────────────────
5186
5189
  constructor() {
5187
5190
  super();
@@ -5205,6 +5208,8 @@ var AvbridgePlayerElement = class extends HTMLElement {
5205
5208
  this._statsEl = shadow.querySelector(".avp-stats");
5206
5209
  this._rippleLeft = shadow.querySelector(".avp-ripple-left");
5207
5210
  this._rippleRight = shadow.querySelector(".avp-ripple-right");
5211
+ this._toolbarTop = shadow.querySelector('[part="toolbar-top"]');
5212
+ this._toolbarTop.setAttribute("data-visible", "true");
5208
5213
  const slots = shadow.querySelectorAll('slot[name="top-left"], slot[name="top-right"]');
5209
5214
  this._updateToolbarEmpty = () => {
5210
5215
  const hasContent = Array.from(slots).some((s) => s.assignedNodes({ flatten: true }).length > 0);
@@ -5388,6 +5393,12 @@ var AvbridgePlayerElement = class extends HTMLElement {
5388
5393
  }
5389
5394
  attributeChangedCallback(name, _old, value) {
5390
5395
  if (!this._video) return;
5396
+ if (PLAYER_ATTRIBUTES.includes(name)) {
5397
+ if (name === "show-fit" && this._settingsOpen) {
5398
+ this._buildSettingsMenu();
5399
+ }
5400
+ return;
5401
+ }
5391
5402
  if (value == null) this._video.removeAttribute(name);
5392
5403
  else this._video.setAttribute(name, value);
5393
5404
  }
@@ -5510,6 +5521,16 @@ var AvbridgePlayerElement = class extends HTMLElement {
5510
5521
  }
5511
5522
  _buildSettingsMenu() {
5512
5523
  const sections = [];
5524
+ if (this.hasAttribute("show-fit")) {
5525
+ const currentFit = this._video.fit ?? "contain";
5526
+ let fitItems = "";
5527
+ for (const mode of FIT_MODES) {
5528
+ const active = mode === currentFit;
5529
+ const label = mode[0].toUpperCase() + mode.slice(1);
5530
+ fitItems += `<div class="avp-settings-item${active ? " active" : ""}" data-fit="${mode}">${label}</div>`;
5531
+ }
5532
+ sections.push(`<div class="avp-settings-section"><div class="avp-settings-label">Fit</div>${fitItems}</div>`);
5533
+ }
5513
5534
  const currentRate = this._video.playbackRate ?? 1;
5514
5535
  let speedItems = "";
5515
5536
  for (const spd of PLAYBACK_SPEEDS) {
@@ -5536,6 +5557,14 @@ var AvbridgePlayerElement = class extends HTMLElement {
5536
5557
  }
5537
5558
  sections.push(`<div class="avp-settings-section"><div class="avp-settings-item" data-stats>Stats for nerds</div></div>`);
5538
5559
  this._settingsMenu.innerHTML = sections.join("");
5560
+ for (const item of this._settingsMenu.querySelectorAll("[data-fit]")) {
5561
+ item.addEventListener("click", (e) => {
5562
+ e.stopPropagation();
5563
+ const mode = item.dataset.fit;
5564
+ this.setAttribute("fit", mode);
5565
+ this._buildSettingsMenu();
5566
+ });
5567
+ }
5539
5568
  for (const item of this._settingsMenu.querySelectorAll("[data-speed]")) {
5540
5569
  item.addEventListener("click", (e) => {
5541
5570
  e.stopPropagation();
@@ -5621,6 +5650,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
5621
5650
  // ── Controls: auto-hide ────────────────────────────────────────────────
5622
5651
  _showControls() {
5623
5652
  this.removeAttribute("data-controls-hidden");
5653
+ this._toolbarTop.setAttribute("data-visible", "true");
5624
5654
  this._scheduleHide();
5625
5655
  }
5626
5656
  _scheduleHide() {
@@ -5630,6 +5660,7 @@ var AvbridgePlayerElement = class extends HTMLElement {
5630
5660
  this._controlsTimer = setTimeout(() => {
5631
5661
  if (this._state === "playing") {
5632
5662
  this.setAttribute("data-controls-hidden", "");
5663
+ this._toolbarTop.setAttribute("data-visible", "false");
5633
5664
  }
5634
5665
  }, CONTROLS_HIDE_MS);
5635
5666
  }