avbridge 2.5.0 → 2.7.0

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,91 @@ 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.7.0]
8
+
9
+ Cross-browser confidence release. A Playwright-based test tier now
10
+ validates that avbridge picks the right strategy on each major browser
11
+ engine. No runtime code changes — the release is tooling, fixtures, and
12
+ a new testing discipline.
13
+
14
+ ### Added
15
+
16
+ - **Cross-browser test tier (Playwright).** New `tests/browser/` directory
17
+ with the matrix run via `npm run test:browser` across Chromium, Firefox,
18
+ and WebKit. Initial slice — `fixtures.spec.ts` — validates that `probe()`
19
+ and `classify()` produce the expected output for each fixture on each
20
+ browser (15 tests, ~11s). Per-browser expectations live in
21
+ `tests/browser/_expectations.ts` so evolving browser codec support is
22
+ a one-file change. Playwright's `webServer` config auto-starts Vite;
23
+ the five existing Puppeteer scripts are unchanged and continue to
24
+ cover Chromium-only scenarios. `npm run test:browser:ui` for the
25
+ interactive trace viewer.
26
+ - New test harness page at `demo/tests-harness.html` that exposes the
27
+ avbridge API on `window` for `page.evaluate()`-style tests. Only served
28
+ in dev mode; not shipped in production builds.
29
+ - New fixture: `bbb-hevc-aac.mkv` (generated via `npm run fixtures`) for
30
+ exercising the HEVC strategy boundary across browsers.
31
+ - `docs/dev/TESTING.md` — documents the new Tier 4 alongside the
32
+ existing three tiers, with scope guidance (what belongs in `fixtures`,
33
+ `playback`, and `contract` spec files).
34
+
35
+ ### Findings worth surfacing
36
+
37
+ - `classify()` is deliberately **browser-independent** for most codec
38
+ paths. Per-browser divergence surfaces at runtime via escalation, not
39
+ at classification. The test-tier split reflects this architecture:
40
+ `fixtures.spec.ts` for the deterministic decision, `playback.spec.ts`
41
+ (planned v2.7.1) for runtime escalation behavior.
42
+
43
+ ### Coming in follow-ups
44
+
45
+ - **v2.7.1** — `playback.spec.ts`. Bootstrap → play → destroy per
46
+ fixture per browser. Catches runtime escalation (e.g. Firefox
47
+ escalating HEVC MKV from remux → fallback when MSE rejects hevc1.*).
48
+ - **v2.7.2** — `contract.spec.ts`. HTMLMediaElement event + property
49
+ parity across strategies and browsers.
50
+
51
+ ## [2.6.0]
52
+
53
+ `<avbridge-player>` polish release. Four targeted ergonomics upgrades.
54
+
55
+ ### Added
56
+
57
+ - **Typed `addEventListener` / `removeEventListener` overloads** on both
58
+ `<avbridge-video>` and `<avbridge-player>`. Consumers using avbridge
59
+ custom events (`ready`, `strategychange`, `trackschange`, `timeupdate`,
60
+ `error`, etc.) now receive a typed `CustomEvent<Detail>` without the
61
+ `as unknown as CustomEvent` cast tax. Standard HTMLMediaElement events
62
+ (`play`, `pause`, `seeking`, etc.) retain their native typing via
63
+ `HTMLElementEventMap`. New type: `AvbridgeVideoElementEventMap`.
64
+ - **Drag-and-drop file input** on `<avbridge-player>`. Drop a video file
65
+ onto the player and it loads + plays, matching the demo's file-picker
66
+ flow. Visual dashed-border feedback during dragover (stylable via
67
+ `.avp-dragover`).
68
+ - **`<track>` children parsing.** Light-DOM `<track src="subs.vtt"
69
+ srclang="en">` children declared inside `<avbridge-player>` or
70
+ `<avbridge-video>` were already cloned into the shadow `<video>` for
71
+ native/remux strategies; they now also populate the subtitle list that
72
+ the player's settings menu renders. HTML-declared tracks get stable
73
+ IDs in the 10000+ range to avoid colliding with container-embedded
74
+ IDs. MutationObserver-driven — add or remove a `<track>` at any time
75
+ and the menu updates.
76
+ - **HTMLMediaElement parity — `readyState` and `seekable`** on canvas
77
+ strategies. Previously the inner `<video>` (with no `src`) returned
78
+ `readyState: 0` and empty `seekable` ranges for hybrid/fallback.
79
+ Now synthesized: `readyState` reflects frame+audio readiness,
80
+ `seekable` spans `[0, duration]` once probe completes. `buffered`
81
+ and `networkState` remain deferred — both need meaningful transport
82
+ state machinery.
83
+
84
+ ### Deprecated / Deferred
85
+
86
+ - `buffered` on canvas strategies still returns empty TimeRanges. Requires
87
+ decoder-position → media-time plumbing; tracked for a follow-up.
88
+ - `networkState` not yet exposed on the element. Needs a transport
89
+ state machine spanning probe → libav reader → decoder; out of scope
90
+ for this release.
91
+
7
92
  ## [2.5.0]
