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 +39 -1
- package/dist/element.cjs +145 -4
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +51 -2
- package/dist/element.d.ts +51 -2
- package/dist/element.js +145 -4
- package/dist/element.js.map +1 -1
- package/package.json +1 -1
- package/src/element/avbridge-player.ts +186 -4
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
|
-
## [
|
|
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
|
-
|
|
103
|
-
|
|
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;
|
|
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
|
}
|
package/dist/element.cjs.map
CHANGED
|
@@ -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":";;;;;;;;AA+BA,IAAM,yBAAA,uBAAgC,GAAA,CAAuB;AAAA,EAC3D,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAC,CAAA;AAeD,IAAM,sBAAA,GAAyB;AAAA,EAC7B,WAAA;AAAA,EACA,gBAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,gBAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA,EACA,YAAA;AAAA,EACA,gBAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA;AAeA,IAAM,eAAA,GACJ,OAAO,WAAA,KAAgB,WAAA,GACnB,cACC,MAAM;AAAC,CAAA;AASP,IAAM,qBAAA,GAAN,cAAoC,eAAA,CAAgB;AAAA,EACzD,OAAgB,kBAAA,GAAqB;AAAA,IACnC,KAAA;AAAA,IACA,UAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA;AAAA,IACA,QAAA;AAAA,IACA,aAAA;AAAA,IACA,aAAA;AAAA,IACA,uBAAA;AAAA,IACA,aAAA;AAAA,IACA;AAAA,GACF;AAAA;AAAA;AAAA,EAKQ,QAAA;AAAA;AAAA,EAGA,OAAA,GAAgC,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhC,YAAA,GAAe,CAAA;AAAA;AAAA,EAGf,UAAA,GAAa,KAAA;AAAA;AAAA,EAGb,IAAA,GAAsB,IAAA;AAAA,EACtB,OAAA,GAA6B,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,wBAAA,GAA2B,KAAA;AAAA;AAAA,EAG3B,SAAA,GAAiC,IAAA;AAAA,EACjC,cAAA,GAAuC,IAAA;AAAA,EACvC,eAAiC,EAAC;AAAA,EAClC,kBAAuC,EAAC;AAAA;AAAA,EAGxC,kBAAA,GAAwC,MAAA;AAAA;AAAA,EAGxC,YAAA,GAA8B,IAAA;AAAA;AAAA,EAE9B,YAAA,GAAe,KAAA;AAAA;AAAA,EAGf,cAAA,GAA0C,IAAA;AAAA;AAAA,EAIlD,WAAA,GAAc;AACZ,IAAA,KAAA,EAAM;AACN,IAAA,MAAM,OAAO,IAAA,CAAK,YAAA,CAAa,EAAE,IAAA,EAAM,QAAQ,CAAA;AAC/C,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC9C,IAAA,IAAA,CAAK,QAAA,CAAS,YAAA,CAAa,MAAA,EAAQ,OAAO,CAAA;AAC1C,IAAA,IAAA,CAAK,QAAA,CAAS,MAAM,OAAA,GAAU,uDAAA;AAC9B,IAAA,IAAA,CAAK,SAAS,WAAA,GAAc,IAAA;AAC5B,IAAA,IAAA,CAAK,WAAA,CAAY,KAAK,QAAQ,CAAA;AAO9B,IAAA,IAAA,CAAK,QAAA,CAAS,gBAAA,CAAiB,UAAA,EAAY,MAAM;AAC/C,MAAA,IAAI,KAAK,UAAA,EAAY;AACrB,MAAA,IAAA,CAAK,UAAU,UAAA,EAAY,EAAE,UAAU,IAAA,CAAK,QAAA,CAAS,UAAU,CAAA;AAAA,IACjE,CAAC,CAAA;AAMD,IAAA,KAAA,MAAW,aAAa,sBAAA,EAAwB;AAC9C,MAAA,IAAA,CAAK,QAAA,CAAS,gBAAA,CAAiB,SAAA,EAAW,MAAM;AAC9C,QAAA,IAAI,KAAK,UAAA,EAAY;AACrB,QAAA,IAAA,CAAK,aAAA,CAAc,IAAI,KAAA,CAAM,SAAA,EAAW,EAAE,OAAA,EAAS,KAAA,EAAO,CAAC,CAAA;AAAA,MAC7D,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEA,iBAAA,GAA0B;AACxB,IAAA,IAAI,KAAK,UAAA,EAAY;AAGrB,IAAA,IAAA,CAAK,eAAA,EAAgB;AACrB,IAAA,IAAI,CAAC,KAAK,cAAA,EAAgB;AACxB,MAAA,IAAA,CAAK,iBAAiB,IAAI,gBAAA,CAAiB,MAAM,IAAA,CAAK,iBAAiB,CAAA;AACvE,MAAA,IAAA,CAAK,cAAA,CAAe,QAAQ,IAAA,EAAM,EAAE,WAAW,IAAA,EAAM,OAAA,EAAS,OAAO,CAAA;AAAA,IACvE;AAGA,IAAA,MAAM,MAAA,GAAS,KAAK,aAAA,EAAc;AAClC,IAAA,IAAI,UAAU,IAAA,EAAM;AAClB,MAAA,KAAK,IAAA,CAAK,WAAW,MAAM,CAAA;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,oBAAA,GAA6B;AAC3B,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,IAAI,KAAK,cAAA,EAAgB;AACvB,MAAA,IAAA,CAAK,eAAe,UAAA,EAAW;AAC/B,MAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AAAA,IACxB;AAKA,IAAA,IAAA,CAAK,YAAA,EAAA;AACL,IAAA,KAAK,KAAK,SAAA,EAAU;AAAA,EACtB;AAAA,EAEA,wBAAA,CAAyB,IAAA,EAAc,SAAA,EAA0B,QAAA,EAA+B;AAC9F,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,QAAQ,IAAA;AAAM,MACZ,KAAK,KAAA;AACH,QAAA,IAAI,KAAK,wBAAA,EAA0B;AACnC,QAAA,IAAA,CAAK,gBAAgB,QAAQ,CAAA;AAC7B,QAAA;AAAA,MACF,KAAK,UAAA;AAAA,MACL,KAAK,OAAA;AAAA,MACL,KAAK,MAAA;AAAA,MACL,KAAK,aAAA;AAAA,MACL,KAAK,uBAAA;AAEH,QAAA,IAAI,QAAA,IAAY,IAAA,EAAM,IAAA,CAAK,QAAA,CAAS,gBAAgB,IAAI,CAAA;AAAA,aACnD,IAAA,CAAK,QAAA,CAAS,YAAA,CAAa,IAAA,EAAM,QAAQ,CAAA;AAC9C,QAAA;AAAA,MACF,KAAK,SAAA;AAAA,MACL,KAAK,QAAA;AAAA,MACL,KAAK,aAAA;AACH,QAAA,IAAI,QAAA,IAAY,IAAA,EAAM,IAAA,CAAK,QAAA,CAAS,gBAAgB,IAAI,CAAA;AAAA,aACnD,IAAA,CAAK,QAAA,CAAS,YAAA,CAAa,IAAA,EAAM,QAAQ,CAAA;AAC9C,QAAA;AAAA,MACF,KAAK,aAAA;AAEH,QAAA;AAAA,MACF,KAAK,gBAAA;AACH,QAAA,IAAI,QAAA,IAAY,yBAAA,CAA0B,GAAA,CAAI,QAA6B,CAAA,EAAG;AAC5E,UAAA,IAAA,CAAK,kBAAA,GAAqB,QAAA;AAAA,QAC5B,CAAA,MAAO;AACL,UAAA,IAAA,CAAK,kBAAA,GAAqB,MAAA;AAAA,QAC5B;AACA,QAAA;AAAA;AACJ,EACF;AAAA;AAAA;AAAA,EAKQ,aAAA,GAAmC;AACzC,IAAA,IAAI,IAAA,CAAK,OAAA,IAAW,IAAA,EAAM,OAAO,IAAA,CAAK,OAAA;AACtC,IAAA,IAAI,IAAA,CAAK,IAAA,IAAQ,IAAA,EAAM,OAAO,IAAA,CAAK,IAAA;AACnC,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,eAAA,GAAwB;AAE9B,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,CAAS,gBAAA,CAAiB,OAAO,CAAA;AACvD,IAAA,KAAA,MAAW,KAAK,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAA,IAAK,MAAA,EAAO;AAE/C,IAAA,KAAA,MAAW,KAAA,IAAS,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,QAAQ,CAAA,EAAG;AAC7C,MAAA,IAAI,KAAA,CAAM,YAAY,OAAA,EAAS;AAC7B,QAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,SAAA,CAAU,IAAI,CAAA;AAClC,QAAA,IAAA,CAAK,QAAA,CAAS,YAAY,KAAK,CAAA;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA,EAIQ,gBAAgB,KAAA,EAA4B;AAElD,IAAA,IAAI,KAAA,KAAU,IAAA,CAAK,IAAA,IAAQ,IAAA,CAAK,WAAW,IAAA,EAAM;AACjD,IAAA,IAAA,CAAK,IAAA,GAAO,KAAA;AACZ,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,EACxB;AAAA;AAAA,EAGQ,gBAAA,GAAyB;AAC/B,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,MAAM,MAAA,GAAS,KAAK,aAAA,EAAc;AAClC,IAAA,IAAI,UAAU,IAAA,EAAM;AAElB,MAAA,IAAA,CAAK,YAAA,EAAA;AACL,MAAA,KAAK,KAAK,SAAA,EAAU;AACpB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,KAAK,WAAA,EAAa;AACpB,MAAA,KAAK,IAAA,CAAK,WAAW,MAAM,CAAA;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA,EAIA,MAAc,WAAW,MAAA,EAAmC;AAC1D,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,MAAM,EAAA,GAAK,EAAE,IAAA,CAAK,YAAA;AAKlB,IAAA,MAAM,IAAA,CAAK,UAAU,EAAE,CAAA;AACvB,IAAA,IAAI,EAAA,KAAO,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,UAAA,EAAY;AAEjD,IAAA,IAAA,CAAK,SAAA,CAAU,WAAA,EAAa,EAAE,CAAA;AAE9B,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI;AACF,MAAA,MAAA,GAAS,MAAMA,8BAAA,CAAa;AAAA,QAC1B,MAAA;AAAA,QACA,QAAQ,IAAA,CAAK;AAAA,OACd,CAAA;AAAA,IACH,SAAS,GAAA,EAAK;AAEZ,MAAA,IAAI,EAAA,KAAO,IAAA,CAAK,YAAA,IAAgB,IAAA,CAAK,UAAA,EAAY;AACjD,MAAA,IAAA,CAAK,eAAe,GAAG,CAAA;AACvB,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAO,IAAA,CAAK,YAAA,IAAgB,KAAK,UAAA,IAAc,CAAC,KAAK,WAAA,EAAa;AACpE,MAAA,IAAI;AAAE,QAAA,MAAM,OAAO,OAAA,EAAQ;AAAA,MAAG,CAAA,CAAA,MAAQ;AAAA,MAAe;AACrD,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AAKf,IAAA,IAAA,CAAK,eAAA,EAAgB;AAIrB,IAAA,MAAA,CAAO,GAAG,UAAA,EAAY,CAAC,EAAE,QAAA,EAAU,QAAO,KAAM;AAE9C,MAAA,MAAM,GAAA,GAAM,MAAA,CAAO,cAAA,EAAe,CAAE,aAAA;AACpC,MAAA,IAAA,CAAK,SAAA,GAAY,QAAA;AACjB,MAAA,IAAA,CAAK,cAAA,GAAiB,GAAA,KAAQ,SAAA,GAAY,IAAA,GAAO,GAAA;AACjD,MAAA,IAAA,CAAK,UAAU,gBAAA,EAAkB;AAAA,QAC/B,QAAA;AAAA,QACA,eAAe,IAAA,CAAK,cAAA;AAAA,QACpB,MAAA;AAAA,QACA,WAAA,EAAa,OAAO,cAAA;AAAe,OACpC,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,MAAA,CAAO,EAAA,CAAG,kBAAkB,CAAC,EAAE,MAAM,EAAA,EAAI,MAAA,EAAQ,aAAY,KAAM;AACjE,MAAA,IAAA,CAAK,UAAU,gBAAA,EAAkB;AAAA,QAC/B,IAAA;AAAA,QACA,QAAA,EAAU,EAAA;AAAA,QACV,aAAA,EAAe,OAAO,cAAA,EAAe,CAAE,kBAAkB,SAAA,GAAY,IAAA,GAAO,MAAA,CAAO,cAAA,EAAe,CAAE,aAAA;AAAA,QACpG,MAAA;AAAA,QACA,WAAA;AAAA,QACA,WAAA,EAAa,OAAO,cAAA;AAAe,OACpC,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,MAAA,CAAO,EAAA,CAAG,UAAU,CAAC,EAAE,OAAO,EAAA,EAAI,KAAA,EAAO,UAAS,KAAM;AACtD,MAAA,IAAA,CAAK,YAAA,GAAe,KAAA;AACpB,MAAA,IAAA,CAAK,eAAA,GAAkB,QAAA;AACvB,MAAA,IAAA,CAAK,UAAU,cAAA,EAAgB;AAAA,QAC7B,WAAA,EAAa,KAAA;AAAA,QACb,cAAA,EAAgB;AAAA,OACjB,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,MAAA,CAAO,EAAA,CAAG,OAAA,EAAS,CAAC,GAAA,KAAe;AACjC,MAAA,IAAA,CAAK,eAAe,GAAG,CAAA;AAAA,IACzB,CAAC,CAAA;AAED,IAAA,MAAA,CAAO,EAAA,CAAG,YAAA,EAAc,CAAC,EAAE,aAAY,KAAM;AAC3C,MAAA,IAAA,CAAK,SAAA,CAAU,YAAA,EAAc,EAAE,WAAA,EAAa,CAAA;AAAA,IAC9C,CAAC,CAAA;AAED,IAAA,MAAA,CAAO,EAAA,CAAG,SAAS,MAAM;AACvB,MAAA,IAAA,CAAK,SAAA,CAAU,OAAA,EAAS,EAAE,CAAA;AAAA,IAC5B,CAAC,CAAA;AAED,IAAA,MAAA,CAAO,EAAA,CAAG,SAAS,MAAM;AACvB,MAAA,IAAA,CAAK,UAAU,OAAA,EAAS,EAAE,aAAa,MAAA,CAAO,cAAA,IAAkB,CAAA;AAEhE,MAAA,IAAI,IAAA,CAAK,gBAAgB,IAAA,EAAM;AAC7B,QAAA,MAAM,IAAI,IAAA,CAAK,YAAA;AACf,QAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,QAAA,KAAK,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,CAAE,MAAM,MAAM;AAAA,QAAe,CAAC,CAAA;AAAA,MAClD;AAEA,MAAA,IAAI,KAAK,YAAA,EAAc;AACrB,QAAA,IAAA,CAAK,YAAA,GAAe,KAAA;AACpB,QAAA,KAAK,MAAA,CAAO,IAAA,EAAK,CAAE,KAAA,CAAM,MAAM;AAAA,QAAyC,CAAC,CAAA;AAAA,MAC3E,CAAA,MAAA,IAAW,KAAK,QAAA,EAAU;AACxB,QAAA,KAAK,MAAA,CAAO,IAAA,EAAK,CAAE,KAAA,CAAM,MAAM;AAAA,QAAe,CAAC,CAAA;AAAA,MACjD;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,UAAU,kBAAA,EAA4C;AAClE,IAAA,IAAI,sBAAsB,IAAA,EAAM;AAI9B,MAAA,IAAA,CAAK,YAAA,EAAA;AAAA,IACP;AACA,IAAA,MAAM,SAAS,IAAA,CAAK,OAAA;AACpB,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AACjB,IAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AACtB,IAAA,IAAA,CAAK,eAAe,EAAC;AACrB,IAAA,IAAA,CAAK,kBAAkB,EAAC;AACxB,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,IAAI;AAAE,QAAA,MAAM,OAAO,OAAA,EAAQ;AAAA,MAAG,CAAA,CAAA,MAAQ;AAAA,MAAe;AAAA,IACvD;AAAA,EACF;AAAA;AAAA,EAIA,IAAI,GAAA,GAAqB;AACvB,IAAA,OAAO,IAAA,CAAK,IAAA;AAAA,EACd;AAAA,EAEA,IAAI,IAAI,KAAA,EAAsB;AAC5B,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,IAAA,CAAK,gBAAgB,KAAK,CAAA;AAAA,IAC5B,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,YAAA,CAAa,OAAO,KAAK,CAAA;AAAA,IAChC;AAAA,EAEF;AAAA,EAEA,IAAI,MAAA,GAA4B;AAC9B,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA,EAEA,IAAI,OAAO,KAAA,EAA0B;AAEnC,IAAA,IAAI,KAAA,KAAU,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,QAAQ,IAAA,EAAM;AACjD,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAI,SAAS,IAAA,EAAM;AAGjB,MAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,MAAA,IAAI,IAAA,CAAK,YAAA,CAAa,KAAK,CAAA,EAAG;AAC5B,QAAA,IAAA,CAAK,wBAAA,GAA2B,IAAA;AAChC,QAAA,IAAI;AACF,UAAA,IAAA,CAAK,gBAAgB,KAAK,CAAA;AAAA,QAC5B,CAAA,SAAE;AACA,UAAA,IAAA,CAAK,wBAAA,GAA2B,KAAA;AAAA,QAClC;AAAA,MACF;AAAA,IACF;AACA,IAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,EACxB;AAAA,EAEA,IAAI,QAAA,GAAoB;AACtB,IAAA,OAAO,IAAA,CAAK,aAAa,UAAU,CAAA;AAAA,EACrC;AAAA,EAEA,IAAI,SAAS,KAAA,EAAgB;AAC3B,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,YAAA,CAAa,UAAA,EAAY,EAAE,CAAA;AAAA,SACtC,IAAA,CAAK,gBAAgB,UAAU,CAAA;AAAA,EACtC;AAAA,EAEA,IAAI,KAAA,GAAiB;AACnB,IAAA,OAAO,IAAA,CAAK,aAAa,OAAO,CAAA;AAAA,EAClC;AAAA,EAEA,IAAI,MAAM,KAAA,EAAgB;AACxB,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,YAAA,CAAa,OAAA,EAAS,EAAE,CAAA;AAAA,SACnC,IAAA,CAAK,gBAAgB,OAAO,CAAA;AAAA,EACnC;AAAA,EAEA,IAAI,IAAA,GAAgB;AAClB,IAAA,OAAO,IAAA,CAAK,aAAa,MAAM,CAAA;AAAA,EACjC;AAAA,EAEA,IAAI,KAAK,KAAA,EAAgB;AACvB,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,YAAA,CAAa,MAAA,EAAQ,EAAE,CAAA;AAAA,SAClC,IAAA,CAAK,gBAAgB,MAAM,CAAA;AAAA,EAClC;AAAA,EAEA,IAAI,OAAA,GAAwC;AAC1C,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,YAAA,CAAa,SAAS,CAAA;AACrC,IAAA,OAAO,MAAM,MAAA,IAAU,CAAA,KAAM,UAAA,IAAc,CAAA,KAAM,SAAS,CAAA,GAAI,MAAA;AAAA,EAChE;AAAA,EAEA,IAAI,QAAQ,KAAA,EAAqC;AAC/C,IAAA,IAAA,CAAK,YAAA,CAAa,WAAW,KAAK,CAAA;AAAA,EACpC;AAAA,EAEA,IAAI,WAAA,GAAuB;AACzB,IAAA,OAAO,IAAA,CAAK,aAAa,aAAa,CAAA;AAAA,EACxC;AAAA,EAEA,IAAI,YAAY,KAAA,EAAgB;AAC9B,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,YAAA,CAAa,aAAA,EAAe,EAAE,CAAA;AAAA,SACzC,IAAA,CAAK,gBAAgB,aAAa,CAAA;AAAA,EACzC;AAAA,EAEA,IAAI,iBAAA,GAAuC;AACzC,IAAA,OAAO,IAAA,CAAK,kBAAA;AAAA,EACd;AAAA,EAEA,IAAI,kBAAkB,KAAA,EAA0B;AAC9C,IAAA,IAAI,yBAAA,CAA0B,GAAA,CAAI,KAAK,CAAA,EAAG;AACxC,MAAA,IAAA,CAAK,YAAA,CAAa,kBAAkB,KAAK,CAAA;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,IAAI,WAAA,GAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,OAAA,EAAS,cAAA,EAAe,IAAK,CAAA;AAAA,EAC3C;AAAA,EAEA,IAAI,YAAY,KAAA,EAAe;AAC7B,IAAA,IAAI,KAAK,OAAA,EAAS;AAChB,MAAA,KAAK,KAAK,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,CAAE,MAAM,MAAM;AAAA,MAAe,CAAC,CAAA;AAAA,IAC5D,CAAA,MAAO;AAEL,MAAA,IAAA,CAAK,YAAA,GAAe,KAAA;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,IAAI,QAAA,GAAmB;AACrB,IAAA,OAAO,IAAA,CAAK,OAAA,EAAS,WAAA,EAAY,IAAK,GAAA;AAAA,EACxC;AAAA,EAEA,IAAI,MAAA,GAAkB;AACpB,IAAA,OAAO,KAAK,QAAA,CAAS,MAAA;AAAA,EACvB;AAAA,EAEA,IAAI,KAAA,GAAiB;AACnB,IAAA,OAAO,KAAK,QAAA,CAAS,KAAA;AAAA,EACvB;AAAA,EAEA,IAAI,UAAA,GAAqB;AACvB,IAAA,OAAO,KAAK,QAAA,CAAS,UAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,IAAI,QAAA,GAAuB;AACzB,IAAA,OAAO,KAAK,QAAA,CAAS,QAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,MAAA,GAAiB;AACnB,IAAA,OAAO,KAAK,QAAA,CAAS,MAAA;AAAA,EACvB;AAAA,EACA,IAAI,OAAO,KAAA,EAAe;AACxB,IAAA,IAAI,SAAS,IAAA,IAAQ,KAAA,KAAU,EAAA,EAAI,IAAA,CAAK,gBAAgB,QAAQ,CAAA;AAAA,SAC3D,IAAA,CAAK,YAAA,CAAa,QAAA,EAAU,KAAK,CAAA;AAAA,EACxC;AAAA,EAEA,IAAI,MAAA,GAAiB;AACnB,IAAA,OAAO,KAAK,QAAA,CAAS,MAAA;AAAA,EACvB;AAAA,EACA,IAAI,OAAO,KAAA,EAAe;AACxB,IAAA,IAAA,CAAK,SAAS,MAAA,GAAS,KAAA;AAAA,EACzB;AAAA,EAEA,IAAI,YAAA,GAAuB;AACzB,IAAA,OAAO,KAAK,QAAA,CAAS,YAAA;AAAA,EACvB;AAAA,EACA,IAAI,aAAa,KAAA,EAAe;AAC9B,IAAA,IAAA,CAAK,SAAS,YAAA,GAAe,KAAA;AAAA,EAC/B;AAAA,EAEA,IAAI,UAAA,GAAqB;AACvB,IAAA,OAAO,KAAK,QAAA,CAAS,UAAA;AAAA,EACvB;AAAA,EAEA,IAAI,WAAA,GAAsB;AACxB,IAAA,OAAO,KAAK,QAAA,CAAS,WAAA;AAAA,EACvB;AAAA,EAEA,IAAI,MAAA,GAAqB;AACvB,IAAA,OAAO,KAAK,QAAA,CAAS,MAAA;AAAA,EACvB;AAAA,EAEA,IAAI,QAAA,GAAuB;AACzB,IAAA,OAAO,KAAK,QAAA,CAAS,QAAA;AAAA,EACvB;AAAA,EAEA,IAAI,WAAA,GAA6B;AAC/B,IAAA,OAAO,KAAK,QAAA,CAAS,WAAA;AAAA,EACvB;AAAA,EACA,IAAI,YAAY,KAAA,EAAsB;AACpC,IAAA,IAAI,KAAA,IAAS,IAAA,EAAM,IAAA,CAAK,eAAA,CAAgB,aAAa,CAAA;AAAA,SAChD,IAAA,CAAK,YAAA,CAAa,aAAA,EAAe,KAAK,CAAA;AAAA,EAC7C;AAAA,EAEA,IAAI,qBAAA,GAAiC;AACnC,IAAA,OAAO,KAAK,QAAA,CAAS,qBAAA;AAAA,EACvB;AAAA,EACA,IAAI,sBAAsB,KAAA,EAAgB;AACxC,IAAA,IAAI,KAAA,EAAO,IAAA,CAAK,YAAA,CAAa,uBAAA,EAAyB,EAAE,CAAA;AAAA,SACnD,IAAA,CAAK,gBAAgB,uBAAuB,CAAA;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,QAAA,EAAqC;AAC/C,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,WAAA,CAAY,QAAQ,CAAA;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,IAAI,YAAA,GAAiC;AACnC,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA,EAEA,IAAI,QAAA,GAAgC;AAClC,IAAA,OAAO,IAAA,CAAK,SAAA;AAAA,EACd;AAAA,EAEA,IAAI,aAAA,GAAsC;AACxC,IAAA,OAAO,IAAA,CAAK,cAAA;AAAA,EACd;AAAA,EAEA,IAAI,MAAA,GAA+B;AACjC,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA,EAEA,IAAI,WAAA,GAAgC;AAClC,IAAA,OAAO,IAAA,CAAK,YAAA;AAAA,EACd;AAAA,EAEA,IAAI,cAAA,GAAsC;AACxC,IAAA,OAAO,IAAA,CAAK,eAAA;AAAA,EACd;AAAA;AAAA;AAAA,EAKA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,MAAM,MAAA,GAAS,KAAK,aAAA,EAAc;AAClC,IAAA,IAAI,UAAU,IAAA,EAAM;AACpB,IAAA,MAAM,IAAA,CAAK,WAAW,MAAM,CAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,IAAI,KAAK,OAAA,EAAS;AAChB,MAAA,MAAM,IAAA,CAAK,QAAQ,IAAA,EAAK;AAAA,IAC1B,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,IAAA,CAAK,YAAA,GAAe,KAAA;AACpB,IAAA,IAAA,CAAK,SAAS,KAAA,EAAM;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAA,GAAyB;AAC7B,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAClB,IAAA,MAAM,KAAK,SAAA,EAAU;AACrB,IAAA,IAAA,CAAK,SAAA,CAAU,SAAA,EAAW,EAAE,CAAA;AAAA,EAC9B;AAAA,EAEA,MAAM,cAAc,EAAA,EAA2B;AAC7C,IAAA,IAAI,IAAA,CAAK,UAAA,IAAc,CAAC,IAAA,CAAK,OAAA,EAAS;AACtC,IAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,aAAA,CAAc,EAAE,CAAA;AAAA,EACrC;AAAA,EAEA,MAAM,iBAAiB,EAAA,EAAkC;AACvD,IAAA,IAAI,IAAA,CAAK,UAAA,IAAc,CAAC,IAAA,CAAK,OAAA,EAAS;AACtC,IAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,gBAAA,CAAiB,EAAE,CAAA;AAAA,EACxC;AAAA,EAEA,cAAA,GAA6C;AAC3C,IAAA,OAAO,IAAA,CAAK,OAAA,EAAS,cAAA,EAAe,IAAK,IAAA;AAAA,EAC3C;AAAA;AAAA,EAIQ,SAAA,CAAa,MAAc,MAAA,EAAiB;AAClD,IAAA,IAAA,CAAK,aAAA,CAAc,IAAI,WAAA,CAAY,IAAA,EAAM,EAAE,MAAA,EAAQ,OAAA,EAAS,KAAA,EAAO,CAAC,CAAA;AAAA,EACtE;AAAA,EAEQ,eAAe,GAAA,EAAoB;AACzC,IAAA,MAAM,KAAA,GAAQ,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAChE,IAAA,IAAA,CAAK,SAAA,CAAU,OAAA,EAAS,EAAE,KAAA,EAAO,WAAA,EAAa,KAAK,OAAA,EAAS,cAAA,EAAe,IAAK,IAAA,EAAM,CAAA;AAAA,EACxF;AACF;;;AC/tBA,IAAI,OAAO,cAAA,KAAmB,WAAA,IAAe,CAAC,cAAA,CAAe,GAAA,CAAI,iBAAiB,CAAA,EAAG;AACnF,EAAA,cAAA,CAAe,MAAA,CAAO,mBAAmB,qBAAqB,CAAA;AAChE","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"]}
|
package/dist/element.d.cts
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;
|
|
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;
|
|
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
|
-
|
|
101
|
-
|
|
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;
|
|
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
|
}
|
package/dist/element.js.map
CHANGED
|
@@ -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
|
@@ -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
|
-
|
|
178
|
-
|
|
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;
|
|
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
|
}
|