avbridge 1.0.0 → 1.1.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,7 +4,45 @@ All notable changes to **avbridge** 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
- ## [Unreleased]
7
+ ## [1.1.0]
8
+
9
+ ### Added
10
+
11
+ - **`<avbridge-player>` is now a true `<video>` drop-in.** The element gained
12
+ the missing slice of the `HTMLMediaElement` surface so existing code that
13
+ reaches for a `<video>` can swap to `<avbridge-player>` with no behavioural
14
+ changes:
15
+ - **Properties**: `poster`, `volume`, `playbackRate`, `videoWidth`,
16
+ `videoHeight`, `played`, `seekable`, `crossOrigin`, `disableRemotePlayback`.
17
+ - **Method**: `canPlayType(mimeType)` — passes through to the underlying
18
+ `<video>`. Note that this answers about the *browser's* native support,
19
+ not avbridge's full capabilities.
20
+ - **Attributes** (reflected to the inner `<video>`): `poster`, `playsinline`,
21
+ `crossorigin`, `disableremoteplayback`.
22
+ - **Event forwarding**: 17 standard `HTMLMediaElement` events are forwarded
23
+ from the inner `<video>` to the wrapper element — `loadstart`,
24
+ `loadedmetadata`, `loadeddata`, `canplay`, `canplaythrough`, `play`,
25
+ `playing`, `pause`, `seeking`, `seeked`, `volumechange`, `ratechange`,
26
+ `durationchange`, `waiting`, `stalled`, `emptied`, `resize`. Consumers can
27
+ `el.addEventListener("loadedmetadata", …)` exactly like a real `<video>`.
28
+ - **`<track>` children**: light-DOM `<track>` elements declared as children
29
+ of `<avbridge-player>` are now mirrored into the shadow `<video>` and kept
30
+ in sync via a `MutationObserver`. This works for static HTML markup as
31
+ well as dynamic insertion / removal.
32
+ - **`videoElement` getter**: escape hatch returning the underlying shadow
33
+ `<video>` for native APIs the wrapper doesn't expose
34
+ (`requestPictureInPicture`, browser-native `audioTracks`, `captureStream`,
35
+ library integrations needing a real `HTMLVideoElement`). Caveat: when the
36
+ active strategy is `"fallback"` or `"hybrid"`, frames render to a canvas
37
+ overlay rather than into this `<video>`, so APIs that depend on the actual
38
+ pixels won't show the playing content in those modes.
39
+
40
+ ### Changed
41
+
42
+ - The element's `observedAttributes` list grew to include `poster`,
43
+ `playsinline`, `crossorigin`, and `disableremoteplayback`.
44
+
45
+ ## [1.0.0]
8
46
 
9
47
  ### Added
10
48
 
package/dist/element.cjs CHANGED
@@ -13,6 +13,26 @@ var PREFERRED_STRATEGY_VALUES = /* @__PURE__ */ new Set([
13
13
  "hybrid",
14
14
  "fallback"
15
15
  ]);
16
+ var FORWARDED_VIDEO_EVENTS = [
17
+ "loadstart",
18
+ "loadedmetadata",
19
+ "loadeddata",
20
+ "canplay",
21
+ "canplaythrough",
22
+ "play",
23
+ "playing",
24
+ "pause",
25
+ "seeking",
26
+ "seeked",
27
+ "volumechange",
28
+ "ratechange",
29
+ "durationchange",
30
+ "waiting",
31
+ "stalled",
32
+ "emptied",
33
+ "resize",
34
+ "error"
35
+ ];
16
36
  var HTMLElementCtor = typeof HTMLElement !== "undefined" ? HTMLElement : class {
17
37
  };
18
38
  var AvbridgePlayerElement = class extends HTMLElementCtor {
@@ -22,6 +42,10 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
22
42
  "muted",
23
43
  "loop",
24
44
  "preload",
45
+ "poster",
46
+ "playsinline",
47
+ "crossorigin",
48
+ "disableremoteplayback",
25
49
  "diagnostics",
26
50
  "preferstrategy"
27
51
  ];
@@ -59,6 +83,8 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
59
83
  _pendingSeek = null;
60
84
  /** Set if play() was called before the player was ready. */
61
85
  _pendingPlay = false;
86
+ /** MutationObserver tracking light-DOM `<track>` children. */
87
+ _trackObserver = null;
62
88
  // ── Construction & lifecycle ───────────────────────────────────────────
63
89
  constructor() {
64
90
  super();
@@ -72,9 +98,20 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
72
98
  if (this._destroyed) return;
73
99
  this._dispatch("progress", { buffered: this._videoEl.buffered });
74
100
  });
101
+ for (const eventName of FORWARDED_VIDEO_EVENTS) {
102
+ this._videoEl.addEventListener(eventName, () => {
103
+ if (this._destroyed) return;
104
+ this.dispatchEvent(new Event(eventName, { bubbles: false }));
105
+ });
106
+ }
75
107
  }
76
108
  connectedCallback() {
77
109
  if (this._destroyed) return;
110
+ this._syncTextTracks();
111
+ if (!this._trackObserver) {
112
+ this._trackObserver = new MutationObserver(() => this._syncTextTracks());
113
+ this._trackObserver.observe(this, { childList: true, subtree: false });
114
+ }
78
115
  const source = this._activeSource();
79
116
  if (source != null) {
80
117
  void this._bootstrap(source);
@@ -82,6 +119,10 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
82
119
  }
83
120
  disconnectedCallback() {
84
121
  if (this._destroyed) return;
122
+ if (this._trackObserver) {
123
+ this._trackObserver.disconnect();
124
+ this._trackObserver = null;
125
+ }
85
126
  this._bootstrapId++;
86
127
  void this._teardown();
87
128
  }
@@ -95,12 +136,16 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
95
136
  case "autoplay":
96
137
  case "muted":
97
138
  case "loop":
139
+ case "playsinline":
140
+ case "disableremoteplayback":
98
141
  if (newValue == null) this._videoEl.removeAttribute(name);
99
142
  else this._videoEl.setAttribute(name, newValue);
100
143
  break;
101
144
  case "preload":
102
- if (newValue == null) this._videoEl.removeAttribute("preload");
103
- else this._videoEl.setAttribute("preload", newValue);
145
+ case "poster":
146
+ case "crossorigin":
147
+ if (newValue == null) this._videoEl.removeAttribute(name);
148
+ else this._videoEl.setAttribute(name, newValue);
104
149
  break;
105
150
  case "diagnostics":
106
151
  break;
@@ -120,6 +165,26 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
120
165
  if (this._src != null) return this._src;
121
166
  return null;
122
167
  }
168
+ /**
169
+ * Mirror light-DOM `<track>` children into the shadow `<video>` so that
170
+ * the browser's native text-track machinery picks them up. Called on
171
+ * connect, on every mutation of light-DOM children, and once after each
172
+ * source change so newly-set tracks survive a fresh `<video>`.
173
+ *
174
+ * Strategy: clone the children. We don't move them because the user's
175
+ * code may still hold references to the originals (e.g. to set `default`).
176
+ * The shadow copies are throwaway — we wipe them on every sync.
177
+ */
178
+ _syncTextTracks() {
179
+ const existing = this._videoEl.querySelectorAll("track");
180
+ for (const t of Array.from(existing)) t.remove();
181
+ for (const child of Array.from(this.children)) {
182
+ if (child.tagName === "TRACK") {
183
+ const clone = child.cloneNode(true);
184
+ this._videoEl.appendChild(clone);
185
+ }
186
+ }
187
+ }
123
188
  /** Internal src setter — separate from the property setter so the
124
189
  * attributeChangedCallback can use it without re-entering reflection. */
125
190
  _setSrcInternal(value) {
@@ -167,6 +232,7 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
167
232
  return;
168
233
  }
169
234
  this._player = player;