8
93
 
9
94
  The "legacy transcode breadth" release. avbridge.js can now transcode
@@ -1990,6 +1990,35 @@ async function loadBridge() {
1990
1990
  }
1991
1991
  }
1992
1992
 
1993
+ // src/util/time-ranges.ts
1994
+ function makeTimeRanges(ranges) {
1995
+ const frozen = ranges.slice();
1996
+ const impl = {
1997
+ get length() {
1998
+ return frozen.length;
1999
+ },
2000
+ start(index) {
2001
+ if (index < 0 || index >= frozen.length) {
2002
+ throw new DOMException(
2003
+ `TimeRanges.start: index ${index} out of range (length=${frozen.length})`,
2004
+ "IndexSizeError"
2005
+ );
2006
+ }
2007
+ return frozen[index][0];
2008
+ },
2009
+ end(index) {
2010
+ if (index < 0 || index >= frozen.length) {
2011
+ throw new DOMException(
2012
+ `TimeRanges.end: index ${index} out of range (length=${frozen.length})`,
2013
+ "IndexSizeError"
2014
+ );
2015
+ }
2016
+ return frozen[index][1];
2017
+ }
2018
+ };
2019
+ return impl;
2020
+ }
2021
+
1993
2022
  // src/strategies/hybrid/index.ts
1994
2023
  var READY_AUDIO_BUFFER_SECONDS = 0.3;
1995
2024
  var READY_TIMEOUT_SECONDS = 10;
@@ -2047,6 +2076,18 @@ async function createHybridSession(ctx, target, transport) {
2047
2076
  get: () => ctx.duration ?? NaN
2048
2077
  });
2049
2078
  }
2079
+ Object.defineProperty(target, "readyState", {
2080
+ configurable: true,
2081
+ get: () => {
2082
+ if (!renderer.hasFrames()) return 0;
2083
+ if (!audio.isPlaying() && audio.bufferAhead() <= 0 && !audio.isNoAudio()) return 1;
2084
+ return 2;
2085
+ }
2086
+ });
2087
+ Object.defineProperty(target, "seekable", {
2088
+ configurable: true,
2089
+ get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
2090
+ });
2050
2091
  async function waitForBuffer() {
2051
2092
  const start = performance.now();
2052
2093
  while (true) {
@@ -2128,6 +2169,8 @@ async function createHybridSession(ctx, target, transport) {
2128
2169
  delete target.paused;
2129
2170
  delete target.volume;
2130
2171
  delete target.muted;
2172
+ delete target.readyState;
2173
+ delete target.seekable;
2131
2174
  } catch {
2132
2175
  }
2133
2176
  },
@@ -2648,6 +2691,18 @@ async function createFallbackSession(ctx, target, transport) {
2648
2691
  get: () => ctx.duration ?? NaN
2649
2692
  });
2650
2693
  }
2694
+ Object.defineProperty(target, "readyState", {
2695
+ configurable: true,
2696
+ get: () => {
2697
+ if (!renderer.hasFrames()) return 0;
2698
+ if (!audio.isPlaying() && audio.bufferAhead() <= 0 && !audio.isNoAudio()) return 1;
2699
+ return 2;
2700
+ }
2701
+ });
2702
+ Object.defineProperty(target, "seekable", {
2703
+ configurable: true,
2704
+ get: () => makeTimeRanges(ctx.duration && Number.isFinite(ctx.duration) && ctx.duration > 0 ? [[0, ctx.duration]] : [])
2705
+ });
2651
2706
  async function waitForBuffer() {
2652
2707
  const start = performance.now();
2653
2708
  let firstFrameAtMs = 0;
@@ -2750,6 +2805,8 @@ async function createFallbackSession(ctx, target, transport) {
2750
2805
  delete target.paused;
2751
2806
  delete target.volume;
2752
2807
  delete target.muted;
2808
+ delete target.readyState;
2809
+ delete target.seekable;
2753
2810
  } catch {
2754
2811
  }
2755
2812
  },
@@ -3309,5 +3366,5 @@ exports.NATIVE_VIDEO_CODECS = NATIVE_VIDEO_CODECS;
3309
3366
  exports.UnifiedPlayer = UnifiedPlayer;
3310
3367
  exports.classifyContext = classifyContext;
3311
3368
  exports.createPlayer = createPlayer;
3312
- //# sourceMappingURL=chunk-TBW26OPP.cjs.map
3313
- //# sourceMappingURL=chunk-TBW26OPP.cjs.map
3369
+ //# sourceMappingURL=chunk-6SOFJV44.cjs.map
3370
+ //# sourceMappingURL=chunk-6SOFJV44.cjs.map