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 +106 -0
- package/README.md +35 -0
- package/dist/{chunk-6SOFJV44.cjs → chunk-IUSFLVLJ.cjs} +21 -6
- package/dist/chunk-IUSFLVLJ.cjs.map +1 -0
- package/dist/{chunk-OGYHFY6K.js → chunk-JSQOBUQB.js} +21 -6
- package/dist/chunk-JSQOBUQB.js.map +1 -0
- package/dist/element-browser.js +146 -7
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +129 -5
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +52 -3
- package/dist/element.d.ts +52 -3
- package/dist/element.js +128 -4
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +8 -8
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{player-DGXeCNfD.d.cts → player-DXEKOky8.d.cts} +3 -0
- package/dist/{player-DGXeCNfD.d.ts → player-DXEKOky8.d.ts} +3 -0
- package/dist/player.cjs +254 -12
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +62 -3
- package/dist/player.d.ts +62 -3
- package/dist/player.js +254 -12
- package/dist/player.js.map +1 -1
- package/package.json +1 -1
- package/src/classify/rules.ts +23 -0
- package/src/element/avbridge-player.ts +83 -7
- package/src/element/avbridge-video.ts +148 -4
- package/src/element/player-styles.ts +44 -0
- package/src/element.ts +3 -3
- package/src/strategies/fallback/decoder.ts +14 -7
- package/src/strategies/fallback/video-renderer.ts +7 -5
- package/src/types.ts +1 -0
- package/dist/chunk-6SOFJV44.cjs.map +0 -1
- package/dist/chunk-OGYHFY6K.js.map +0 -1
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 =
|
|
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})
|
|
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-
|
|
3370
|
-
//# sourceMappingURL=chunk-
|
|
3384
|
+
//# sourceMappingURL=chunk-IUSFLVLJ.cjs.map
|
|
3385
|
+
//# sourceMappingURL=chunk-IUSFLVLJ.cjs.map
|