avbridge 2.7.0 → 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,112 @@ 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
+
26
+ ## [2.8.1]
27
+
28
+ Cross-browser playback validation — second slice of the v2.7.0 Playwright
29
+ tier. Bootstrap → play → destroy lifecycle tested across Chromium,
30
+ Firefox, and WebKit. Surfaced and fixed three real bugs along the way:
31
+
32
+ ### Fixed
33
+
34
+ - **`<avbridge-player>` constructor violated the Custom Elements spec**
35
+ by setting attributes (`tabindex`, `data-toolbar-empty`) on `this`.
36
+ Browsers allow this when the parser constructs the element, but
37
+ `document.createElement("avbridge-player")` fails with "The result
38
+ must not have attributes" — so programmatic creation, including all
39
+ Playwright tests, was broken. Moved attribute writes to
40
+ `connectedCallback`. Caught by the new browser matrix.
41
+ - **classify() didn't feature-detect MSE for remuxable non-native
42
+ containers.** On open-source Chromium (no proprietary codecs),
43
+ MKV+HEVC was classified as `remux` even though MSE rejected the
44
+ target mime — playback stalled silently. classify() now calls
45
+ `MediaSource.isTypeSupported()` for the remux target and gracefully
46
+ degrades to hybrid/fallback when it says no.
47
+ - **Fallback strategy was loading the wrong libav variant for some
48
+ codecs.** `pickLibavVariant` would choose the "webcodecs" companion
49
+ variant (smaller, assumes the browser decodes natively) for codecs
50
+ like HEVC — but fallback does *full* software decode, and that
51
+ variant lacks HEVC's software decoder. Fallback now unconditionally
52
+ loads the "avbridge" variant, which is correct since we're there
53
+ specifically because the browser can't decode.
54
+
55
+ ### Added
56
+
57
+ - **`tests/browser/playback.spec.ts`** — bootstrap → play → destroy per
58
+ fixture per browser. Asserts playback actually advances (either via
59
+ audio-clock `currentTime` for native/remux, or `framesPainted` for
60
+ canvas strategies) and that the runtime strategy after escalation
61
+ matches the per-browser matrix. 29 passed, 1 skipped (documented).
62
+ - **`playbackStrategy` field on `_expectations.ts`** for codifying
63
+ per-browser runtime expectations distinct from initial classify
64
+ output. Firefox HEVC is the one skip, pending a decode-stall
65
+ detection follow-up.
66
+
67
+ ### Known deferred
68
+
69
+ - **Firefox HEVC**: MSE optimistically reports `hev1.*` supported but
70
+ the decoder can't decode it. Audio plays, video is black. No signal
71
+ currently reaches escalation. Runtime decode-stall detection
72
+ (buffered but `currentTime` not advancing) is the right fix; tracked
73
+ as a follow-up, skipped in the matrix for now.
74
+
75
+ ## [2.8.0]
76
+
77
+ Element feature release — fit mode, consumer-slotted toolbar chrome, and
78
+ orientation-aware fullscreen.
79
+
80
+ ### Added
81
+
82
+ - **`fit` attribute on `<avbridge-video>`.** `fit="contain|cover|fill"`
83
+ (also reflected as the `fit` property, with a `fitchange` event) maps to
84
+ `object-fit` on the inner `<video>` and the fallback canvas via a new
85
+ `--avbridge-fit` CSS custom property on the stage wrapper. Default
86
+ `contain`; invalid values fall back to `contain`. Proxied through
87
+ `<avbridge-player>`.
88
+ - **Top toolbar slots on `<avbridge-player>`.** `<slot name="top-left">`
89
+ and `<slot name="top-right">` inside a new `part="toolbar-top"`
90
+ wrapper let consumers place back / title / translate buttons inside the
91
+ auto-hide chrome so they fade together with the bottom controls. A
92
+ `slotchange` listener toggles a `data-toolbar-empty` host attribute so
93
+ the gradient band disappears when no content is slotted. Click,
94
+ double-click, and tap handlers ignore events originating from slotted
95
+ content via `composedPath()` — so consumer buttons don't trigger
96
+ play/pause or seek.
97
+ - **Orientation-aware fullscreen on `<avbridge-video>`.** On fullscreen
98
+ entry (including fullscreen applied to an ancestor like
99
+ `<avbridge-player>` whose shadow DOM hosts the video), the element
100
+ derives the target orientation from the video's intrinsic
101
+ `videoWidth`/`videoHeight` (already SAR-corrected by the browser) and
102
+ calls `screen.orientation.lock('landscape'|'portrait')`. Releases the
103
+ lock on exit. iOS Safari rejections are swallowed (iOS handles rotation
104
+ via `webkitEnterFullscreen` on `<video>` itself). Opt out per element
105
+ with the `no-orientation-lock` attribute / `noOrientationLock` property.
106
+
107
+ ### Notes
108
+
109
+ - `<avbridge-player>` is no longer a "reserved" name — it's the
110
+ shipping chrome-bearing player. `<avbridge-video>` remains the bare
111
+ HTMLMediaElement-compatible primitive.
112
+
7
113
  ## [2.7.0]
8
114
 
9
115
  Cross-browser confidence release. A Playwright-based test tier now
package/README.md CHANGED
@@ -329,6 +329,35 @@ automatically via `import.meta.url` in the generated chunk.
329
329
  <avbridge-video src="/video.mkv" autoplay playsinline></avbridge-video>