235
+ this._syncTextTracks();
170
236
  player.on("strategy", ({ strategy, reason }) => {
171
237
  const cls = player.getDiagnostics().strategyClass;
172
238
  this._strategy = strategy;
@@ -346,12 +412,87 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
346
412
  * `<video>.buffered` `TimeRanges` API. For the native and remux strategies
347
413
  * this reflects the underlying SourceBuffer / progressive download state.
348
414
  * For the hybrid and fallback (canvas-rendered) strategies it currently
349
- * returns an empty TimeRanges; v1.1 will synthesize a coarse range from
350
- * the decoder's read position.
415
+ * returns an empty TimeRanges; a future release will synthesize a coarse
416
+ * range from the decoder's read position.
351
417
  */
352
418
  get buffered() {
353
419
  return this._videoEl.buffered;
354
420
  }
421
+ // ── HTMLMediaElement parity ───────────────────────────────────────────
422
+ // Mirror the standard <video> surface so consumers can drop the element
423
+ // in as a <video> replacement. Each property is a thin passthrough to the
424
+ // shadow `<video>`.
425
+ get poster() {
426
+ return this._videoEl.poster;
427
+ }
428
+ set poster(value) {
429
+ if (value == null || value === "") this.removeAttribute("poster");
430
+ else this.setAttribute("poster", value);
431
+ }
432
+ get volume() {
433
+ return this._videoEl.volume;
434
+ }
435
+ set volume(value) {
436
+ this._videoEl.volume = value;
437
+ }
438
+ get playbackRate() {
439
+ return this._videoEl.playbackRate;
440
+ }
441
+ set playbackRate(value) {
442
+ this._videoEl.playbackRate = value;
443
+ }
444
+ get videoWidth() {
445
+ return this._videoEl.videoWidth;
446
+ }
447
+ get videoHeight() {
448
+ return this._videoEl.videoHeight;
449
+ }
450
+ get played() {
451
+ return this._videoEl.played;
452
+ }
453
+ get seekable() {
454
+ return this._videoEl.seekable;
455
+ }
456
+ get crossOrigin() {
457
+ return this._videoEl.crossOrigin;
458
+ }
459
+ set crossOrigin(value) {
460
+ if (value == null) this.removeAttribute("crossorigin");
461
+ else this.setAttribute("crossorigin", value);
462
+ }
463
+ get disableRemotePlayback() {
464
+ return this._videoEl.disableRemotePlayback;
465
+ }
466
+ set disableRemotePlayback(value) {
467
+ if (value) this.setAttribute("disableremoteplayback", "");
468
+ else this.removeAttribute("disableremoteplayback");
469
+ }
470
+ /**
471
+ * Native `HTMLMediaElement.canPlayType()` passthrough. Note that this
472
+ * answers about the *browser's* native support, not avbridge's full
473
+ * capabilities — avbridge can play many formats this method returns ""
474
+ * for, by routing them to the remux/hybrid/fallback strategies.
475
+ */
476
+ canPlayType(mimeType) {
477
+ return this._videoEl.canPlayType(mimeType);
478
+ }
479
+ /**
480
+ * **Escape hatch.** The underlying shadow-DOM `<video>` element.
481
+ *
482
+ * Use for native browser APIs the wrapper doesn't expose:
483
+ * - `el.videoElement.requestPictureInPicture()`
484
+ * - `el.videoElement.audioTracks` (browser native, not avbridge's track list)
485
+ * - direct integration with libraries that need a real HTMLVideoElement
486
+ *
487
+ * **Caveat:** When the active strategy is `"fallback"` or `"hybrid"`,
488
+ * frames are rendered to a canvas overlay, not into this `<video>`.
489
+ * APIs that depend on the actual pixels (Picture-in-Picture, captureStream)
490
+ * will not show the playing content in those modes. Check `el.strategy`
491
+ * before using such APIs.
492
+ */
493
+ get videoElement() {
494
+ return this._videoEl;
495
+ }
355
496
  get strategy() {
356
497
  return this._strategy;
357
498
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/element/avbridge-player.ts","../src/element.ts"],"names":["createPlayer"],"mappings":"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","file":"element.cjs","sourcesContent":["/**\n * `<avbridge-player>` — reference web component for the avbridge engine.\n *\n * This is a *thin* wrapper around `createPlayer()`. Its purpose is to:\n *\n * 1. Validate the public API by being a real consumer of it.\n * 2. Drive lifecycle correctness in the core via adversarial integration tests.\n * 3. Provide a drop-in player for users who don't want to wire `createPlayer()`\n * themselves.\n *\n * It is **not** a player UI framework. See `docs/dev/WEB_COMPONENT_SPEC.md`\n * for the full spec, lifecycle invariants, and edge case list.\n *\n * Phase A scope (this file): lifecycle scaffold only — `src` / `source` /\n * `currentTime` / `play` / `pause` / `load` / `destroy` / events. No built-in\n * controls. Shadow DOM contains a single `<video part=\"video\">`.\n */\n\nimport { createPlayer, type UnifiedPlayer } from \"../player.js\";\nimport type {\n MediaInput,\n StrategyName,\n StrategyClass,\n AudioTrackInfo,\n SubtitleTrackInfo,\n DiagnosticsSnapshot,\n} from \"../types.js\";\n\n/** Strategy preference passed via the `preferstrategy` attribute. */\ntype PreferredStrategy = \"auto\" | StrategyName;\n\nconst PREFERRED_STRATEGY_VALUES = new Set<PreferredStrategy>([\n \"auto\",\n \"native\",\n \"remux\",\n \"hybrid\",\n \"fallback\",\n]);\n\n/**\n * `HTMLElement` is a browser-only global. SSR frameworks (Next.js, Astro,\n * Remix, etc.) commonly import library modules on the server to extract\n * types or do tree-shaking, even if the user only ends up using them in\n * the browser. If we extended `HTMLElement` directly, the `class extends`\n * expression would be evaluated at module load time and crash in Node.\n *\n * The fix: in non-browser environments, fall back to an empty stub class.\n * The element is never *constructed* server-side (the registration in\n * `element.ts` is guarded by `typeof customElements !== \"undefined\"`), so\n * the stub is never instantiated — it just lets the class declaration\n * evaluate cleanly so the module can be imported anywhere.\n */\nconst HTMLElementCtor: typeof HTMLElement =\n typeof HTMLElement !== \"undefined\"\n ? HTMLElement\n : (class {} as unknown as typeof HTMLElement);\n\n/**\n * Custom element. Lifecycle correctness is enforced via a monotonically\n * increasing `_bootstrapId`: every async bootstrap captures the ID at start\n * and discards itself if the ID has changed by the time it resolves. This\n * single pattern handles disconnect-during-bootstrap, rapid src reassignment,\n * bootstrap races, and destroy-during-bootstrap.\n */\nexport class AvbridgePlayerElement extends HTMLElementCtor {\n static readonly observedAttributes = [\n \"src\",\n \"autoplay\",\n \"muted\",\n \"loop\",\n \"preload\",\n \"diagnostics\",\n \"preferstrategy\",\n ];\n\n // ── Internal state ─────────────────────────────────────────────────────\n\n /** The shadow DOM `<video>` element that strategies render into. */\n private _videoEl!: HTMLVideoElement;\n\n /** Active player session, if any. Cleared on teardown. */\n private _player: UnifiedPlayer | null = null;\n\n /**\n * Monotonic counter incremented on every (re)bootstrap. Async bootstrap\n * work captures the current ID; if it doesn't match by the time the work\n * resolves, the work is discarded.\n */\n private _bootstrapId = 0;\n\n /** True after destroy() — element is permanently unusable. */\n private _destroyed = false;\n\n /** Internal source state. Either string-form (src) OR rich (source). */\n private _src: string | null = null;\n private _source: MediaInput | null = null;\n\n /**\n * Set when the `source` property setter is in the middle of clearing the\n * `src` attribute as part of mutual exclusion. The attributeChangedCallback\n * checks this flag and skips its normal \"clear source\" side effect, which\n * would otherwise wipe the value we just set.\n */\n private _suppressSrcAttrCallback = false;\n\n /** Last-known runtime state surfaced via getters. */\n private _strategy: StrategyName | null = null;\n private _strategyClass: StrategyClass | null = null;\n private _audioTracks: AudioTrackInfo[] = [];\n private _subtitleTracks: SubtitleTrackInfo[] = [];\n\n /** Strategy preference (does not currently affect routing — reserved). */\n private _preferredStrategy: PreferredStrategy = \"auto\";\n\n /** Set if currentTime was assigned before the player was ready. */\n private _pendingSeek: number | null = null;\n /** Set if play() was called before the player was ready. */\n private _pendingPlay = false;\n\n // ── Construction & lifecycle ───────────────────────────────────────────\n\n constructor() {\n super();\n const root = this.attachShadow({ mode: \"open\" });\n this._videoEl = document.createElement(\"video\");\n this._videoEl.setAttribute(\"part\", \"video\");\n this._videoEl.style.cssText = \"width:100%;height:100%;display:block;background:#000;\";\n this._videoEl.playsInline = true;\n root.appendChild(this._videoEl);\n\n // Forward the underlying <video>'s `progress` event so consumers can\n // observe buffered-range updates without reaching into the shadow DOM.\n // This works for native + remux (real video element with buffered\n // ranges) and is a no-op for hybrid/fallback (canvas-rendered, no\n // buffered ranges yet).\n this._videoEl.addEventListener(\"progress\", () => {\n if (this._destroyed) return;\n this._dispatch(\"progress\", { buffered: this._videoEl.buffered });\n });\n }\n\n connectedCallback(): void {\n if (this._destroyed) return;\n // Connection is the trigger for bootstrap. If we have a pending source\n // (set before connect), kick off bootstrap now.\n const source = this._activeSource();\n if (source != null) {\n void this._bootstrap(source);\n }\n }\n\n disconnectedCallback(): void {\n if (this._destroyed) return;\n // Bump the bootstrap token so any in-flight async work is invalidated\n // before we tear down. _teardown() also bumps but we want the bump to\n // happen synchronously here so any awaited promise that resolves\n // between `disconnect` and `_teardown` sees the new ID.\n this._bootstrapId++;\n void this._teardown();\n }\n\n attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {\n if (this._destroyed) return;\n switch (name) {\n case \"src\":\n if (this._suppressSrcAttrCallback) break;\n this._setSrcInternal(newValue);\n break;\n case \"autoplay\":\n case \"muted\":\n case \"loop\":\n // Reflect onto the underlying <video> element.\n if (newValue == null) this._videoEl.removeAttribute(name);\n else this._videoEl.setAttribute(name, newValue);\n break;\n case \"preload\":\n if (newValue == null) this._videoEl.removeAttribute(\"preload\");\n else this._videoEl.setAttribute(\"preload\", newValue);\n break;\n case \"diagnostics\":\n // Phase A: no UI. Property is observable for users via getDiagnostics().\n break;\n case \"preferstrategy\":\n if (newValue && PREFERRED_STRATEGY_VALUES.has(newValue as PreferredStrategy)) {\n this._preferredStrategy = newValue as PreferredStrategy;\n } else {\n this._preferredStrategy = \"auto\";\n }\n break;\n }\n }\n\n // ── Source handling ────────────────────────────────────────────────────\n\n /** Returns the currently-active source (src or source), whichever is set. */\n private _activeSource(): MediaInput | null {\n if (this._source != null) return this._source;\n if (this._src != null) return this._src;\n return null;\n }\n\n /** Internal src setter — separate from the property setter so the\n * attributeChangedCallback can use it without re-entering reflection. */\n private _setSrcInternal(value: string | null): void {\n // Same-value reassignment: no-op (#11 in the lifecycle list).\n if (value === this._src && this._source == null) return;\n this._src = value;\n this._source = null;\n this._onSourceChanged();\n }\n\n /** Called whenever the active source changes (src or source). */\n private _onSourceChanged(): void {\n if (this._destroyed) return;\n const source = this._activeSource();\n if (source == null) {\n // Null transition: tear down and stay idle.\n this._bootstrapId++;\n void this._teardown();\n return;\n }\n // Only bootstrap if we're connected to the DOM.\n if (this.isConnected) {\n void this._bootstrap(source);\n }\n }\n\n // ── Bootstrap (the only place a UnifiedPlayer is created) ──────────────\n\n private async _bootstrap(source: MediaInput): Promise<void> {\n if (this._destroyed) return;\n const id = ++this._bootstrapId;\n\n // Tear down any existing player before starting a new one. Pass the\n // bootstrap id we just claimed so teardown doesn't bump it again\n // (which would invalidate ourselves).\n await this._teardown(id);\n if (id !== this._bootstrapId || this._destroyed) return;\n\n this._dispatch(\"loadstart\", {});\n\n let player: UnifiedPlayer;\n try {\n player = await createPlayer({\n source,\n target: this._videoEl,\n });\n } catch (err) {\n // Stale or destroyed — silently abandon.\n if (id !== this._bootstrapId || this._destroyed) return;\n this._dispatchError(err);\n return;\n }\n\n // Race check: if anything happened during the await above, bail.\n if (id !== this._bootstrapId || this._destroyed || !this.isConnected) {\n try { await player.destroy(); } catch { /* ignore */ }\n return;\n }\n\n this._player = player;\n\n // Wire events. The unsubscribe handles are not stored individually\n // because destroy() will tear down the whole session anyway.\n player.on(\"strategy\", ({ strategy, reason }) => {\n // strategy event fires on initial classification AND any escalation.\n const cls = player.getDiagnostics().strategyClass;\n this._strategy = strategy;\n this._strategyClass = cls === \"pending\" ? null : cls;\n this._dispatch(\"strategychange\", {\n strategy,\n strategyClass: this._strategyClass,\n reason,\n diagnostics: player.getDiagnostics(),\n });\n });\n\n player.on(\"strategychange\", ({ from, to, reason, currentTime }) => {\n this._dispatch(\"strategychange\", {\n from,\n strategy: to,\n strategyClass: player.getDiagnostics().strategyClass === \"pending\" ? null : player.getDiagnostics().strategyClass,\n reason,\n currentTime,\n diagnostics: player.getDiagnostics(),\n });\n });\n\n player.on(\"tracks\", ({ video: _v, audio, subtitle }) => {\n this._audioTracks = audio;\n this._subtitleTracks = subtitle;\n this._dispatch(\"trackschange\", {\n audioTracks: audio,\n subtitleTracks: subtitle,\n });\n });\n\n player.on(\"error\", (err: Error) => {\n this._dispatchError(err);\n });\n\n player.on(\"timeupdate\", ({ currentTime }) => {\n this._dispatch(\"timeupdate\", { currentTime });\n });\n\n player.on(\"ended\", () => {\n this._dispatch(\"ended\", {});\n });\n\n player.on(\"ready\", () => {\n this._dispatch(\"ready\", { diagnostics: player.getDiagnostics() });\n // Apply any pending seek that was set before the player existed.\n if (this._pendingSeek != null) {\n const t = this._pendingSeek;\n this._pendingSeek = null;\n void player.seek(t).catch(() => { /* ignore */ });\n }\n // Honor any pending play() that was queued before bootstrap finished.\n if (this._pendingPlay) {\n this._pendingPlay = false;\n void player.play().catch(() => { /* ignore — autoplay may be blocked */ });\n } else if (this.autoplay) {\n void player.play().catch(() => { /* ignore */ });\n }\n });\n }\n\n /**\n * Tear down the active player and reset runtime state. Idempotent.\n * If `currentBootstrapId` is provided, the bootstrap counter is NOT\n * incremented (used by `_bootstrap()` to avoid invalidating itself).\n */\n private async _teardown(currentBootstrapId?: number): Promise<void> {\n if (currentBootstrapId == null) {\n // External callers (disconnect, destroy, source change) should bump\n // the counter so any in-flight bootstrap is invalidated. The internal\n // _bootstrap() call passes its own ID and we skip the bump.\n this._bootstrapId++;\n }\n const player = this._player;\n this._player = null;\n this._strategy = null;\n this._strategyClass = null;\n this._audioTracks = [];\n this._subtitleTracks = [];\n if (player) {\n try { await player.destroy(); } catch { /* ignore */ }\n }\n }\n\n // ── Public properties ──────────────────────────────────────────────────\n\n get src(): string | null {\n return this._src;\n }\n\n set src(value: string | null) {\n if (value == null) {\n this.removeAttribute(\"src\");\n } else {\n this.setAttribute(\"src\", value);\n }\n // attributeChangedCallback handles the rest.\n }\n\n get source(): MediaInput | null {\n return this._source;\n }\n\n set source(value: MediaInput | null) {\n // Same-value reassignment for rich values is identity-based.\n if (value === this._source && this._src == null) return;\n this._source = value;\n if (value != null) {\n // Setting source clears src. Suppress the attribute callback so\n // removing the src attribute doesn't wipe the source we just set.\n this._src = null;\n if (this.hasAttribute(\"src\")) {\n this._suppressSrcAttrCallback = true;\n try {\n this.removeAttribute(\"src\");\n } finally {\n this._suppressSrcAttrCallback = false;\n }\n }\n }\n this._onSourceChanged();\n }\n\n get autoplay(): boolean {\n return this.hasAttribute(\"autoplay\");\n }\n\n set autoplay(value: boolean) {\n if (value) this.setAttribute(\"autoplay\", \"\");\n else this.removeAttribute(\"autoplay\");\n }\n\n get muted(): boolean {\n return this.hasAttribute(\"muted\");\n }\n\n set muted(value: boolean) {\n if (value) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop(): boolean {\n return this.hasAttribute(\"loop\");\n }\n\n set loop(value: boolean) {\n if (value) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n get preload(): \"none\" | \"metadata\" | \"auto\" {\n const v = this.getAttribute(\"preload\");\n return v === \"none\" || v === \"metadata\" || v === \"auto\" ? v : \"auto\";\n }\n\n set preload(value: \"none\" | \"metadata\" | \"auto\") {\n this.setAttribute(\"preload\", value);\n }\n\n get diagnostics(): boolean {\n return this.hasAttribute(\"diagnostics\");\n }\n\n set diagnostics(value: boolean) {\n if (value) this.setAttribute(\"diagnostics\", \"\");\n else this.removeAttribute(\"diagnostics\");\n }\n\n get preferredStrategy(): PreferredStrategy {\n return this._preferredStrategy;\n }\n\n set preferredStrategy(value: PreferredStrategy) {\n if (PREFERRED_STRATEGY_VALUES.has(value)) {\n this.setAttribute(\"preferstrategy\", value);\n }\n }\n\n get currentTime(): number {\n return this._player?.getCurrentTime() ?? 0;\n }\n\n set currentTime(value: number) {\n if (this._player) {\n void this._player.seek(value).catch(() => { /* ignore */ });\n } else {\n // Defer to the next bootstrap. The `ready` handler applies it.\n this._pendingSeek = value;\n }\n }\n\n get duration(): number {\n return this._player?.getDuration() ?? NaN;\n }\n\n get paused(): boolean {\n return this._videoEl.paused;\n }\n\n get ended(): boolean {\n return this._videoEl.ended;\n }\n\n get readyState(): number {\n return this._videoEl.readyState;\n }\n\n /**\n * Buffered time ranges for the active source. Mirrors the standard\n * `<video>.buffered` `TimeRanges` API. For the native and remux strategies\n * this reflects the underlying SourceBuffer / progressive download state.\n * For the hybrid and fallback (canvas-rendered) strategies it currently\n * returns an empty TimeRanges; v1.1 will synthesize a coarse range from\n * the decoder's read position.\n */\n get buffered(): TimeRanges {\n return this._videoEl.buffered;\n }\n\n get strategy(): StrategyName | null {\n return this._strategy;\n }\n\n get strategyClass(): StrategyClass | null {\n return this._strategyClass;\n }\n\n get player(): UnifiedPlayer | null {\n return this._player;\n }\n\n get audioTracks(): AudioTrackInfo[] {\n return this._audioTracks;\n }\n\n get subtitleTracks(): SubtitleTrackInfo[] {\n return this._subtitleTracks;\n }\n\n // ── Public methods ─────────────────────────────────────────────────────\n\n /** Force a (re-)bootstrap if a source is currently set. */\n async load(): Promise<void> {\n if (this._destroyed) return;\n const source = this._activeSource();\n if (source == null) return;\n await this._bootstrap(source);\n }\n\n /**\n * Begin or resume playback. If the player isn't ready yet, the call is\n * queued and applied once `ready` fires.\n */\n async play(): Promise<void> {\n if (this._destroyed) return;\n if (this._player) {\n await this._player.play();\n } else {\n this._pendingPlay = true;\n }\n }\n\n pause(): void {\n if (this._destroyed) return;\n this._pendingPlay = false;\n this._player?.pause();\n }\n\n /**\n * Tear down the element permanently. After destroy(), the element ignores\n * all method calls and attribute changes.\n */\n async destroy(): Promise<void> {\n if (this._destroyed) return;\n this._destroyed = true;\n await this._teardown();\n this._dispatch(\"destroy\", {});\n }\n\n async setAudioTrack(id: number): Promise<void> {\n if (this._destroyed || !this._player) return;\n await this._player.setAudioTrack(id);\n }\n\n async setSubtitleTrack(id: number | null): Promise<void> {\n if (this._destroyed || !this._player) return;\n await this._player.setSubtitleTrack(id);\n }\n\n getDiagnostics(): DiagnosticsSnapshot | null {\n return this._player?.getDiagnostics() ?? null;\n }\n\n // ── Event helpers ──────────────────────────────────────────────────────\n\n private _dispatch<T>(name: string, detail: T): void {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: false }));\n }\n\n private _dispatchError(err: unknown): void {\n const error = err instanceof Error ? err : new Error(String(err));\n this._dispatch(\"error\", { error, diagnostics: this._player?.getDiagnostics() ?? null });\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"avbridge-player\": AvbridgePlayerElement;\n }\n}\n","/**\n * Subpath entry: `import \"avbridge/element\"` registers the\n * `<avbridge-player>` custom element.\n *\n * This is a separate entry point from the core (`avbridge`) so that consumers\n * who only want the engine don't pay for the element code, and consumers who\n * want both pay for the element code exactly once.\n *\n * The registration is guarded so re-importing this module (e.g. via HMR or\n * multiple bundles) does not throw a \"name already defined\" error.\n */\n\nimport { AvbridgePlayerElement } from \"./element/avbridge-player.js\";\n\nexport { AvbridgePlayerElement };\n\nif (typeof customElements !== \"undefined\" && !customElements.get(\"avbridge-player\")) {\n customElements.define(\"avbridge-player\", AvbridgePlayerElement);\n}\n"]}
1
+ {"version":3,"sources":["../src/element/avbridge-player.ts","../src/element.ts"],"names":["createPlayer"],"mappings":"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","file":"element.cjs","sourcesContent":["/**\n * `<avbridge-player>` — reference web component for the avbridge engine.\n *\n * This is a *thin* wrapper around `createPlayer()`. Its purpose is to:\n *\n * 1. Validate the public API by being a real consumer of it.\n * 2. Drive lifecycle correctness in the core via adversarial integration tests.\n * 3. Provide a drop-in player for users who don't want to wire `createPlayer()`\n * themselves.\n *\n * It is **not** a player UI framework. See `docs/dev/WEB_COMPONENT_SPEC.md`\n * for the full spec, lifecycle invariants, and edge case list.\n *\n * Phase A scope (this file): lifecycle scaffold only — `src` / `source` /\n * `currentTime` / `play` / `pause` / `load` / `destroy` / events. No built-in\n * controls. Shadow DOM contains a single `<video part=\"video\">`.\n */\n\nimport { createPlayer, type UnifiedPlayer } from \"../player.js\";\nimport type {\n MediaInput,\n StrategyName,\n StrategyClass,\n AudioTrackInfo,\n SubtitleTrackInfo,\n DiagnosticsSnapshot,\n} from \"../types.js\";\n\n/** Strategy preference passed via the `preferstrategy` attribute. */\ntype PreferredStrategy = \"auto\" | StrategyName;\n\nconst PREFERRED_STRATEGY_VALUES = new Set<PreferredStrategy>([\n \"auto\",\n \"native\",\n \"remux\",\n \"hybrid\",\n \"fallback\",\n]);\n\n/**\n * Standard `HTMLMediaElement` events we forward from the inner `<video>`\n * to the wrapper element so consumers can `el.addEventListener(\"loadedmetadata\", ...)`\n * exactly like they would with a real `<video>`. The element also dispatches\n * its own custom events (`strategychange`, `ready`, `error`, etc.) — those\n * are NOT in this list because they're avbridge-specific.\n *\n * Note: `progress` and `timeupdate` are deliberately NOT forwarded here.\n * `progress` is dispatched by the constructor with our own `{ buffered }`\n * detail. `timeupdate` is dispatched by the player layer (so it works for\n * canvas-rendered fallback playback too, where the inner <video> never\n * fires its own timeupdate).\n */\nconst FORWARDED_VIDEO_EVENTS = [\n \"loadstart\",\n \"loadedmetadata\",\n \"loadeddata\",\n \"canplay\",\n \"canplaythrough\",\n \"play\",\n \"playing\",\n \"pause\",\n \"seeking\",\n \"seeked\",\n \"volumechange\",\n \"ratechange\",\n \"durationchange\",\n \"waiting\",\n \"stalled\",\n \"emptied\",\n \"resize\",\n \"error\",\n] as const;\n\n/**\n * `HTMLElement` is a browser-only global. SSR frameworks (Next.js, Astro,\n * Remix, etc.) commonly import library modules on the server to extract\n * types or do tree-shaking, even if the user only ends up using them in\n * the browser. If we extended `HTMLElement` directly, the `class extends`\n * expression would be evaluated at module load time and crash in Node.\n *\n * The fix: in non-browser environments, fall back to an empty stub class.\n * The element is never *constructed* server-side (the registration in\n * `element.ts` is guarded by `typeof customElements !== \"undefined\"`), so\n * the stub is never instantiated — it just lets the class declaration\n * evaluate cleanly so the module can be imported anywhere.\n */\nconst HTMLElementCtor: typeof HTMLElement =\n typeof HTMLElement !== \"undefined\"\n ? HTMLElement\n : (class {} as unknown as typeof HTMLElement);\n\n/**\n * Custom element. Lifecycle correctness is enforced via a monotonically\n * increasing `_bootstrapId`: every async bootstrap captures the ID at start\n * and discards itself if the ID has changed by the time it resolves. This\n * single pattern handles disconnect-during-bootstrap, rapid src reassignment,\n * bootstrap races, and destroy-during-bootstrap.\n */\nexport class AvbridgePlayerElement extends HTMLElementCtor {\n static readonly observedAttributes = [\n \"src\",\n \"autoplay\",\n \"muted\",\n \"loop\",\n \"preload\",\n \"poster\",\n \"playsinline\",\n \"crossorigin\",\n \"disableremoteplayback\",\n \"diagnostics\",\n \"preferstrategy\",\n ];\n\n // ── Internal state ─────────────────────────────────────────────────────\n\n /** The shadow DOM `<video>` element that strategies render into. */\n private _videoEl!: HTMLVideoElement;\n\n /** Active player session, if any. Cleared on teardown. */\n private _player: UnifiedPlayer | null = null;\n\n /**\n * Monotonic counter incremented on every (re)bootstrap. Async bootstrap\n * work captures the current ID; if it doesn't match by the time the work\n * resolves, the work is discarded.\n */\n private _bootstrapId = 0;\n\n /** True after destroy() — element is permanently unusable. */\n private _destroyed = false;\n\n /** Internal source state. Either string-form (src) OR rich (source). */\n private _src: string | null = null;\n private _source: MediaInput | null = null;\n\n /**\n * Set when the `source` property setter is in the middle of clearing the\n * `src` attribute as part of mutual exclusion. The attributeChangedCallback\n * checks this flag and skips its normal \"clear source\" side effect, which\n * would otherwise wipe the value we just set.\n */\n private _suppressSrcAttrCallback = false;\n\n /** Last-known runtime state surfaced via getters. */\n private _strategy: StrategyName | null = null;\n private _strategyClass: StrategyClass | null = null;\n private _audioTracks: AudioTrackInfo[] = [];\n private _subtitleTracks: SubtitleTrackInfo[] = [];\n\n /** Strategy preference (does not currently affect routing — reserved). */\n private _preferredStrategy: PreferredStrategy = \"auto\";\n\n /** Set if currentTime was assigned before the player was ready. */\n private _pendingSeek: number | null = null;\n /** Set if play() was called before the player was ready. */\n private _pendingPlay = false;\n\n /** MutationObserver tracking light-DOM `<track>` children. */\n private _trackObserver: MutationObserver | null = null;\n\n // ── Construction & lifecycle ───────────────────────────────────────────\n\n constructor() {\n super();\n const root = this.attachShadow({ mode: \"open\" });\n this._videoEl = document.createElement(\"video\");\n this._videoEl.setAttribute(\"part\", \"video\");\n this._videoEl.style.cssText = \"width:100%;height:100%;display:block;background:#000;\";\n this._videoEl.playsInline = true;\n root.appendChild(this._videoEl);\n\n // Forward the underlying <video>'s `progress` event so consumers can\n // observe buffered-range updates without reaching into the shadow DOM.\n // This works for native + remux (real video element with buffered\n // ranges) and is a no-op for hybrid/fallback (canvas-rendered, no\n // buffered ranges yet).\n this._videoEl.addEventListener(\"progress\", () => {\n if (this._destroyed) return;\n this._dispatch(\"progress\", { buffered: this._videoEl.buffered });\n });\n\n // Forward all standard HTMLMediaElement events from the inner <video>\n // so consumers can use the element as a drop-in <video> replacement.\n // Each event is re-dispatched on the wrapper element with no detail —\n // listeners that need state should read it from the element directly.\n for (const eventName of FORWARDED_VIDEO_EVENTS) {\n this._videoEl.addEventListener(eventName, () => {\n if (this._destroyed) return;\n this.dispatchEvent(new Event(eventName, { bubbles: false }));\n });\n }\n }\n\n connectedCallback(): void {\n if (this._destroyed) return;\n // Pick up any <track> children that were declared in HTML before the\n // element upgraded, and watch for future additions/removals.\n this._syncTextTracks();\n if (!this._trackObserver) {\n this._trackObserver = new MutationObserver(() => this._syncTextTracks());\n this._trackObserver.observe(this, { childList: true, subtree: false });\n }\n // Connection is the trigger for bootstrap. If we have a pending source\n // (set before connect), kick off bootstrap now.\n const source = this._activeSource();\n if (source != null) {\n void this._bootstrap(source);\n }\n }\n\n disconnectedCallback(): void {\n if (this._destroyed) return;\n if (this._trackObserver) {\n this._trackObserver.disconnect();\n this._trackObserver = null;\n }\n // Bump the bootstrap token so any in-flight async work is invalidated\n // before we tear down. _teardown() also bumps but we want the bump to\n // happen synchronously here so any awaited promise that resolves\n // between `disconnect` and `_teardown` sees the new ID.\n this._bootstrapId++;\n void this._teardown();\n }\n\n attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {\n if (this._destroyed) return;\n switch (name) {\n case \"src\":\n if (this._suppressSrcAttrCallback) break;\n this._setSrcInternal(newValue);\n break;\n case \"autoplay\":\n case \"muted\":\n case \"loop\":\n case \"playsinline\":\n case \"disableremoteplayback\":\n // Reflect onto the underlying <video> element.\n if (newValue == null) this._videoEl.removeAttribute(name);\n else this._videoEl.setAttribute(name, newValue);\n break;\n case \"preload\":\n case \"poster\":\n case \"crossorigin\":\n if (newValue == null) this._videoEl.removeAttribute(name);\n else this._videoEl.setAttribute(name, newValue);\n break;\n case \"diagnostics\":\n // Phase A: no UI. Property is observable for users via getDiagnostics().\n break;\n case \"preferstrategy\":\n if (newValue && PREFERRED_STRATEGY_VALUES.has(newValue as PreferredStrategy)) {\n this._preferredStrategy = newValue as PreferredStrategy;\n } else {\n this._preferredStrategy = \"auto\";\n }\n break;\n }\n }\n\n // ── Source handling ────────────────────────────────────────────────────\n\n /** Returns the currently-active source (src or source), whichever is set. */\n private _activeSource(): MediaInput | null {\n if (this._source != null) return this._source;\n if (this._src != null) return this._src;\n return null;\n }\n\n /**\n * Mirror light-DOM `<track>` children into the shadow `<video>` so that\n * the browser's native text-track machinery picks them up. Called on\n * connect, on every mutation of light-DOM children, and once after each\n * source change so newly-set tracks survive a fresh `<video>`.\n *\n * Strategy: clone the children. We don't move them because the user's\n * code may still hold references to the originals (e.g. to set `default`).\n * The shadow copies are throwaway — we wipe them on every sync.\n */\n private _syncTextTracks(): void {\n // Remove existing shadow tracks.\n const existing = this._videoEl.querySelectorAll(\"track\");\n for (const t of Array.from(existing)) t.remove();\n // Clone every <track> light-DOM child into the shadow video.\n for (const child of Array.from(this.children)) {\n if (child.tagName === \"TRACK\") {\n const clone = child.cloneNode(true) as HTMLTrackElement;\n this._videoEl.appendChild(clone);\n }\n }\n }\n\n /** Internal src setter — separate from the property setter so the\n * attributeChangedCallback can use it without re-entering reflection. */\n private _setSrcInternal(value: string | null): void {\n // Same-value reassignment: no-op (#11 in the lifecycle list).\n if (value === this._src && this._source == null) return;\n this._src = value;\n this._source = null;\n this._onSourceChanged();\n }\n\n /** Called whenever the active source changes (src or source). */\n private _onSourceChanged(): void {\n if (this._destroyed) return;\n const source = this._activeSource();\n if (source == null) {\n // Null transition: tear down and stay idle.\n this._bootstrapId++;\n void this._teardown();\n return;\n }\n // Only bootstrap if we're connected to the DOM.\n if (this.isConnected) {\n void this._bootstrap(source);\n }\n }\n\n // ── Bootstrap (the only place a UnifiedPlayer is created) ──────────────\n\n private async _bootstrap(source: MediaInput): Promise<void> {\n if (this._destroyed) return;\n const id = ++this._bootstrapId;\n\n // Tear down any existing player before starting a new one. Pass the\n // bootstrap id we just claimed so teardown doesn't bump it again\n // (which would invalidate ourselves).\n await this._teardown(id);\n if (id !== this._bootstrapId || this._destroyed) return;\n\n this._dispatch(\"loadstart\", {});\n\n let player: UnifiedPlayer;\n try {\n player = await createPlayer({\n source,\n target: this._videoEl,\n });\n } catch (err) {\n // Stale or destroyed — silently abandon.\n if (id !== this._bootstrapId || this._destroyed) return;\n this._dispatchError(err);\n return;\n }\n\n // Race check: if anything happened during the await above, bail.\n if (id !== this._bootstrapId || this._destroyed || !this.isConnected) {\n try { await player.destroy(); } catch { /* ignore */ }\n return;\n }\n\n this._player = player;\n\n // Resync any light-DOM <track> children into the (possibly fresh) shadow\n // <video>. Strategies that swap or reset the inner video state would\n // otherwise lose the tracks the user declared in HTML.\n this._syncTextTracks();\n\n // Wire events. The unsubscribe handles are not stored individually\n // because destroy() will tear down the whole session anyway.\n player.on(\"strategy\", ({ strategy, reason }) => {\n // strategy event fires on initial classification AND any escalation.\n const cls = player.getDiagnostics().strategyClass;\n this._strategy = strategy;\n this._strategyClass = cls === \"pending\" ? null : cls;\n this._dispatch(\"strategychange\", {\n strategy,\n strategyClass: this._strategyClass,\n reason,\n diagnostics: player.getDiagnostics(),\n });\n });\n\n player.on(\"strategychange\", ({ from, to, reason, currentTime }) => {\n this._dispatch(\"strategychange\", {\n from,\n strategy: to,\n strategyClass: player.getDiagnostics().strategyClass === \"pending\" ? null : player.getDiagnostics().strategyClass,\n reason,\n currentTime,\n diagnostics: player.getDiagnostics(),\n });\n });\n\n player.on(\"tracks\", ({ video: _v, audio, subtitle }) => {\n this._audioTracks = audio;\n this._subtitleTracks = subtitle;\n this._dispatch(\"trackschange\", {\n audioTracks: audio,\n subtitleTracks: subtitle,\n });\n });\n\n player.on(\"error\", (err: Error) => {\n this._dispatchError(err);\n });\n\n player.on(\"timeupdate\", ({ currentTime }) => {\n this._dispatch(\"timeupdate\", { currentTime });\n });\n\n player.on(\"ended\", () => {\n this._dispatch(\"ended\", {});\n });\n\n player.on(\"ready\", () => {\n this._dispatch(\"ready\", { diagnostics: player.getDiagnostics() });\n // Apply any pending seek that was set before the player existed.\n if (this._pendingSeek != null) {\n const t = this._pendingSeek;\n this._pendingSeek = null;\n void player.seek(t).catch(() => { /* ignore */ });\n }\n // Honor any pending play() that was queued before bootstrap finished.\n if (this._pendingPlay) {\n this._pendingPlay = false;\n void player.play().catch(() => { /* ignore — autoplay may be blocked */ });\n } else if (this.autoplay) {\n void player.play().catch(() => { /* ignore */ });\n }\n });\n }\n\n /**\n * Tear down the active player and reset runtime state. Idempotent.\n * If `currentBootstrapId` is provided, the bootstrap counter is NOT\n * incremented (used by `_bootstrap()` to avoid invalidating itself).\n */\n private async _teardown(currentBootstrapId?: number): Promise<void> {\n if (currentBootstrapId == null) {\n // External callers (disconnect, destroy, source change) should bump\n // the counter so any in-flight bootstrap is invalidated. The internal\n // _bootstrap() call passes its own ID and we skip the bump.\n this._bootstrapId++;\n }\n const player = this._player;\n this._player = null;\n this._strategy = null;\n this._strategyClass = null;\n this._audioTracks = [];\n this._subtitleTracks = [];\n if (player) {\n try { await player.destroy(); } catch { /* ignore */ }\n }\n }\n\n // ── Public properties ──────────────────────────────────────────────────\n\n get src(): string | null {\n return this._src;\n }\n\n set src(value: string | null) {\n if (value == null) {\n this.removeAttribute(\"src\");\n } else {\n this.setAttribute(\"src\", value);\n }\n // attributeChangedCallback handles the rest.\n }\n\n get source(): MediaInput | null {\n return this._source;\n }\n\n set source(value: MediaInput | null) {\n // Same-value reassignment for rich values is identity-based.\n if (value === this._source && this._src == null) return;\n this._source = value;\n if (value != null) {\n // Setting source clears src. Suppress the attribute callback so\n // removing the src attribute doesn't wipe the source we just set.\n this._src = null;\n if (this.hasAttribute(\"src\")) {\n this._suppressSrcAttrCallback = true;\n try {\n this.removeAttribute(\"src\");\n } finally {\n this._suppressSrcAttrCallback = false;\n }\n }\n }\n this._onSourceChanged();\n }\n\n get autoplay(): boolean {\n return this.hasAttribute(\"autoplay\");\n }\n\n set autoplay(value: boolean) {\n if (value) this.setAttribute(\"autoplay\", \"\");\n else this.removeAttribute(\"autoplay\");\n }\n\n get muted(): boolean {\n return this.hasAttribute(\"muted\");\n }\n\n set muted(value: boolean) {\n if (value) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop(): boolean {\n return this.hasAttribute(\"loop\");\n }\n\n set loop(value: boolean) {\n if (value) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n get preload(): \"none\" | \"metadata\" | \"auto\" {\n const v = this.getAttribute(\"preload\");\n return v === \"none\" || v === \"metadata\" || v === \"auto\" ? v : \"auto\";\n }\n\n set preload(value: \"none\" | \"metadata\" | \"auto\") {\n this.setAttribute(\"preload\", value);\n }\n\n get diagnostics(): boolean {\n return this.hasAttribute(\"diagnostics\");\n }\n\n set diagnostics(value: boolean) {\n if (value) this.setAttribute(\"diagnostics\", \"\");\n else this.removeAttribute(\"diagnostics\");\n }\n\n get preferredStrategy(): PreferredStrategy {\n return this._preferredStrategy;\n }\n\n set preferredStrategy(value: PreferredStrategy) {\n if (PREFERRED_STRATEGY_VALUES.has(value)) {\n this.setAttribute(\"preferstrategy\", value);\n }\n }\n\n get currentTime(): number {\n return this._player?.getCurrentTime() ?? 0;\n }\n\n set currentTime(value: number) {\n if (this._player) {\n void this._player.seek(value).catch(() => { /* ignore */ });\n } else {\n // Defer to the next bootstrap. The `ready` handler applies it.\n this._pendingSeek = value;\n }\n }\n\n get duration(): number {\n return this._player?.getDuration() ?? NaN;\n }\n\n get paused(): boolean {\n return this._videoEl.paused;\n }\n\n get ended(): boolean {\n return this._videoEl.ended;\n }\n\n get readyState(): number {\n return this._videoEl.readyState;\n }\n\n /**\n * Buffered time ranges for the active source. Mirrors the standard\n * `<video>.buffered` `TimeRanges` API. For the native and remux strategies\n * this reflects the underlying SourceBuffer / progressive download state.\n * For the hybrid and fallback (canvas-rendered) strategies it currently\n * returns an empty TimeRanges; a future release will synthesize a coarse\n * range from the decoder's read position.\n */\n get buffered(): TimeRanges {\n return this._videoEl.buffered;\n }\n\n // ── HTMLMediaElement parity ───────────────────────────────────────────\n // Mirror the standard <video> surface so consumers can drop the element\n // in as a <video> replacement. Each property is a thin passthrough to the\n // shadow `<video>`.\n\n get poster(): string {\n return this._videoEl.poster;\n }\n set poster(value: string) {\n if (value == null || value === \"\") this.removeAttribute(\"poster\");\n else this.setAttribute(\"poster\", value);\n }\n\n get volume(): number {\n return this._videoEl.volume;\n }\n set volume(value: number) {\n this._videoEl.volume = value;\n }\n\n get playbackRate(): number {\n return this._videoEl.playbackRate;\n }\n set playbackRate(value: number) {\n this._videoEl.playbackRate = value;\n }\n\n get videoWidth(): number {\n return this._videoEl.videoWidth;\n }\n\n get videoHeight(): number {\n return this._videoEl.videoHeight;\n }\n\n get played(): TimeRanges {\n return this._videoEl.played;\n }\n\n get seekable(): TimeRanges {\n return this._videoEl.seekable;\n }\n\n get crossOrigin(): string | null {\n return this._videoEl.crossOrigin;\n }\n set crossOrigin(value: string | null) {\n if (value == null) this.removeAttribute(\"crossorigin\");\n else this.setAttribute(\"crossorigin\", value);\n }\n\n get disableRemotePlayback(): boolean {\n return this._videoEl.disableRemotePlayback;\n }\n set disableRemotePlayback(value: boolean) {\n if (value) this.setAttribute(\"disableremoteplayback\", \"\");\n else this.removeAttribute(\"disableremoteplayback\");\n }\n\n /**\n * Native `HTMLMediaElement.canPlayType()` passthrough. Note that this\n * answers about the *browser's* native support, not avbridge's full\n * capabilities — avbridge can play many formats this method returns \"\"\n * for, by routing them to the remux/hybrid/fallback strategies.\n */\n canPlayType(mimeType: string): CanPlayTypeResult {\n return this._videoEl.canPlayType(mimeType);\n }\n\n /**\n * **Escape hatch.** The underlying shadow-DOM `<video>` element.\n *\n * Use for native browser APIs the wrapper doesn't expose:\n * - `el.videoElement.requestPictureInPicture()`\n * - `el.videoElement.audioTracks` (browser native, not avbridge's track list)\n * - direct integration with libraries that need a real HTMLVideoElement\n *\n * **Caveat:** When the active strategy is `\"fallback\"` or `\"hybrid\"`,\n * frames are rendered to a canvas overlay, not into this `<video>`.\n * APIs that depend on the actual pixels (Picture-in-Picture, captureStream)\n * will not show the playing content in those modes. Check `el.strategy`\n * before using such APIs.\n */\n get videoElement(): HTMLVideoElement {\n return this._videoEl;\n }\n\n get strategy(): StrategyName | null {\n return this._strategy;\n }\n\n get strategyClass(): StrategyClass | null {\n return this._strategyClass;\n }\n\n get player(): UnifiedPlayer | null {\n return this._player;\n }\n\n get audioTracks(): AudioTrackInfo[] {\n return this._audioTracks;\n }\n\n get subtitleTracks(): SubtitleTrackInfo[] {\n return this._subtitleTracks;\n }\n\n // ── Public methods ─────────────────────────────────────────────────────\n\n /** Force a (re-)bootstrap if a source is currently set. */\n async load(): Promise<void> {\n if (this._destroyed) return;\n const source = this._activeSource();\n if (source == null) return;\n await this._bootstrap(source);\n }\n\n /**\n * Begin or resume playback. If the player isn't ready yet, the call is\n * queued and applied once `ready` fires.\n */\n async play(): Promise<void> {\n if (this._destroyed) return;\n if (this._player) {\n await this._player.play();\n } else {\n this._pendingPlay = true;\n }\n }\n\n pause(): void {\n if (this._destroyed) return;\n this._pendingPlay = false;\n this._player?.pause();\n }\n\n /**\n * Tear down the element permanently. After destroy(), the element ignores\n * all method calls and attribute changes.\n */\n async destroy(): Promise<void> {\n if (this._destroyed) return;\n this._destroyed = true;\n await this._teardown();\n this._dispatch(\"destroy\", {});\n }\n\n async setAudioTrack(id: number): Promise<void> {\n if (this._destroyed || !this._player) return;\n await this._player.setAudioTrack(id);\n }\n\n async setSubtitleTrack(id: number | null): Promise<void> {\n if (this._destroyed || !this._player) return;\n await this._player.setSubtitleTrack(id);\n }\n\n getDiagnostics(): DiagnosticsSnapshot | null {\n return this._player?.getDiagnostics() ?? null;\n }\n\n // ── Event helpers ──────────────────────────────────────────────────────\n\n private _dispatch<T>(name: string, detail: T): void {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: false }));\n }\n\n private _dispatchError(err: unknown): void {\n const error = err instanceof Error ? err : new Error(String(err));\n this._dispatch(\"error\", { error, diagnostics: this._player?.getDiagnostics() ?? null });\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"avbridge-player\": AvbridgePlayerElement;\n }\n}\n","/**\n * Subpath entry: `import \"avbridge/element\"` registers the\n * `<avbridge-player>` custom element.\n *\n * This is a separate entry point from the core (`avbridge`) so that consumers\n * who only want the engine don't pay for the element code, and consumers who\n * want both pay for the element code exactly once.\n *\n * The registration is guarded so re-importing this module (e.g. via HMR or\n * multiple bundles) does not throw a \"name already defined\" error.\n */\n\nimport { AvbridgePlayerElement } from \"./element/avbridge-player.js\";\n\nexport { AvbridgePlayerElement };\n\nif (typeof customElements !== \"undefined\" && !customElements.get(\"avbridge-player\")) {\n customElements.define(\"avbridge-player\", AvbridgePlayerElement);\n}\n"]}
@@ -76,12 +76,25 @@ declare class AvbridgePlayerElement extends HTMLElementCtor {
76
76
  private _pendingSeek;
77
77
  /** Set if play() was called before the player was ready. */
78
78
  private _pendingPlay;
79
+ /** MutationObserver tracking light-DOM `<track>` children. */
80
+ private _trackObserver;
79
81
  constructor();
80
82
  connectedCallback(): void;
81
83
  disconnectedCallback(): void;
82
84
  attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
83
85
  /** Returns the currently-active source (src or source), whichever is set. */
84
86
  private _activeSource;
87
+ /**
88
+ * Mirror light-DOM `<track>` children into the shadow `<video>` so that
89
+ * the browser's native text-track machinery picks them up. Called on
90
+ * connect, on every mutation of light-DOM children, and once after each
91
+ * source change so newly-set tracks survive a fresh `<video>`.
92
+ *
93
+ * Strategy: clone the children. We don't move them because the user's
94
+ * code may still hold references to the originals (e.g. to set `default`).
95
+ * The shadow copies are throwaway — we wipe them on every sync.
96
+ */
97
+ private _syncTextTracks;
85
98
  /** Internal src setter — separate from the property setter so the
86
99
  * attributeChangedCallback can use it without re-entering reflection. */
87
100
  private _setSrcInternal;
@@ -121,10 +134,46 @@ declare class AvbridgePlayerElement extends HTMLElementCtor {
121
134
  * `<video>.buffered` `TimeRanges` API. For the native and remux strategies
122
135
  * this reflects the underlying SourceBuffer / progressive download state.
123
136
  * For the hybrid and fallback (canvas-rendered) strategies it currently
124
- * returns an empty TimeRanges; v1.1 will synthesize a coarse range from
125
- * the decoder's read position.
137
+ * returns an empty TimeRanges; a future release will synthesize a coarse
138
+ * range from the decoder's read position.
126
139
  */
127
140
  get buffered(): TimeRanges;
141
+ get poster(): string;
142
+ set poster(value: string);
143
+ get volume(): number;
144
+ set volume(value: number);
145
+ get playbackRate(): number;
146
+ set playbackRate(value: number);
147
+ get videoWidth(): number;
148
+ get videoHeight(): number;
149
+ get played(): TimeRanges;
150
+ get seekable(): TimeRanges;
151
+ get crossOrigin(): string | null;
152
+ set crossOrigin(value: string | null);
153
+ get disableRemotePlayback(): boolean;
154
+ set disableRemotePlayback(value: boolean);
155
+ /**
156
+ * Native `HTMLMediaElement.canPlayType()` passthrough. Note that this
157
+ * answers about the *browser's* native support, not avbridge's full
158
+ * capabilities — avbridge can play many formats this method returns ""
159
+ * for, by routing them to the remux/hybrid/fallback strategies.
160
+ */
161
+ canPlayType(mimeType: string): CanPlayTypeResult;
162
+ /**
163
+ * **Escape hatch.** The underlying shadow-DOM `<video>` element.
164
+ *
165
+ * Use for native browser APIs the wrapper doesn't expose:
166
+ * - `el.videoElement.requestPictureInPicture()`
167
+ * - `el.videoElement.audioTracks` (browser native, not avbridge's track list)
168
+ * - direct integration with libraries that need a real HTMLVideoElement
169
+ *
170
+ * **Caveat:** When the active strategy is `"fallback"` or `"hybrid"`,
171
+ * frames are rendered to a canvas overlay, not into this `<video>`.
172
+ * APIs that depend on the actual pixels (Picture-in-Picture, captureStream)
173
+ * will not show the playing content in those modes. Check `el.strategy`
174
+ * before using such APIs.
175
+ */
176
+ get videoElement(): HTMLVideoElement;
128
177
  get strategy(): StrategyName | null;
129
178
  get strategyClass(): StrategyClass | null;
130
179
  get player(): UnifiedPlayer | null;
package/dist/element.d.ts CHANGED
@@ -76,12 +76,25 @@ declare class AvbridgePlayerElement extends HTMLElementCtor {
76
76
  private _pendingSeek;
77
77
  /** Set if play() was called before the player was ready. */
78
78
  private _pendingPlay;
79
+ /** MutationObserver tracking light-DOM `<track>` children. */
80
+ private _trackObserver;
79
81
  constructor();
80
82
  connectedCallback(): void;
81
83
  disconnectedCallback(): void;
82
84
  attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
83
85
  /** Returns the currently-active source (src or source), whichever is set. */
84
86
  private _activeSource;
87
+ /**
88
+ * Mirror light-DOM `<track>` children into the shadow `<video>` so that
89
+ * the browser's native text-track machinery picks them up. Called on
90
+ * connect, on every mutation of light-DOM children, and once after each
91
+ * source change so newly-set tracks survive a fresh `<video>`.
92
+ *
93
+ * Strategy: clone the children. We don't move them because the user's
94
+ * code may still hold references to the originals (e.g. to set `default`).
95
+ * The shadow copies are throwaway — we wipe them on every sync.
96
+ */
97
+ private _syncTextTracks;
85
98
  /** Internal src setter — separate from the property setter so the
86
99
  * attributeChangedCallback can use it without re-entering reflection. */
87
100
  private _setSrcInternal;
@@ -121,10 +134,46 @@ declare class AvbridgePlayerElement extends HTMLElementCtor {
121
134
  * `<video>.buffered` `TimeRanges` API. For the native and remux strategies
122
135
  * this reflects the underlying SourceBuffer / progressive download state.
123
136
  * For the hybrid and fallback (canvas-rendered) strategies it currently
124
- * returns an empty TimeRanges; v1.1 will synthesize a coarse range from
125
- * the decoder's read position.
137
+ * returns an empty TimeRanges; a future release will synthesize a coarse
138
+ * range from the decoder's read position.
126
139
  */
127
140
  get buffered(): TimeRanges;
141
+ get poster(): string;
142
+ set poster(value: string);
143
+ get volume(): number;
144
+ set volume(value: number);
145
+ get playbackRate(): number;
146
+ set playbackRate(value: number);
147
+ get videoWidth(): number;
148
+ get videoHeight(): number;
149
+ get played(): TimeRanges;
150
+ get seekable(): TimeRanges;
151
+ get crossOrigin(): string | null;
152
+ set crossOrigin(value: string | null);
153
+ get disableRemotePlayback(): boolean;
154
+ set disableRemotePlayback(value: boolean);
155
+ /**
156
+ * Native `HTMLMediaElement.canPlayType()` passthrough. Note that this
157
+ * answers about the *browser's* native support, not avbridge's full
158
+ * capabilities — avbridge can play many formats this method returns ""
159
+ * for, by routing them to the remux/hybrid/fallback strategies.
160
+ */
161
+ canPlayType(mimeType: string): CanPlayTypeResult;
162
+ /**
163
+ * **Escape hatch.** The underlying shadow-DOM `<video>` element.
164
+ *
165
+ * Use for native browser APIs the wrapper doesn't expose:
166
+ * - `el.videoElement.requestPictureInPicture()`
167
+ * - `el.videoElement.audioTracks` (browser native, not avbridge's track list)
168
+ * - direct integration with libraries that need a real HTMLVideoElement
169
+ *
170
+ * **Caveat:** When the active strategy is `"fallback"` or `"hybrid"`,
171
+ * frames are rendered to a canvas overlay, not into this `<video>`.
172
+ * APIs that depend on the actual pixels (Picture-in-Picture, captureStream)
173
+ * will not show the playing content in those modes. Check `el.strategy`
174
+ * before using such APIs.
175
+ */
176
+ get videoElement(): HTMLVideoElement;
128
177
  get strategy(): StrategyName | null;
129
178
  get strategyClass(): StrategyClass | null;
130
179
  get player(): UnifiedPlayer | null;
package/dist/element.js CHANGED
@@ -11,6 +11,26 @@ var PREFERRED_STRATEGY_VALUES = /* @__PURE__ */ new Set([
11
11
  "hybrid",
12
12
  "fallback"
13
13
  ]);
14
+ var FORWARDED_VIDEO_EVENTS = [
15
+ "loadstart",
16
+ "loadedmetadata",
17
+ "loadeddata",
18
+ "canplay",
19
+ "canplaythrough",
20
+ "play",
21
+ "playing",
22
+ "pause",
23
+ "seeking",
24
+ "seeked",
25
+ "volumechange",
26
+ "ratechange",
27
+ "durationchange",
28
+ "waiting",
29
+ "stalled",
30
+ "emptied",
31
+ "resize",
32
+ "error"
33
+ ];
14
34
  var HTMLElementCtor = typeof HTMLElement !== "undefined" ? HTMLElement : class {
15
35
  };
16
36
  var AvbridgePlayerElement = class extends HTMLElementCtor {
@@ -20,6 +40,10 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
20
40
  "muted",
21
41
  "loop",
22
42
  "preload",
43
+ "poster",
44
+ "playsinline",
45
+ "crossorigin",
46
+ "disableremoteplayback",
23
47
  "diagnostics",
24
48
  "preferstrategy"
25
49
  ];
@@ -57,6 +81,8 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
57
81
  _pendingSeek = null;
58
82
  /** Set if play() was called before the player was ready. */
59
83
  _pendingPlay = false;
84
+ /** MutationObserver tracking light-DOM `<track>` children. */
85
+ _trackObserver = null;
60
86
  // ── Construction & lifecycle ───────────────────────────────────────────
61
87
  constructor() {
62
88
  super();
@@ -70,9 +96,20 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
70
96
  if (this._destroyed) return;
71
97
  this._dispatch("progress", { buffered: this._videoEl.buffered });
72
98
  });
99
+ for (const eventName of FORWARDED_VIDEO_EVENTS) {
100
+ this._videoEl.addEventListener(eventName, () => {
101
+ if (this._destroyed) return;
102
+ this.dispatchEvent(new Event(eventName, { bubbles: false }));
103
+ });
104
+ }
73
105
  }
74
106
  connectedCallback() {
75
107
  if (this._destroyed) return;
108
+ this._syncTextTracks();
109
+ if (!this._trackObserver) {
110
+ this._trackObserver = new MutationObserver(() => this._syncTextTracks());
111
+ this._trackObserver.observe(this, { childList: true, subtree: false });
112
+ }
76
113
  const source = this._activeSource();
77
114
  if (source != null) {
78
115
  void this._bootstrap(source);
@@ -80,6 +117,10 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
80
117
  }
81
118
  disconnectedCallback() {
82
119
  if (this._destroyed) return;
120
+ if (this._trackObserver) {
121
+ this._trackObserver.disconnect();
122
+ this._trackObserver = null;
123
+ }
83
124
  this._bootstrapId++;
84
125
  void this._teardown();
85
126
  }
@@ -93,12 +134,16 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
93
134
  case "autoplay":
94
135
  case "muted":
95
136
  case "loop":
137
+ case "playsinline":
138
+ case "disableremoteplayback":
96
139
  if (newValue == null) this._videoEl.removeAttribute(name);
97
140
  else this._videoEl.setAttribute(name, newValue);
98
141
  break;
99
142
  case "preload":
100
- if (newValue == null) this._videoEl.removeAttribute("preload");
101
- else this._videoEl.setAttribute("preload", newValue);
143
+ case "poster":
144
+ case "crossorigin":
145
+ if (newValue == null) this._videoEl.removeAttribute(name);
146
+ else this._videoEl.setAttribute(name, newValue);
102
147
  break;
103
148
  case "diagnostics":
104
149
  break;
@@ -118,6 +163,26 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
118
163
  if (this._src != null) return this._src;
119
164
  return null;
120
165
  }
166
+ /**
167
+ * Mirror light-DOM `<track>` children into the shadow `<video>` so that
168
+ * the browser's native text-track machinery picks them up. Called on
169
+ * connect, on every mutation of light-DOM children, and once after each
170
+ * source change so newly-set tracks survive a fresh `<video>`.
171
+ *
172
+ * Strategy: clone the children. We don't move them because the user's
173
+ * code may still hold references to the originals (e.g. to set `default`).
174
+ * The shadow copies are throwaway — we wipe them on every sync.
175
+ */
176
+ _syncTextTracks() {
177
+ const existing = this._videoEl.querySelectorAll("track");
178
+ for (const t of Array.from(existing)) t.remove();
179
+ for (const child of Array.from(this.children)) {
180
+ if (child.tagName === "TRACK") {
181
+ const clone = child.cloneNode(true);
182
+ this._videoEl.appendChild(clone);
183
+ }
184
+ }
185
+ }
121
186
  /** Internal src setter — separate from the property setter so the
122
187
  * attributeChangedCallback can use it without re-entering reflection. */
123
188
  _setSrcInternal(value) {
@@ -165,6 +230,7 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
165
230
  return;
166
231
  }
167
232
  this._player = player;
233
+ this._syncTextTracks();
168
234
  player.on("strategy", ({ strategy, reason }) => {
169
235
  const cls = player.getDiagnostics().strategyClass;
170
236
  this._strategy = strategy;
@@ -344,12 +410,87 @@ var AvbridgePlayerElement = class extends HTMLElementCtor {
344
410
  * `<video>.buffered` `TimeRanges` API. For the native and remux strategies
345
411
  * this reflects the underlying SourceBuffer / progressive download state.
346
412
  * For the hybrid and fallback (canvas-rendered) strategies it currently
347
- * returns an empty TimeRanges; v1.1 will synthesize a coarse range from
348
- * the decoder's read position.
413
+ * returns an empty TimeRanges; a future release will synthesize a coarse
414
+ * range from the decoder's read position.
349
415
  */
350
416
  get buffered() {
351
417
  return this._videoEl.buffered;
352
418
  }
419
+ // ── HTMLMediaElement parity ───────────────────────────────────────────
420
+ // Mirror the standard <video> surface so consumers can drop the element
421
+ // in as a <video> replacement. Each property is a thin passthrough to the
422
+ // shadow `<video>`.
423
+ get poster() {
424
+ return this._videoEl.poster;
425
+ }
426
+ set poster(value) {
427
+ if (value == null || value === "") this.removeAttribute("poster");
428
+ else this.setAttribute("poster", value);
429
+ }
430
+ get volume() {
431
+ return this._videoEl.volume;
432
+ }
433
+ set volume(value) {
434
+ this._videoEl.volume = value;
435
+ }
436
+ get playbackRate() {
437
+ return this._videoEl.playbackRate;
438
+ }
439
+ set playbackRate(value) {
440
+ this._videoEl.playbackRate = value;
441
+ }
442
+ get videoWidth() {
443
+ return this._videoEl.videoWidth;
444
+ }
445
+ get videoHeight() {
446
+ return this._videoEl.videoHeight;
447
+ }
448
+ get played() {
449
+ return this._videoEl.played;
450
+ }
451
+ get seekable() {
452
+ return this._videoEl.seekable;
453
+ }
454
+ get crossOrigin() {
455
+ return this._videoEl.crossOrigin;
456
+ }
457
+ set crossOrigin(value) {
458
+ if (value == null) this.removeAttribute("crossorigin");
459
+ else this.setAttribute("crossorigin", value);
460
+ }
461
+ get disableRemotePlayback() {
462
+ return this._videoEl.disableRemotePlayback;
463
+ }
464
+ set disableRemotePlayback(value) {
465
+ if (value) this.setAttribute("disableremoteplayback", "");
466
+ else this.removeAttribute("disableremoteplayback");
467
+ }
468
+ /**
469
+ * Native `HTMLMediaElement.canPlayType()` passthrough. Note that this
470
+ * answers about the *browser's* native support, not avbridge's full
471
+ * capabilities — avbridge can play many formats this method returns ""
472
+ * for, by routing them to the remux/hybrid/fallback strategies.
473
+ */
474
+ canPlayType(mimeType) {
475
+ return this._videoEl.canPlayType(mimeType);
476
+ }
477
+ /**
478
+ * **Escape hatch.** The underlying shadow-DOM `<video>` element.
479
+ *
480
+ * Use for native browser APIs the wrapper doesn't expose:
481
+ * - `el.videoElement.requestPictureInPicture()`
482
+ * - `el.videoElement.audioTracks` (browser native, not avbridge's track list)
483
+ * - direct integration with libraries that need a real HTMLVideoElement
484
+ *
485
+ * **Caveat:** When the active strategy is `"fallback"` or `"hybrid"`,
486
+ * frames are rendered to a canvas overlay, not into this `<video>`.
487
+ * APIs that depend on the actual pixels (Picture-in-Picture, captureStream)
488
+ * will not show the playing content in those modes. Check `el.strategy`
489
+ * before using such APIs.
490
+ */
491
+ get videoElement() {
492
+ return this._videoEl;
493
+ }
353
494
  get strategy() {
354
495
  return this._strategy;
355
496
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/element/avbridge-player.ts","../src/element.ts"],"names":[],"mappings":"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","file":"element.js","sourcesContent":["/**\n * `<avbridge-player>` — reference web component for the avbridge engine.\n *\n * This is a *thin* wrapper around `createPlayer()`. Its purpose is to:\n *\n * 1. Validate the public API by being a real consumer of it.\n * 2. Drive lifecycle correctness in the core via adversarial integration tests.\n * 3. Provide a drop-in player for users who don't want to wire `createPlayer()`\n * themselves.\n *\n * It is **not** a player UI framework. See `docs/dev/WEB_COMPONENT_SPEC.md`\n * for the full spec, lifecycle invariants, and edge case list.\n *\n * Phase A scope (this file): lifecycle scaffold only — `src` / `source` /\n * `currentTime` / `play` / `pause` / `load` / `destroy` / events. No built-in\n * controls. Shadow DOM contains a single `<video part=\"video\">`.\n */\n\nimport { createPlayer, type UnifiedPlayer } from \"../player.js\";\nimport type {\n MediaInput,\n StrategyName,\n StrategyClass,\n AudioTrackInfo,\n SubtitleTrackInfo,\n DiagnosticsSnapshot,\n} from \"../types.js\";\n\n/** Strategy preference passed via the `preferstrategy` attribute. */\ntype PreferredStrategy = \"auto\" | StrategyName;\n\nconst PREFERRED_STRATEGY_VALUES = new Set<PreferredStrategy>([\n \"auto\",\n \"native\",\n \"remux\",\n \"hybrid\",\n \"fallback\",\n]);\n\n/**\n * `HTMLElement` is a browser-only global. SSR frameworks (Next.js, Astro,\n * Remix, etc.) commonly import library modules on the server to extract\n * types or do tree-shaking, even if the user only ends up using them in\n * the browser. If we extended `HTMLElement` directly, the `class extends`\n * expression would be evaluated at module load time and crash in Node.\n *\n * The fix: in non-browser environments, fall back to an empty stub class.\n * The element is never *constructed* server-side (the registration in\n * `element.ts` is guarded by `typeof customElements !== \"undefined\"`), so\n * the stub is never instantiated — it just lets the class declaration\n * evaluate cleanly so the module can be imported anywhere.\n */\nconst HTMLElementCtor: typeof HTMLElement =\n typeof HTMLElement !== \"undefined\"\n ? HTMLElement\n : (class {} as unknown as typeof HTMLElement);\n\n/**\n * Custom element. Lifecycle correctness is enforced via a monotonically\n * increasing `_bootstrapId`: every async bootstrap captures the ID at start\n * and discards itself if the ID has changed by the time it resolves. This\n * single pattern handles disconnect-during-bootstrap, rapid src reassignment,\n * bootstrap races, and destroy-during-bootstrap.\n */\nexport class AvbridgePlayerElement extends HTMLElementCtor {\n static readonly observedAttributes = [\n \"src\",\n \"autoplay\",\n \"muted\",\n \"loop\",\n \"preload\",\n \"diagnostics\",\n \"preferstrategy\",\n ];\n\n // ── Internal state ─────────────────────────────────────────────────────\n\n /** The shadow DOM `<video>` element that strategies render into. */\n private _videoEl!: HTMLVideoElement;\n\n /** Active player session, if any. Cleared on teardown. */\n private _player: UnifiedPlayer | null = null;\n\n /**\n * Monotonic counter incremented on every (re)bootstrap. Async bootstrap\n * work captures the current ID; if it doesn't match by the time the work\n * resolves, the work is discarded.\n */\n private _bootstrapId = 0;\n\n /** True after destroy() — element is permanently unusable. */\n private _destroyed = false;\n\n /** Internal source state. Either string-form (src) OR rich (source). */\n private _src: string | null = null;\n private _source: MediaInput | null = null;\n\n /**\n * Set when the `source` property setter is in the middle of clearing the\n * `src` attribute as part of mutual exclusion. The attributeChangedCallback\n * checks this flag and skips its normal \"clear source\" side effect, which\n * would otherwise wipe the value we just set.\n */\n private _suppressSrcAttrCallback = false;\n\n /** Last-known runtime state surfaced via getters. */\n private _strategy: StrategyName | null = null;\n private _strategyClass: StrategyClass | null = null;\n private _audioTracks: AudioTrackInfo[] = [];\n private _subtitleTracks: SubtitleTrackInfo[] = [];\n\n /** Strategy preference (does not currently affect routing — reserved). */\n private _preferredStrategy: PreferredStrategy = \"auto\";\n\n /** Set if currentTime was assigned before the player was ready. */\n private _pendingSeek: number | null = null;\n /** Set if play() was called before the player was ready. */\n private _pendingPlay = false;\n\n // ── Construction & lifecycle ───────────────────────────────────────────\n\n constructor() {\n super();\n const root = this.attachShadow({ mode: \"open\" });\n this._videoEl = document.createElement(\"video\");\n this._videoEl.setAttribute(\"part\", \"video\");\n this._videoEl.style.cssText = \"width:100%;height:100%;display:block;background:#000;\";\n this._videoEl.playsInline = true;\n root.appendChild(this._videoEl);\n\n // Forward the underlying <video>'s `progress` event so consumers can\n // observe buffered-range updates without reaching into the shadow DOM.\n // This works for native + remux (real video element with buffered\n // ranges) and is a no-op for hybrid/fallback (canvas-rendered, no\n // buffered ranges yet).\n this._videoEl.addEventListener(\"progress\", () => {\n if (this._destroyed) return;\n this._dispatch(\"progress\", { buffered: this._videoEl.buffered });\n });\n }\n\n connectedCallback(): void {\n if (this._destroyed) return;\n // Connection is the trigger for bootstrap. If we have a pending source\n // (set before connect), kick off bootstrap now.\n const source = this._activeSource();\n if (source != null) {\n void this._bootstrap(source);\n }\n }\n\n disconnectedCallback(): void {\n if (this._destroyed) return;\n // Bump the bootstrap token so any in-flight async work is invalidated\n // before we tear down. _teardown() also bumps but we want the bump to\n // happen synchronously here so any awaited promise that resolves\n // between `disconnect` and `_teardown` sees the new ID.\n this._bootstrapId++;\n void this._teardown();\n }\n\n attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {\n if (this._destroyed) return;\n switch (name) {\n case \"src\":\n if (this._suppressSrcAttrCallback) break;\n this._setSrcInternal(newValue);\n break;\n case \"autoplay\":\n case \"muted\":\n case \"loop\":\n // Reflect onto the underlying <video> element.\n if (newValue == null) this._videoEl.removeAttribute(name);\n else this._videoEl.setAttribute(name, newValue);\n break;\n case \"preload\":\n if (newValue == null) this._videoEl.removeAttribute(\"preload\");\n else this._videoEl.setAttribute(\"preload\", newValue);\n break;\n case \"diagnostics\":\n // Phase A: no UI. Property is observable for users via getDiagnostics().\n break;\n case \"preferstrategy\":\n if (newValue && PREFERRED_STRATEGY_VALUES.has(newValue as PreferredStrategy)) {\n this._preferredStrategy = newValue as PreferredStrategy;\n } else {\n this._preferredStrategy = \"auto\";\n }\n break;\n }\n }\n\n // ── Source handling ────────────────────────────────────────────────────\n\n /** Returns the currently-active source (src or source), whichever is set. */\n private _activeSource(): MediaInput | null {\n if (this._source != null) return this._source;\n if (this._src != null) return this._src;\n return null;\n }\n\n /** Internal src setter — separate from the property setter so the\n * attributeChangedCallback can use it without re-entering reflection. */\n private _setSrcInternal(value: string | null): void {\n // Same-value reassignment: no-op (#11 in the lifecycle list).\n if (value === this._src && this._source == null) return;\n this._src = value;\n this._source = null;\n this._onSourceChanged();\n }\n\n /** Called whenever the active source changes (src or source). */\n private _onSourceChanged(): void {\n if (this._destroyed) return;\n const source = this._activeSource();\n if (source == null) {\n // Null transition: tear down and stay idle.\n this._bootstrapId++;\n void this._teardown();\n return;\n }\n // Only bootstrap if we're connected to the DOM.\n if (this.isConnected) {\n void this._bootstrap(source);\n }\n }\n\n // ── Bootstrap (the only place a UnifiedPlayer is created) ──────────────\n\n private async _bootstrap(source: MediaInput): Promise<void> {\n if (this._destroyed) return;\n const id = ++this._bootstrapId;\n\n // Tear down any existing player before starting a new one. Pass the\n // bootstrap id we just claimed so teardown doesn't bump it again\n // (which would invalidate ourselves).\n await this._teardown(id);\n if (id !== this._bootstrapId || this._destroyed) return;\n\n this._dispatch(\"loadstart\", {});\n\n let player: UnifiedPlayer;\n try {\n player = await createPlayer({\n source,\n target: this._videoEl,\n });\n } catch (err) {\n // Stale or destroyed — silently abandon.\n if (id !== this._bootstrapId || this._destroyed) return;\n this._dispatchError(err);\n return;\n }\n\n // Race check: if anything happened during the await above, bail.\n if (id !== this._bootstrapId || this._destroyed || !this.isConnected) {\n try { await player.destroy(); } catch { /* ignore */ }\n return;\n }\n\n this._player = player;\n\n // Wire events. The unsubscribe handles are not stored individually\n // because destroy() will tear down the whole session anyway.\n player.on(\"strategy\", ({ strategy, reason }) => {\n // strategy event fires on initial classification AND any escalation.\n const cls = player.getDiagnostics().strategyClass;\n this._strategy = strategy;\n this._strategyClass = cls === \"pending\" ? null : cls;\n this._dispatch(\"strategychange\", {\n strategy,\n strategyClass: this._strategyClass,\n reason,\n diagnostics: player.getDiagnostics(),\n });\n });\n\n player.on(\"strategychange\", ({ from, to, reason, currentTime }) => {\n this._dispatch(\"strategychange\", {\n from,\n strategy: to,\n strategyClass: player.getDiagnostics().strategyClass === \"pending\" ? null : player.getDiagnostics().strategyClass,\n reason,\n currentTime,\n diagnostics: player.getDiagnostics(),\n });\n });\n\n player.on(\"tracks\", ({ video: _v, audio, subtitle }) => {\n this._audioTracks = audio;\n this._subtitleTracks = subtitle;\n this._dispatch(\"trackschange\", {\n audioTracks: audio,\n subtitleTracks: subtitle,\n });\n });\n\n player.on(\"error\", (err: Error) => {\n this._dispatchError(err);\n });\n\n player.on(\"timeupdate\", ({ currentTime }) => {\n this._dispatch(\"timeupdate\", { currentTime });\n });\n\n player.on(\"ended\", () => {\n this._dispatch(\"ended\", {});\n });\n\n player.on(\"ready\", () => {\n this._dispatch(\"ready\", { diagnostics: player.getDiagnostics() });\n // Apply any pending seek that was set before the player existed.\n if (this._pendingSeek != null) {\n const t = this._pendingSeek;\n this._pendingSeek = null;\n void player.seek(t).catch(() => { /* ignore */ });\n }\n // Honor any pending play() that was queued before bootstrap finished.\n if (this._pendingPlay) {\n this._pendingPlay = false;\n void player.play().catch(() => { /* ignore — autoplay may be blocked */ });\n } else if (this.autoplay) {\n void player.play().catch(() => { /* ignore */ });\n }\n });\n }\n\n /**\n * Tear down the active player and reset runtime state. Idempotent.\n * If `currentBootstrapId` is provided, the bootstrap counter is NOT\n * incremented (used by `_bootstrap()` to avoid invalidating itself).\n */\n private async _teardown(currentBootstrapId?: number): Promise<void> {\n if (currentBootstrapId == null) {\n // External callers (disconnect, destroy, source change) should bump\n // the counter so any in-flight bootstrap is invalidated. The internal\n // _bootstrap() call passes its own ID and we skip the bump.\n this._bootstrapId++;\n }\n const player = this._player;\n this._player = null;\n this._strategy = null;\n this._strategyClass = null;\n this._audioTracks = [];\n this._subtitleTracks = [];\n if (player) {\n try { await player.destroy(); } catch { /* ignore */ }\n }\n }\n\n // ── Public properties ──────────────────────────────────────────────────\n\n get src(): string | null {\n return this._src;\n }\n\n set src(value: string | null) {\n if (value == null) {\n this.removeAttribute(\"src\");\n } else {\n this.setAttribute(\"src\", value);\n }\n // attributeChangedCallback handles the rest.\n }\n\n get source(): MediaInput | null {\n return this._source;\n }\n\n set source(value: MediaInput | null) {\n // Same-value reassignment for rich values is identity-based.\n if (value === this._source && this._src == null) return;\n this._source = value;\n if (value != null) {\n // Setting source clears src. Suppress the attribute callback so\n // removing the src attribute doesn't wipe the source we just set.\n this._src = null;\n if (this.hasAttribute(\"src\")) {\n this._suppressSrcAttrCallback = true;\n try {\n this.removeAttribute(\"src\");\n } finally {\n this._suppressSrcAttrCallback = false;\n }\n }\n }\n this._onSourceChanged();\n }\n\n get autoplay(): boolean {\n return this.hasAttribute(\"autoplay\");\n }\n\n set autoplay(value: boolean) {\n if (value) this.setAttribute(\"autoplay\", \"\");\n else this.removeAttribute(\"autoplay\");\n }\n\n get muted(): boolean {\n return this.hasAttribute(\"muted\");\n }\n\n set muted(value: boolean) {\n if (value) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop(): boolean {\n return this.hasAttribute(\"loop\");\n }\n\n set loop(value: boolean) {\n if (value) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n get preload(): \"none\" | \"metadata\" | \"auto\" {\n const v = this.getAttribute(\"preload\");\n return v === \"none\" || v === \"metadata\" || v === \"auto\" ? v : \"auto\";\n }\n\n set preload(value: \"none\" | \"metadata\" | \"auto\") {\n this.setAttribute(\"preload\", value);\n }\n\n get diagnostics(): boolean {\n return this.hasAttribute(\"diagnostics\");\n }\n\n set diagnostics(value: boolean) {\n if (value) this.setAttribute(\"diagnostics\", \"\");\n else this.removeAttribute(\"diagnostics\");\n }\n\n get preferredStrategy(): PreferredStrategy {\n return this._preferredStrategy;\n }\n\n set preferredStrategy(value: PreferredStrategy) {\n if (PREFERRED_STRATEGY_VALUES.has(value)) {\n this.setAttribute(\"preferstrategy\", value);\n }\n }\n\n get currentTime(): number {\n return this._player?.getCurrentTime() ?? 0;\n }\n\n set currentTime(value: number) {\n if (this._player) {\n void this._player.seek(value).catch(() => { /* ignore */ });\n } else {\n // Defer to the next bootstrap. The `ready` handler applies it.\n this._pendingSeek = value;\n }\n }\n\n get duration(): number {\n return this._player?.getDuration() ?? NaN;\n }\n\n get paused(): boolean {\n return this._videoEl.paused;\n }\n\n get ended(): boolean {\n return this._videoEl.ended;\n }\n\n get readyState(): number {\n return this._videoEl.readyState;\n }\n\n /**\n * Buffered time ranges for the active source. Mirrors the standard\n * `<video>.buffered` `TimeRanges` API. For the native and remux strategies\n * this reflects the underlying SourceBuffer / progressive download state.\n * For the hybrid and fallback (canvas-rendered) strategies it currently\n * returns an empty TimeRanges; v1.1 will synthesize a coarse range from\n * the decoder's read position.\n */\n get buffered(): TimeRanges {\n return this._videoEl.buffered;\n }\n\n get strategy(): StrategyName | null {\n return this._strategy;\n }\n\n get strategyClass(): StrategyClass | null {\n return this._strategyClass;\n }\n\n get player(): UnifiedPlayer | null {\n return this._player;\n }\n\n get audioTracks(): AudioTrackInfo[] {\n return this._audioTracks;\n }\n\n get subtitleTracks(): SubtitleTrackInfo[] {\n return this._subtitleTracks;\n }\n\n // ── Public methods ─────────────────────────────────────────────────────\n\n /** Force a (re-)bootstrap if a source is currently set. */\n async load(): Promise<void> {\n if (this._destroyed) return;\n const source = this._activeSource();\n if (source == null) return;\n await this._bootstrap(source);\n }\n\n /**\n * Begin or resume playback. If the player isn't ready yet, the call is\n * queued and applied once `ready` fires.\n */\n async play(): Promise<void> {\n if (this._destroyed) return;\n if (this._player) {\n await this._player.play();\n } else {\n this._pendingPlay = true;\n }\n }\n\n pause(): void {\n if (this._destroyed) return;\n this._pendingPlay = false;\n this._player?.pause();\n }\n\n /**\n * Tear down the element permanently. After destroy(), the element ignores\n * all method calls and attribute changes.\n */\n async destroy(): Promise<void> {\n if (this._destroyed) return;\n this._destroyed = true;\n await this._teardown();\n this._dispatch(\"destroy\", {});\n }\n\n async setAudioTrack(id: number): Promise<void> {\n if (this._destroyed || !this._player) return;\n await this._player.setAudioTrack(id);\n }\n\n async setSubtitleTrack(id: number | null): Promise<void> {\n if (this._destroyed || !this._player) return;\n await this._player.setSubtitleTrack(id);\n }\n\n getDiagnostics(): DiagnosticsSnapshot | null {\n return this._player?.getDiagnostics() ?? null;\n }\n\n // ── Event helpers ──────────────────────────────────────────────────────\n\n private _dispatch<T>(name: string, detail: T): void {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: false }));\n }\n\n private _dispatchError(err: unknown): void {\n const error = err instanceof Error ? err : new Error(String(err));\n this._dispatch(\"error\", { error, diagnostics: this._player?.getDiagnostics() ?? null });\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"avbridge-player\": AvbridgePlayerElement;\n }\n}\n","/**\n * Subpath entry: `import \"avbridge/element\"` registers the\n * `<avbridge-player>` custom element.\n *\n * This is a separate entry point from the core (`avbridge`) so that consumers\n * who only want the engine don't pay for the element code, and consumers who\n * want both pay for the element code exactly once.\n *\n * The registration is guarded so re-importing this module (e.g. via HMR or\n * multiple bundles) does not throw a \"name already defined\" error.\n */\n\nimport { AvbridgePlayerElement } from \"./element/avbridge-player.js\";\n\nexport { AvbridgePlayerElement };\n\nif (typeof customElements !== \"undefined\" && !customElements.get(\"avbridge-player\")) {\n customElements.define(\"avbridge-player\", AvbridgePlayerElement);\n}\n"]}
1
+ {"version":3,"sources":["../src/element/avbridge-player.ts","../src/element.ts"],"names":[],"mappings":"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","file":"element.js","sourcesContent":["/**\n * `<avbridge-player>` — reference web component for the avbridge engine.\n *\n * This is a *thin* wrapper around `createPlayer()`. Its purpose is to:\n *\n * 1. Validate the public API by being a real consumer of it.\n * 2. Drive lifecycle correctness in the core via adversarial integration tests.\n * 3. Provide a drop-in player for users who don't want to wire `createPlayer()`\n * themselves.\n *\n * It is **not** a player UI framework. See `docs/dev/WEB_COMPONENT_SPEC.md`\n * for the full spec, lifecycle invariants, and edge case list.\n *\n * Phase A scope (this file): lifecycle scaffold only — `src` / `source` /\n * `currentTime` / `play` / `pause` / `load` / `destroy` / events. No built-in\n * controls. Shadow DOM contains a single `<video part=\"video\">`.\n */\n\nimport { createPlayer, type UnifiedPlayer } from \"../player.js\";\nimport type {\n MediaInput,\n StrategyName,\n StrategyClass,\n AudioTrackInfo,\n SubtitleTrackInfo,\n DiagnosticsSnapshot,\n} from \"../types.js\";\n\n/** Strategy preference passed via the `preferstrategy` attribute. */\ntype PreferredStrategy = \"auto\" | StrategyName;\n\nconst PREFERRED_STRATEGY_VALUES = new Set<PreferredStrategy>([\n \"auto\",\n \"native\",\n \"remux\",\n \"hybrid\",\n \"fallback\",\n]);\n\n/**\n * Standard `HTMLMediaElement` events we forward from the inner `<video>`\n * to the wrapper element so consumers can `el.addEventListener(\"loadedmetadata\", ...)`\n * exactly like they would with a real `<video>`. The element also dispatches\n * its own custom events (`strategychange`, `ready`, `error`, etc.) — those\n * are NOT in this list because they're avbridge-specific.\n *\n * Note: `progress` and `timeupdate` are deliberately NOT forwarded here.\n * `progress` is dispatched by the constructor with our own `{ buffered }`\n * detail. `timeupdate` is dispatched by the player layer (so it works for\n * canvas-rendered fallback playback too, where the inner <video> never\n * fires its own timeupdate).\n */\nconst FORWARDED_VIDEO_EVENTS = [\n \"loadstart\",\n \"loadedmetadata\",\n \"loadeddata\",\n \"canplay\",\n \"canplaythrough\",\n \"play\",\n \"playing\",\n \"pause\",\n \"seeking\",\n \"seeked\",\n \"volumechange\",\n \"ratechange\",\n \"durationchange\",\n \"waiting\",\n \"stalled\",\n \"emptied\",\n \"resize\",\n \"error\",\n] as const;\n\n/**\n * `HTMLElement` is a browser-only global. SSR frameworks (Next.js, Astro,\n * Remix, etc.) commonly import library modules on the server to extract\n * types or do tree-shaking, even if the user only ends up using them in\n * the browser. If we extended `HTMLElement` directly, the `class extends`\n * expression would be evaluated at module load time and crash in Node.\n *\n * The fix: in non-browser environments, fall back to an empty stub class.\n * The element is never *constructed* server-side (the registration in\n * `element.ts` is guarded by `typeof customElements !== \"undefined\"`), so\n * the stub is never instantiated — it just lets the class declaration\n * evaluate cleanly so the module can be imported anywhere.\n */\nconst HTMLElementCtor: typeof HTMLElement =\n typeof HTMLElement !== \"undefined\"\n ? HTMLElement\n : (class {} as unknown as typeof HTMLElement);\n\n/**\n * Custom element. Lifecycle correctness is enforced via a monotonically\n * increasing `_bootstrapId`: every async bootstrap captures the ID at start\n * and discards itself if the ID has changed by the time it resolves. This\n * single pattern handles disconnect-during-bootstrap, rapid src reassignment,\n * bootstrap races, and destroy-during-bootstrap.\n */\nexport class AvbridgePlayerElement extends HTMLElementCtor {\n static readonly observedAttributes = [\n \"src\",\n \"autoplay\",\n \"muted\",\n \"loop\",\n \"preload\",\n \"poster\",\n \"playsinline\",\n \"crossorigin\",\n \"disableremoteplayback\",\n \"diagnostics\",\n \"preferstrategy\",\n ];\n\n // ── Internal state ─────────────────────────────────────────────────────\n\n /** The shadow DOM `<video>` element that strategies render into. */\n private _videoEl!: HTMLVideoElement;\n\n /** Active player session, if any. Cleared on teardown. */\n private _player: UnifiedPlayer | null = null;\n\n /**\n * Monotonic counter incremented on every (re)bootstrap. Async bootstrap\n * work captures the current ID; if it doesn't match by the time the work\n * resolves, the work is discarded.\n */\n private _bootstrapId = 0;\n\n /** True after destroy() — element is permanently unusable. */\n private _destroyed = false;\n\n /** Internal source state. Either string-form (src) OR rich (source). */\n private _src: string | null = null;\n private _source: MediaInput | null = null;\n\n /**\n * Set when the `source` property setter is in the middle of clearing the\n * `src` attribute as part of mutual exclusion. The attributeChangedCallback\n * checks this flag and skips its normal \"clear source\" side effect, which\n * would otherwise wipe the value we just set.\n */\n private _suppressSrcAttrCallback = false;\n\n /** Last-known runtime state surfaced via getters. */\n private _strategy: StrategyName | null = null;\n private _strategyClass: StrategyClass | null = null;\n private _audioTracks: AudioTrackInfo[] = [];\n private _subtitleTracks: SubtitleTrackInfo[] = [];\n\n /** Strategy preference (does not currently affect routing — reserved). */\n private _preferredStrategy: PreferredStrategy = \"auto\";\n\n /** Set if currentTime was assigned before the player was ready. */\n private _pendingSeek: number | null = null;\n /** Set if play() was called before the player was ready. */\n private _pendingPlay = false;\n\n /** MutationObserver tracking light-DOM `<track>` children. */\n private _trackObserver: MutationObserver | null = null;\n\n // ── Construction & lifecycle ───────────────────────────────────────────\n\n constructor() {\n super();\n const root = this.attachShadow({ mode: \"open\" });\n this._videoEl = document.createElement(\"video\");\n this._videoEl.setAttribute(\"part\", \"video\");\n this._videoEl.style.cssText = \"width:100%;height:100%;display:block;background:#000;\";\n this._videoEl.playsInline = true;\n root.appendChild(this._videoEl);\n\n // Forward the underlying <video>'s `progress` event so consumers can\n // observe buffered-range updates without reaching into the shadow DOM.\n // This works for native + remux (real video element with buffered\n // ranges) and is a no-op for hybrid/fallback (canvas-rendered, no\n // buffered ranges yet).\n this._videoEl.addEventListener(\"progress\", () => {\n if (this._destroyed) return;\n this._dispatch(\"progress\", { buffered: this._videoEl.buffered });\n });\n\n // Forward all standard HTMLMediaElement events from the inner <video>\n // so consumers can use the element as a drop-in <video> replacement.\n // Each event is re-dispatched on the wrapper element with no detail —\n // listeners that need state should read it from the element directly.\n for (const eventName of FORWARDED_VIDEO_EVENTS) {\n this._videoEl.addEventListener(eventName, () => {\n if (this._destroyed) return;\n this.dispatchEvent(new Event(eventName, { bubbles: false }));\n });\n }\n }\n\n connectedCallback(): void {\n if (this._destroyed) return;\n // Pick up any <track> children that were declared in HTML before the\n // element upgraded, and watch for future additions/removals.\n this._syncTextTracks();\n if (!this._trackObserver) {\n this._trackObserver = new MutationObserver(() => this._syncTextTracks());\n this._trackObserver.observe(this, { childList: true, subtree: false });\n }\n // Connection is the trigger for bootstrap. If we have a pending source\n // (set before connect), kick off bootstrap now.\n const source = this._activeSource();\n if (source != null) {\n void this._bootstrap(source);\n }\n }\n\n disconnectedCallback(): void {\n if (this._destroyed) return;\n if (this._trackObserver) {\n this._trackObserver.disconnect();\n this._trackObserver = null;\n }\n // Bump the bootstrap token so any in-flight async work is invalidated\n // before we tear down. _teardown() also bumps but we want the bump to\n // happen synchronously here so any awaited promise that resolves\n // between `disconnect` and `_teardown` sees the new ID.\n this._bootstrapId++;\n void this._teardown();\n }\n\n attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void {\n if (this._destroyed) return;\n switch (name) {\n case \"src\":\n if (this._suppressSrcAttrCallback) break;\n this._setSrcInternal(newValue);\n break;\n case \"autoplay\":\n case \"muted\":\n case \"loop\":\n case \"playsinline\":\n case \"disableremoteplayback\":\n // Reflect onto the underlying <video> element.\n if (newValue == null) this._videoEl.removeAttribute(name);\n else this._videoEl.setAttribute(name, newValue);\n break;\n case \"preload\":\n case \"poster\":\n case \"crossorigin\":\n if (newValue == null) this._videoEl.removeAttribute(name);\n else this._videoEl.setAttribute(name, newValue);\n break;\n case \"diagnostics\":\n // Phase A: no UI. Property is observable for users via getDiagnostics().\n break;\n case \"preferstrategy\":\n if (newValue && PREFERRED_STRATEGY_VALUES.has(newValue as PreferredStrategy)) {\n this._preferredStrategy = newValue as PreferredStrategy;\n } else {\n this._preferredStrategy = \"auto\";\n }\n break;\n }\n }\n\n // ── Source handling ────────────────────────────────────────────────────\n\n /** Returns the currently-active source (src or source), whichever is set. */\n private _activeSource(): MediaInput | null {\n if (this._source != null) return this._source;\n if (this._src != null) return this._src;\n return null;\n }\n\n /**\n * Mirror light-DOM `<track>` children into the shadow `<video>` so that\n * the browser's native text-track machinery picks them up. Called on\n * connect, on every mutation of light-DOM children, and once after each\n * source change so newly-set tracks survive a fresh `<video>`.\n *\n * Strategy: clone the children. We don't move them because the user's\n * code may still hold references to the originals (e.g. to set `default`).\n * The shadow copies are throwaway — we wipe them on every sync.\n */\n private _syncTextTracks(): void {\n // Remove existing shadow tracks.\n const existing = this._videoEl.querySelectorAll(\"track\");\n for (const t of Array.from(existing)) t.remove();\n // Clone every <track> light-DOM child into the shadow video.\n for (const child of Array.from(this.children)) {\n if (child.tagName === \"TRACK\") {\n const clone = child.cloneNode(true) as HTMLTrackElement;\n this._videoEl.appendChild(clone);\n }\n }\n }\n\n /** Internal src setter — separate from the property setter so the\n * attributeChangedCallback can use it without re-entering reflection. */\n private _setSrcInternal(value: string | null): void {\n // Same-value reassignment: no-op (#11 in the lifecycle list).\n if (value === this._src && this._source == null) return;\n this._src = value;\n this._source = null;\n this._onSourceChanged();\n }\n\n /** Called whenever the active source changes (src or source). */\n private _onSourceChanged(): void {\n if (this._destroyed) return;\n const source = this._activeSource();\n if (source == null) {\n // Null transition: tear down and stay idle.\n this._bootstrapId++;\n void this._teardown();\n return;\n }\n // Only bootstrap if we're connected to the DOM.\n if (this.isConnected) {\n void this._bootstrap(source);\n }\n }\n\n // ── Bootstrap (the only place a UnifiedPlayer is created) ──────────────\n\n private async _bootstrap(source: MediaInput): Promise<void> {\n if (this._destroyed) return;\n const id = ++this._bootstrapId;\n\n // Tear down any existing player before starting a new one. Pass the\n // bootstrap id we just claimed so teardown doesn't bump it again\n // (which would invalidate ourselves).\n await this._teardown(id);\n if (id !== this._bootstrapId || this._destroyed) return;\n\n this._dispatch(\"loadstart\", {});\n\n let player: UnifiedPlayer;\n try {\n player = await createPlayer({\n source,\n target: this._videoEl,\n });\n } catch (err) {\n // Stale or destroyed — silently abandon.\n if (id !== this._bootstrapId || this._destroyed) return;\n this._dispatchError(err);\n return;\n }\n\n // Race check: if anything happened during the await above, bail.\n if (id !== this._bootstrapId || this._destroyed || !this.isConnected) {\n try { await player.destroy(); } catch { /* ignore */ }\n return;\n }\n\n this._player = player;\n\n // Resync any light-DOM <track> children into the (possibly fresh) shadow\n // <video>. Strategies that swap or reset the inner video state would\n // otherwise lose the tracks the user declared in HTML.\n this._syncTextTracks();\n\n // Wire events. The unsubscribe handles are not stored individually\n // because destroy() will tear down the whole session anyway.\n player.on(\"strategy\", ({ strategy, reason }) => {\n // strategy event fires on initial classification AND any escalation.\n const cls = player.getDiagnostics().strategyClass;\n this._strategy = strategy;\n this._strategyClass = cls === \"pending\" ? null : cls;\n this._dispatch(\"strategychange\", {\n strategy,\n strategyClass: this._strategyClass,\n reason,\n diagnostics: player.getDiagnostics(),\n });\n });\n\n player.on(\"strategychange\", ({ from, to, reason, currentTime }) => {\n this._dispatch(\"strategychange\", {\n from,\n strategy: to,\n strategyClass: player.getDiagnostics().strategyClass === \"pending\" ? null : player.getDiagnostics().strategyClass,\n reason,\n currentTime,\n diagnostics: player.getDiagnostics(),\n });\n });\n\n player.on(\"tracks\", ({ video: _v, audio, subtitle }) => {\n this._audioTracks = audio;\n this._subtitleTracks = subtitle;\n this._dispatch(\"trackschange\", {\n audioTracks: audio,\n subtitleTracks: subtitle,\n });\n });\n\n player.on(\"error\", (err: Error) => {\n this._dispatchError(err);\n });\n\n player.on(\"timeupdate\", ({ currentTime }) => {\n this._dispatch(\"timeupdate\", { currentTime });\n });\n\n player.on(\"ended\", () => {\n this._dispatch(\"ended\", {});\n });\n\n player.on(\"ready\", () => {\n this._dispatch(\"ready\", { diagnostics: player.getDiagnostics() });\n // Apply any pending seek that was set before the player existed.\n if (this._pendingSeek != null) {\n const t = this._pendingSeek;\n this._pendingSeek = null;\n void player.seek(t).catch(() => { /* ignore */ });\n }\n // Honor any pending play() that was queued before bootstrap finished.\n if (this._pendingPlay) {\n this._pendingPlay = false;\n void player.play().catch(() => { /* ignore — autoplay may be blocked */ });\n } else if (this.autoplay) {\n void player.play().catch(() => { /* ignore */ });\n }\n });\n }\n\n /**\n * Tear down the active player and reset runtime state. Idempotent.\n * If `currentBootstrapId` is provided, the bootstrap counter is NOT\n * incremented (used by `_bootstrap()` to avoid invalidating itself).\n */\n private async _teardown(currentBootstrapId?: number): Promise<void> {\n if (currentBootstrapId == null) {\n // External callers (disconnect, destroy, source change) should bump\n // the counter so any in-flight bootstrap is invalidated. The internal\n // _bootstrap() call passes its own ID and we skip the bump.\n this._bootstrapId++;\n }\n const player = this._player;\n this._player = null;\n this._strategy = null;\n this._strategyClass = null;\n this._audioTracks = [];\n this._subtitleTracks = [];\n if (player) {\n try { await player.destroy(); } catch { /* ignore */ }\n }\n }\n\n // ── Public properties ──────────────────────────────────────────────────\n\n get src(): string | null {\n return this._src;\n }\n\n set src(value: string | null) {\n if (value == null) {\n this.removeAttribute(\"src\");\n } else {\n this.setAttribute(\"src\", value);\n }\n // attributeChangedCallback handles the rest.\n }\n\n get source(): MediaInput | null {\n return this._source;\n }\n\n set source(value: MediaInput | null) {\n // Same-value reassignment for rich values is identity-based.\n if (value === this._source && this._src == null) return;\n this._source = value;\n if (value != null) {\n // Setting source clears src. Suppress the attribute callback so\n // removing the src attribute doesn't wipe the source we just set.\n this._src = null;\n if (this.hasAttribute(\"src\")) {\n this._suppressSrcAttrCallback = true;\n try {\n this.removeAttribute(\"src\");\n } finally {\n this._suppressSrcAttrCallback = false;\n }\n }\n }\n this._onSourceChanged();\n }\n\n get autoplay(): boolean {\n return this.hasAttribute(\"autoplay\");\n }\n\n set autoplay(value: boolean) {\n if (value) this.setAttribute(\"autoplay\", \"\");\n else this.removeAttribute(\"autoplay\");\n }\n\n get muted(): boolean {\n return this.hasAttribute(\"muted\");\n }\n\n set muted(value: boolean) {\n if (value) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get loop(): boolean {\n return this.hasAttribute(\"loop\");\n }\n\n set loop(value: boolean) {\n if (value) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n get preload(): \"none\" | \"metadata\" | \"auto\" {\n const v = this.getAttribute(\"preload\");\n return v === \"none\" || v === \"metadata\" || v === \"auto\" ? v : \"auto\";\n }\n\n set preload(value: \"none\" | \"metadata\" | \"auto\") {\n this.setAttribute(\"preload\", value);\n }\n\n get diagnostics(): boolean {\n return this.hasAttribute(\"diagnostics\");\n }\n\n set diagnostics(value: boolean) {\n if (value) this.setAttribute(\"diagnostics\", \"\");\n else this.removeAttribute(\"diagnostics\");\n }\n\n get preferredStrategy(): PreferredStrategy {\n return this._preferredStrategy;\n }\n\n set preferredStrategy(value: PreferredStrategy) {\n if (PREFERRED_STRATEGY_VALUES.has(value)) {\n this.setAttribute(\"preferstrategy\", value);\n }\n }\n\n get currentTime(): number {\n return this._player?.getCurrentTime() ?? 0;\n }\n\n set currentTime(value: number) {\n if (this._player) {\n void this._player.seek(value).catch(() => { /* ignore */ });\n } else {\n // Defer to the next bootstrap. The `ready` handler applies it.\n this._pendingSeek = value;\n }\n }\n\n get duration(): number {\n return this._player?.getDuration() ?? NaN;\n }\n\n get paused(): boolean {\n return this._videoEl.paused;\n }\n\n get ended(): boolean {\n return this._videoEl.ended;\n }\n\n get readyState(): number {\n return this._videoEl.readyState;\n }\n\n /**\n * Buffered time ranges for the active source. Mirrors the standard\n * `<video>.buffered` `TimeRanges` API. For the native and remux strategies\n * this reflects the underlying SourceBuffer / progressive download state.\n * For the hybrid and fallback (canvas-rendered) strategies it currently\n * returns an empty TimeRanges; a future release will synthesize a coarse\n * range from the decoder's read position.\n */\n get buffered(): TimeRanges {\n return this._videoEl.buffered;\n }\n\n // ── HTMLMediaElement parity ───────────────────────────────────────────\n // Mirror the standard <video> surface so consumers can drop the element\n // in as a <video> replacement. Each property is a thin passthrough to the\n // shadow `<video>`.\n\n get poster(): string {\n return this._videoEl.poster;\n }\n set poster(value: string) {\n if (value == null || value === \"\") this.removeAttribute(\"poster\");\n else this.setAttribute(\"poster\", value);\n }\n\n get volume(): number {\n return this._videoEl.volume;\n }\n set volume(value: number) {\n this._videoEl.volume = value;\n }\n\n get playbackRate(): number {\n return this._videoEl.playbackRate;\n }\n set playbackRate(value: number) {\n this._videoEl.playbackRate = value;\n }\n\n get videoWidth(): number {\n return this._videoEl.videoWidth;\n }\n\n get videoHeight(): number {\n return this._videoEl.videoHeight;\n }\n\n get played(): TimeRanges {\n return this._videoEl.played;\n }\n\n get seekable(): TimeRanges {\n return this._videoEl.seekable;\n }\n\n get crossOrigin(): string | null {\n return this._videoEl.crossOrigin;\n }\n set crossOrigin(value: string | null) {\n if (value == null) this.removeAttribute(\"crossorigin\");\n else this.setAttribute(\"crossorigin\", value);\n }\n\n get disableRemotePlayback(): boolean {\n return this._videoEl.disableRemotePlayback;\n }\n set disableRemotePlayback(value: boolean) {\n if (value) this.setAttribute(\"disableremoteplayback\", \"\");\n else this.removeAttribute(\"disableremoteplayback\");\n }\n\n /**\n * Native `HTMLMediaElement.canPlayType()` passthrough. Note that this\n * answers about the *browser's* native support, not avbridge's full\n * capabilities — avbridge can play many formats this method returns \"\"\n * for, by routing them to the remux/hybrid/fallback strategies.\n */\n canPlayType(mimeType: string): CanPlayTypeResult {\n return this._videoEl.canPlayType(mimeType);\n }\n\n /**\n * **Escape hatch.** The underlying shadow-DOM `<video>` element.\n *\n * Use for native browser APIs the wrapper doesn't expose:\n * - `el.videoElement.requestPictureInPicture()`\n * - `el.videoElement.audioTracks` (browser native, not avbridge's track list)\n * - direct integration with libraries that need a real HTMLVideoElement\n *\n * **Caveat:** When the active strategy is `\"fallback\"` or `\"hybrid\"`,\n * frames are rendered to a canvas overlay, not into this `<video>`.\n * APIs that depend on the actual pixels (Picture-in-Picture, captureStream)\n * will not show the playing content in those modes. Check `el.strategy`\n * before using such APIs.\n */\n get videoElement(): HTMLVideoElement {\n return this._videoEl;\n }\n\n get strategy(): StrategyName | null {\n return this._strategy;\n }\n\n get strategyClass(): StrategyClass | null {\n return this._strategyClass;\n }\n\n get player(): UnifiedPlayer | null {\n return this._player;\n }\n\n get audioTracks(): AudioTrackInfo[] {\n return this._audioTracks;\n }\n\n get subtitleTracks(): SubtitleTrackInfo[] {\n return this._subtitleTracks;\n }\n\n // ── Public methods ─────────────────────────────────────────────────────\n\n /** Force a (re-)bootstrap if a source is currently set. */\n async load(): Promise<void> {\n if (this._destroyed) return;\n const source = this._activeSource();\n if (source == null) return;\n await this._bootstrap(source);\n }\n\n /**\n * Begin or resume playback. If the player isn't ready yet, the call is\n * queued and applied once `ready` fires.\n */\n async play(): Promise<void> {\n if (this._destroyed) return;\n if (this._player) {\n await this._player.play();\n } else {\n this._pendingPlay = true;\n }\n }\n\n pause(): void {\n if (this._destroyed) return;\n this._pendingPlay = false;\n this._player?.pause();\n }\n\n /**\n * Tear down the element permanently. After destroy(), the element ignores\n * all method calls and attribute changes.\n */\n async destroy(): Promise<void> {\n if (this._destroyed) return;\n this._destroyed = true;\n await this._teardown();\n this._dispatch(\"destroy\", {});\n }\n\n async setAudioTrack(id: number): Promise<void> {\n if (this._destroyed || !this._player) return;\n await this._player.setAudioTrack(id);\n }\n\n async setSubtitleTrack(id: number | null): Promise<void> {\n if (this._destroyed || !this._player) return;\n await this._player.setSubtitleTrack(id);\n }\n\n getDiagnostics(): DiagnosticsSnapshot | null {\n return this._player?.getDiagnostics() ?? null;\n }\n\n // ── Event helpers ──────────────────────────────────────────────────────\n\n private _dispatch<T>(name: string, detail: T): void {\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: false }));\n }\n\n private _dispatchError(err: unknown): void {\n const error = err instanceof Error ? err : new Error(String(err));\n this._dispatch(\"error\", { error, diagnostics: this._player?.getDiagnostics() ?? null });\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"avbridge-player\": AvbridgePlayerElement;\n }\n}\n","/**\n * Subpath entry: `import \"avbridge/element\"` registers the\n * `<avbridge-player>` custom element.\n *\n * This is a separate entry point from the core (`avbridge`) so that consumers\n * who only want the engine don't pay for the element code, and consumers who\n * want both pay for the element code exactly once.\n *\n * The registration is guarded so re-importing this module (e.g. via HMR or\n * multiple bundles) does not throw a \"name already defined\" error.\n */\n\nimport { AvbridgePlayerElement } from \"./element/avbridge-player.js\";\n\nexport { AvbridgePlayerElement };\n\nif (typeof customElements !== \"undefined\" && !customElements.get(\"avbridge-player\")) {\n customElements.define(\"avbridge-player\", AvbridgePlayerElement);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avbridge",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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",
@@ -37,6 +37,40 @@ const PREFERRED_STRATEGY_VALUES = new Set<PreferredStrategy>([
37
37
  "fallback",
38
38
  ]);
39
39
 
40
+ /**
41
+ * Standard `HTMLMediaElement` events we forward from the inner `<video>`
42
+ * to the wrapper element so consumers can `el.addEventListener("loadedmetadata", ...)`
43
+ * exactly like they would with a real `<video>`. The element also dispatches
44
+ * its own custom events (`strategychange`, `ready`, `error`, etc.) — those
45
+ * are NOT in this list because they're avbridge-specific.
46
+ *
47
+ * Note: `progress` and `timeupdate` are deliberately NOT forwarded here.
48
+ * `progress` is dispatched by the constructor with our own `{ buffered }`
49
+ * detail. `timeupdate` is dispatched by the player layer (so it works for
50
+ * canvas-rendered fallback playback too, where the inner <video> never
51
+ * fires its own timeupdate).
52
+ */
53
+ const FORWARDED_VIDEO_EVENTS = [
54
+ "loadstart",
55
+ "loadedmetadata",
56
+ "loadeddata",
57
+ "canplay",
58
+ "canplaythrough",
59
+ "play",
60
+ "playing",
61
+ "pause",
62
+ "seeking",
63
+ "seeked",
64
+ "volumechange",
65
+ "ratechange",
66
+ "durationchange",
67
+ "waiting",
68
+ "stalled",
69
+ "emptied",
70
+ "resize",
71
+ "error",
72
+ ] as const;
73
+
40
74
  /**
41
75
  * `HTMLElement` is a browser-only global. SSR frameworks (Next.js, Astro,
42
76
  * Remix, etc.) commonly import library modules on the server to extract
@@ -69,6 +103,10 @@ export class AvbridgePlayerElement extends HTMLElementCtor {
69
103
  "muted",
70
104
  "loop",
71
105
  "preload",
106
+ "poster",
107
+ "playsinline",
108
+ "crossorigin",
109
+ "disableremoteplayback",
72
110
  "diagnostics",
73
111
  "preferstrategy",
74
112
  ];
@@ -117,6 +155,9 @@ export class AvbridgePlayerElement extends HTMLElementCtor {
117
155
  /** Set if play() was called before the player was ready. */
118
156
  private _pendingPlay = false;
119
157
 
158
+ /** MutationObserver tracking light-DOM `<track>` children. */
159
+ private _trackObserver: MutationObserver | null = null;
160
+
120
161
  // ── Construction & lifecycle ───────────────────────────────────────────
121
162
 
122
163
  constructor() {
@@ -137,10 +178,28 @@ export class AvbridgePlayerElement extends HTMLElementCtor {
137
178
  if (this._destroyed) return;
138
179
  this._dispatch("progress", { buffered: this._videoEl.buffered });
139
180
  });
181
+
182
+ // Forward all standard HTMLMediaElement events from the inner <video>
183
+ // so consumers can use the element as a drop-in <video> replacement.
184
+ // Each event is re-dispatched on the wrapper element with no detail —
185
+ // listeners that need state should read it from the element directly.
186
+ for (const eventName of FORWARDED_VIDEO_EVENTS) {
187
+ this._videoEl.addEventListener(eventName, () => {
188
+ if (this._destroyed) return;
189
+ this.dispatchEvent(new Event(eventName, { bubbles: false }));
190
+ });
191
+ }
140
192
  }
141
193
 
142
194
  connectedCallback(): void {
143
195
  if (this._destroyed) return;
196
+ // Pick up any <track> children that were declared in HTML before the
197
+ // element upgraded, and watch for future additions/removals.
198
+ this._syncTextTracks();
199
+ if (!this._trackObserver) {
200
+ this._trackObserver = new MutationObserver(() => this._syncTextTracks());
201
+ this._trackObserver.observe(this, { childList: true, subtree: false });
202
+ }
144
203
  // Connection is the trigger for bootstrap. If we have a pending source
145
204
  // (set before connect), kick off bootstrap now.
146
205
  const source = this._activeSource();
@@ -151,6 +210,10 @@ export class AvbridgePlayerElement extends HTMLElementCtor {
151
210
 
152
211
  disconnectedCallback(): void {
153
212
  if (this._destroyed) return;
213
+ if (this._trackObserver) {
214
+ this._trackObserver.disconnect();
215
+ this._trackObserver = null;
216
+ }
154
217
  // Bump the bootstrap token so any in-flight async work is invalidated
155
218
  // before we tear down. _teardown() also bumps but we want the bump to
156
219
  // happen synchronously here so any awaited promise that resolves
@@ -169,13 +232,17 @@ export class AvbridgePlayerElement extends HTMLElementCtor {
169
232
  case "autoplay":
170
233
  case "muted":
171
234
  case "loop":
235
+ case "playsinline":
236
+ case "disableremoteplayback":
172
237
  // Reflect onto the underlying <video> element.
173
238
  if (newValue == null) this._videoEl.removeAttribute(name);
174
239
  else this._videoEl.setAttribute(name, newValue);
175
240
  break;
176
241
  case "preload":
177
- if (newValue == null) this._videoEl.removeAttribute("preload");
178
- else this._videoEl.setAttribute("preload", newValue);
242
+ case "poster":
243
+ case "crossorigin":
244
+ if (newValue == null) this._videoEl.removeAttribute(name);
245
+ else this._videoEl.setAttribute(name, newValue);
179
246
  break;
180
247
  case "diagnostics":
181
248
  // Phase A: no UI. Property is observable for users via getDiagnostics().
@@ -199,6 +266,29 @@ export class AvbridgePlayerElement extends HTMLElementCtor {
199
266
  return null;
200
267
  }
201
268
 
269
+ /**
270
+ * Mirror light-DOM `<track>` children into the shadow `<video>` so that
271
+ * the browser's native text-track machinery picks them up. Called on
272
+ * connect, on every mutation of light-DOM children, and once after each
273
+ * source change so newly-set tracks survive a fresh `<video>`.
274
+ *
275
+ * Strategy: clone the children. We don't move them because the user's
276
+ * code may still hold references to the originals (e.g. to set `default`).
277
+ * The shadow copies are throwaway — we wipe them on every sync.
278
+ */
279
+ private _syncTextTracks(): void {
280
+ // Remove existing shadow tracks.
281
+ const existing = this._videoEl.querySelectorAll("track");
282
+ for (const t of Array.from(existing)) t.remove();
283
+ // Clone every <track> light-DOM child into the shadow video.
284
+ for (const child of Array.from(this.children)) {
285
+ if (child.tagName === "TRACK") {
286
+ const clone = child.cloneNode(true) as HTMLTrackElement;
287
+ this._videoEl.appendChild(clone);
288
+ }
289
+ }
290
+ }
291
+
202
292
  /** Internal src setter — separate from the property setter so the
203
293
  * attributeChangedCallback can use it without re-entering reflection. */
204
294
  private _setSrcInternal(value: string | null): void {
@@ -260,6 +350,11 @@ export class AvbridgePlayerElement extends HTMLElementCtor {
260
350
 
261
351
  this._player = player;
262
352
 
353
+ // Resync any light-DOM <track> children into the (possibly fresh) shadow
354
+ // <video>. Strategies that swap or reset the inner video state would
355
+ // otherwise lose the tracks the user declared in HTML.
356
+ this._syncTextTracks();
357
+
263
358
  // Wire events. The unsubscribe handles are not stored individually
264
359
  // because destroy() will tear down the whole session anyway.
265
360
  player.on("strategy", ({ strategy, reason }) => {
@@ -476,13 +571,100 @@ export class AvbridgePlayerElement extends HTMLElementCtor {
476
571
  * `<video>.buffered` `TimeRanges` API. For the native and remux strategies
477
572
  * this reflects the underlying SourceBuffer / progressive download state.
478
573
  * For the hybrid and fallback (canvas-rendered) strategies it currently
479
- * returns an empty TimeRanges; v1.1 will synthesize a coarse range from
480
- * the decoder's read position.
574
+ * returns an empty TimeRanges; a future release will synthesize a coarse
575
+ * range from the decoder's read position.
481
576
  */
482
577
  get buffered(): TimeRanges {
483
578
  return this._videoEl.buffered;
484
579
  }
485
580
 
581
+ // ── HTMLMediaElement parity ───────────────────────────────────────────
582
+ // Mirror the standard <video> surface so consumers can drop the element
583
+ // in as a <video> replacement. Each property is a thin passthrough to the
584
+ // shadow `<video>`.
585
+
586
+ get poster(): string {
587
+ return this._videoEl.poster;
588
+ }
589
+ set poster(value: string) {
590
+ if (value == null || value === "") this.removeAttribute("poster");
591
+ else this.setAttribute("poster", value);
592
+ }
593
+
594
+ get volume(): number {
595
+ return this._videoEl.volume;
596
+ }
597
+ set volume(value: number) {
598
+ this._videoEl.volume = value;
599
+ }
600
+
601
+ get playbackRate(): number {
602
+ return this._videoEl.playbackRate;
603
+ }
604
+ set playbackRate(value: number) {
605
+ this._videoEl.playbackRate = value;
606
+ }
607
+
608
+ get videoWidth(): number {
609
+ return this._videoEl.videoWidth;
610
+ }
611
+
612
+ get videoHeight(): number {
613
+ return this._videoEl.videoHeight;
614
+ }
615
+
616
+ get played(): TimeRanges {
617
+ return this._videoEl.played;
618
+ }
619
+
620
+ get seekable(): TimeRanges {
621
+ return this._videoEl.seekable;
622
+ }
623
+
624
+ get crossOrigin(): string | null {
625
+ return this._videoEl.crossOrigin;
626
+ }
627
+ set crossOrigin(value: string | null) {
628
+ if (value == null) this.removeAttribute("crossorigin");
629
+ else this.setAttribute("crossorigin", value);
630
+ }
631
+
632
+ get disableRemotePlayback(): boolean {
633
+ return this._videoEl.disableRemotePlayback;
634
+ }
635
+ set disableRemotePlayback(value: boolean) {
636
+ if (value) this.setAttribute("disableremoteplayback", "");
637
+ else this.removeAttribute("disableremoteplayback");
638
+ }
639
+
640
+ /**
641
+ * Native `HTMLMediaElement.canPlayType()` passthrough. Note that this
642
+ * answers about the *browser's* native support, not avbridge's full
643
+ * capabilities — avbridge can play many formats this method returns ""
644
+ * for, by routing them to the remux/hybrid/fallback strategies.
645
+ */
646
+ canPlayType(mimeType: string): CanPlayTypeResult {
647
+ return this._videoEl.canPlayType(mimeType);
648
+ }
649
+
650
+ /**
651
+ * **Escape hatch.** The underlying shadow-DOM `<video>` element.
652
+ *
653
+ * Use for native browser APIs the wrapper doesn't expose:
654
+ * - `el.videoElement.requestPictureInPicture()`
655
+ * - `el.videoElement.audioTracks` (browser native, not avbridge's track list)
656
+ * - direct integration with libraries that need a real HTMLVideoElement
657
+ *
658
+ * **Caveat:** When the active strategy is `"fallback"` or `"hybrid"`,
659
+ * frames are rendered to a canvas overlay, not into this `<video>`.
660
+ * APIs that depend on the actual pixels (Picture-in-Picture, captureStream)
661
+ * will not show the playing content in those modes. Check `el.strategy`
662
+ * before using such APIs.
663
+ */
664
+ get videoElement(): HTMLVideoElement {
665
+ return this._videoEl;
666
+ }
667
+
486
668
  get strategy(): StrategyName | null {
487
669
  return this._strategy;
488
670
  }