330
330
  ```
331
331
 
332
+ **Two elements ship.** `<avbridge-video>` is the bare
333
+ `HTMLMediaElement`-compatible primitive with zero UI; `<avbridge-player>`
334
+ (from `avbridge/player-element`) wraps it with YouTube-style chrome.
335
+ Both support:
336
+
337
+ - `fit="contain|cover|fill"` — how the video fills the element's box
338
+ (maps to `object-fit`; default `contain`). Fires a `fitchange` event.
339
+ - `no-orientation-lock` — opt out of the default behavior that locks
340
+ `screen.orientation` to the video's intrinsic aspect on fullscreen
341
+ entry (landscape video → landscape, portrait video → portrait). Safe
342
+ on iOS / desktop — the lock call is swallowed where unsupported.
343
+
344
+ `<avbridge-player>` also exposes `top-left` and `top-right` slots
345
+ inside its auto-hiding top chrome for consumer buttons (back, title,
346
+ translate, etc.), and an opt-in `show-fit` attribute that adds a
347
+ Contain / Cover / Fill entry to the settings menu:
348
+
349
+ ```html
350
+ <avbridge-player src="/video.mkv" fit="cover" show-fit>
351
+ <button slot="top-left">← Back</button>
352
+ <button slot="top-right">Translate</button>
353
+ </avbridge-player>
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
+
332
361
  This is a second tsup entry (`dist/element-browser.js`) that inlines
333
362
  mediabunny + libavjs-webcodecs-bridge into a single ~1.3 MB file with
334
363
  zero bare specifiers at runtime. Perfect for self-hosted tools or static
@@ -378,6 +407,12 @@ LGPL compliance — see [`NOTICE.md`](./NOTICE.md) and
378
407
 
379
408
  ## Demos
380
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
+
381
416
  ```bash
382
417
  npm install
383
418
  npm run demo
@@ -217,6 +217,22 @@ function classifyContext(ctx) {
217
217
  };
218
218
  }
219
219
  if (REMUXABLE_CONTAINERS.has(ctx.container)) {
220
+ const mime = mp4MimeFor(video, audio);
221
+ if (mime && typeof MediaSource !== "undefined" && !mseSupports(mime)) {
222
+ if (webCodecsAvailable()) {
223
+ return {
224
+ class: "HYBRID_CANDIDATE",
225
+ strategy: "hybrid",
226
+ reason: `${ctx.container} container with ${video.codec}${audio ? "/" + audio.codec : ""}; MSE rejects the remux target mime \u2014 routing to WebCodecs hardware decode`,
227
+ fallbackChain: ["fallback"]
228
+ };
229
+ }
230
+ return {
231
+ class: "FALLBACK_REQUIRED",
232
+ strategy: "fallback",
233
+ 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`
234
+ };
235
+ }
220
236
  return {
221
237
  class: "REMUX_CANDIDATE",
222
238
  strategy: "remux",
@@ -1008,7 +1024,7 @@ var VideoRenderer = class {
1008
1024
  this.resolveFirstFrame = resolve;
1009
1025
  });
1010
1026
  this.canvas = document.createElement("canvas");
1011
- this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;object-fit:contain;";
1027
+ this.canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;background:black;object-fit:var(--avbridge-fit, contain);";
1012
1028
  const parent = target.parentElement ?? target.parentNode;
1013
1029
  if (parent && parent instanceof HTMLElement) {
1014
1030
  if (getComputedStyle(parent).position === "static") {
@@ -2182,7 +2198,7 @@ async function createHybridSession(ctx, target, transport) {
2182
2198
 
2183
2199
  // src/strategies/fallback/decoder.ts
2184
2200
  async function startDecoder(opts) {
2185
- const variant = chunkF3LQJKXK_cjs.pickLibavVariant(opts.context);
2201
+ const variant = "avbridge";
2186
2202
  const libav = await chunkG4APZMCP_cjs.loadLibav(variant);
2187
2203
  const bridge = await loadBridge2();
2188
2204
  const { prepareLibavInput } = await import('./libav-http-reader-AZLE7YFS.cjs');
@@ -2238,9 +2254,8 @@ async function startDecoder(opts) {
2238
2254
  videoStream ? `video: ${opts.context.videoTracks[0]?.codec ?? "unknown"}` : null,
2239
2255
  audioStream ? `audio: ${opts.context.audioTracks[0]?.codec ?? "unknown"}` : null
2240
2256
  ].filter(Boolean).join(", ");
2241
- const hint = variant === "webcodecs" ? ` The "${variant}" libav variant does not include software decoders for these codecs. Try the custom "avbridge" variant (scripts/build-libav.sh) for broader codec support, or use a lighter strategy (native, remux, hybrid) instead.` : "";
2242
2257
  throw new Error(
2243
- `fallback decoder: could not initialize any libav decoders (${codecs}).${hint}`
2258
+ `fallback decoder: could not initialize any libav decoders (${codecs}). The "${variant}" libav variant lacks software decoders for these codecs \u2014 rebuild with scripts/build-libav.sh including the missing decoder, or use a lighter strategy (native, remux, hybrid) instead.`
2244
2259
  );
2245
2260
  }
2246
2261
  let bsfCtx = null;
@@ -3366,5 +3381,5 @@ exports.NATIVE_VIDEO_CODECS = NATIVE_VIDEO_CODECS;
3366
3381
  exports.UnifiedPlayer = UnifiedPlayer;
3367
3382
  exports.classifyContext = classifyContext;
3368
3383
  exports.createPlayer = createPlayer;
3369
- //# sourceMappingURL=chunk-6SOFJV44.cjs.map
3370
- //# sourceMappingURL=chunk-6SOFJV44.cjs.map
3384
+ //# sourceMappingURL=chunk-IUSFLVLJ.cjs.map
3385
+ //# sourceMappingURL=chunk-IUSFLVLJ.cjs.map