avbridge 1.0.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 +120 -0
- package/LICENSE +21 -0
- package/README.md +415 -0
- package/dist/avi-M5B4SHRM.cjs +164 -0
- package/dist/avi-M5B4SHRM.cjs.map +1 -0
- package/dist/avi-POCGZ4JX.js +162 -0
- package/dist/avi-POCGZ4JX.js.map +1 -0
- package/dist/chunk-5ISVAODK.js +80 -0
- package/dist/chunk-5ISVAODK.js.map +1 -0
- package/dist/chunk-F7YS2XOA.cjs +2966 -0
- package/dist/chunk-F7YS2XOA.cjs.map +1 -0
- package/dist/chunk-FKM7QBZU.js +2957 -0
- package/dist/chunk-FKM7QBZU.js.map +1 -0
- package/dist/chunk-J5MCMN3S.js +27 -0
- package/dist/chunk-J5MCMN3S.js.map +1 -0
- package/dist/chunk-L4NPOJ36.cjs +180 -0
- package/dist/chunk-L4NPOJ36.cjs.map +1 -0
- package/dist/chunk-NZU7W256.cjs +29 -0
- package/dist/chunk-NZU7W256.cjs.map +1 -0
- package/dist/chunk-PQTZS7OA.js +147 -0
- package/dist/chunk-PQTZS7OA.js.map +1 -0
- package/dist/chunk-WD2ZNQA7.js +177 -0
- package/dist/chunk-WD2ZNQA7.js.map +1 -0
- package/dist/chunk-Y5FYF5KG.cjs +153 -0
- package/dist/chunk-Y5FYF5KG.cjs.map +1 -0
- package/dist/chunk-Z2FJ5TJC.cjs +82 -0
- package/dist/chunk-Z2FJ5TJC.cjs.map +1 -0
- package/dist/element.cjs +433 -0
- package/dist/element.cjs.map +1 -0
- package/dist/element.d.cts +158 -0
- package/dist/element.d.ts +158 -0
- package/dist/element.js +431 -0
- package/dist/element.js.map +1 -0
- package/dist/index.cjs +576 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +554 -0
- package/dist/index.js.map +1 -0
- package/dist/libav-http-reader-FPYDBMYK.cjs +16 -0
- package/dist/libav-http-reader-FPYDBMYK.cjs.map +1 -0
- package/dist/libav-http-reader-NQJVY273.js +3 -0
- package/dist/libav-http-reader-NQJVY273.js.map +1 -0
- package/dist/libav-import-2JURFHEW.js +8 -0
- package/dist/libav-import-2JURFHEW.js.map +1 -0
- package/dist/libav-import-GST2AMPL.cjs +30 -0
- package/dist/libav-import-GST2AMPL.cjs.map +1 -0
- package/dist/libav-loader-KA2MAWLM.js +3 -0
- package/dist/libav-loader-KA2MAWLM.js.map +1 -0
- package/dist/libav-loader-ZHOERPHW.cjs +12 -0
- package/dist/libav-loader-ZHOERPHW.cjs.map +1 -0
- package/dist/player-BBwbCkdL.d.cts +365 -0
- package/dist/player-BBwbCkdL.d.ts +365 -0
- package/dist/source-SC6ZEQYR.cjs +28 -0
- package/dist/source-SC6ZEQYR.cjs.map +1 -0
- package/dist/source-ZFS4H7J3.js +3 -0
- package/dist/source-ZFS4H7J3.js.map +1 -0
- package/dist/variant-routing-GOHB2RZN.cjs +12 -0
- package/dist/variant-routing-GOHB2RZN.cjs.map +1 -0
- package/dist/variant-routing-JOBWXYKD.js +3 -0
- package/dist/variant-routing-JOBWXYKD.js.map +1 -0
- package/package.json +95 -0
- package/src/classify/index.ts +1 -0
- package/src/classify/rules.ts +214 -0
- package/src/convert/index.ts +2 -0
- package/src/convert/remux.ts +522 -0
- package/src/convert/transcode.ts +329 -0
- package/src/diagnostics.ts +99 -0
- package/src/element/avbridge-player.ts +576 -0
- package/src/element.ts +19 -0
- package/src/events.ts +71 -0
- package/src/index.ts +42 -0
- package/src/libav-stubs.d.ts +24 -0
- package/src/player.ts +455 -0
- package/src/plugins/builtin.ts +37 -0
- package/src/plugins/registry.ts +32 -0
- package/src/probe/avi.ts +242 -0
- package/src/probe/index.ts +59 -0
- package/src/probe/mediabunny.ts +194 -0
- package/src/strategies/fallback/audio-output.ts +293 -0
- package/src/strategies/fallback/clock.ts +7 -0
- package/src/strategies/fallback/decoder.ts +660 -0
- package/src/strategies/fallback/index.ts +170 -0
- package/src/strategies/fallback/libav-import.ts +27 -0
- package/src/strategies/fallback/libav-loader.ts +190 -0
- package/src/strategies/fallback/variant-routing.ts +43 -0
- package/src/strategies/fallback/video-renderer.ts +216 -0
- package/src/strategies/hybrid/decoder.ts +641 -0
- package/src/strategies/hybrid/index.ts +139 -0
- package/src/strategies/native.ts +107 -0
- package/src/strategies/remux/annexb.ts +112 -0
- package/src/strategies/remux/index.ts +79 -0
- package/src/strategies/remux/mse.ts +234 -0
- package/src/strategies/remux/pipeline.ts +254 -0
- package/src/subtitles/index.ts +91 -0
- package/src/subtitles/render.ts +62 -0
- package/src/subtitles/srt.ts +62 -0
- package/src/subtitles/vtt.ts +5 -0
- package/src/types-shim.d.ts +3 -0
- package/src/types.ts +360 -0
- package/src/util/codec-strings.ts +86 -0
- package/src/util/libav-http-reader.ts +315 -0
- package/src/util/source.ts +274 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { a as MediaInput, m as StrategyName, S as StrategyClass, U as UnifiedPlayer, d as AudioTrackInfo, n as SubtitleTrackInfo, D as DiagnosticsSnapshot } from './player-BBwbCkdL.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<avbridge-player>` — reference web component for the avbridge engine.
|
|
5
|
+
*
|
|
6
|
+
* This is a *thin* wrapper around `createPlayer()`. Its purpose is to:
|
|
7
|
+
*
|
|
8
|
+
* 1. Validate the public API by being a real consumer of it.
|
|
9
|
+
* 2. Drive lifecycle correctness in the core via adversarial integration tests.
|
|
10
|
+
* 3. Provide a drop-in player for users who don't want to wire `createPlayer()`
|
|
11
|
+
* themselves.
|
|
12
|
+
*
|
|
13
|
+
* It is **not** a player UI framework. See `docs/dev/WEB_COMPONENT_SPEC.md`
|
|
14
|
+
* for the full spec, lifecycle invariants, and edge case list.
|
|
15
|
+
*
|
|
16
|
+
* Phase A scope (this file): lifecycle scaffold only — `src` / `source` /
|
|
17
|
+
* `currentTime` / `play` / `pause` / `load` / `destroy` / events. No built-in
|
|
18
|
+
* controls. Shadow DOM contains a single `<video part="video">`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Strategy preference passed via the `preferstrategy` attribute. */
|
|
22
|
+
type PreferredStrategy = "auto" | StrategyName;
|
|
23
|
+
/**
|
|
24
|
+
* `HTMLElement` is a browser-only global. SSR frameworks (Next.js, Astro,
|
|
25
|
+
* Remix, etc.) commonly import library modules on the server to extract
|
|
26
|
+
* types or do tree-shaking, even if the user only ends up using them in
|
|
27
|
+
* the browser. If we extended `HTMLElement` directly, the `class extends`
|
|
28
|
+
* expression would be evaluated at module load time and crash in Node.
|
|
29
|
+
*
|
|
30
|
+
* The fix: in non-browser environments, fall back to an empty stub class.
|
|
31
|
+
* The element is never *constructed* server-side (the registration in
|
|
32
|
+
* `element.ts` is guarded by `typeof customElements !== "undefined"`), so
|
|
33
|
+
* the stub is never instantiated — it just lets the class declaration
|
|
34
|
+
* evaluate cleanly so the module can be imported anywhere.
|
|
35
|
+
*/
|
|
36
|
+
declare const HTMLElementCtor: typeof HTMLElement;
|
|
37
|
+
/**
|
|
38
|
+
* Custom element. Lifecycle correctness is enforced via a monotonically
|
|
39
|
+
* increasing `_bootstrapId`: every async bootstrap captures the ID at start
|
|
40
|
+
* and discards itself if the ID has changed by the time it resolves. This
|
|
41
|
+
* single pattern handles disconnect-during-bootstrap, rapid src reassignment,
|
|
42
|
+
* bootstrap races, and destroy-during-bootstrap.
|
|
43
|
+
*/
|
|
44
|
+
declare class AvbridgePlayerElement extends HTMLElementCtor {
|
|
45
|
+
static readonly observedAttributes: string[];
|
|
46
|
+
/** The shadow DOM `<video>` element that strategies render into. */
|
|
47
|
+
private _videoEl;
|
|
48
|
+
/** Active player session, if any. Cleared on teardown. */
|
|
49
|
+
private _player;
|
|
50
|
+
/**
|
|
51
|
+
* Monotonic counter incremented on every (re)bootstrap. Async bootstrap
|
|
52
|
+
* work captures the current ID; if it doesn't match by the time the work
|
|
53
|
+
* resolves, the work is discarded.
|
|
54
|
+
*/
|
|
55
|
+
private _bootstrapId;
|
|
56
|
+
/** True after destroy() — element is permanently unusable. */
|
|
57
|
+
private _destroyed;
|
|
58
|
+
/** Internal source state. Either string-form (src) OR rich (source). */
|
|
59
|
+
private _src;
|
|
60
|
+
private _source;
|
|
61
|
+
/**
|
|
62
|
+
* Set when the `source` property setter is in the middle of clearing the
|
|
63
|
+
* `src` attribute as part of mutual exclusion. The attributeChangedCallback
|
|
64
|
+
* checks this flag and skips its normal "clear source" side effect, which
|
|
65
|
+
* would otherwise wipe the value we just set.
|
|
66
|
+
*/
|
|
67
|
+
private _suppressSrcAttrCallback;
|
|
68
|
+
/** Last-known runtime state surfaced via getters. */
|
|
69
|
+
private _strategy;
|
|
70
|
+
private _strategyClass;
|
|
71
|
+
private _audioTracks;
|
|
72
|
+
private _subtitleTracks;
|
|
73
|
+
/** Strategy preference (does not currently affect routing — reserved). */
|
|
74
|
+
private _preferredStrategy;
|
|
75
|
+
/** Set if currentTime was assigned before the player was ready. */
|
|
76
|
+
private _pendingSeek;
|
|
77
|
+
/** Set if play() was called before the player was ready. */
|
|
78
|
+
private _pendingPlay;
|
|
79
|
+
constructor();
|
|
80
|
+
connectedCallback(): void;
|
|
81
|
+
disconnectedCallback(): void;
|
|
82
|
+
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
|
|
83
|
+
/** Returns the currently-active source (src or source), whichever is set. */
|
|
84
|
+
private _activeSource;
|
|
85
|
+
/** Internal src setter — separate from the property setter so the
|
|
86
|
+
* attributeChangedCallback can use it without re-entering reflection. */
|
|
87
|
+
private _setSrcInternal;
|
|
88
|
+
/** Called whenever the active source changes (src or source). */
|
|
89
|
+
private _onSourceChanged;
|
|
90
|
+
private _bootstrap;
|
|
91
|
+
/**
|
|
92
|
+
* Tear down the active player and reset runtime state. Idempotent.
|
|
93
|
+
* If `currentBootstrapId` is provided, the bootstrap counter is NOT
|
|
94
|
+
* incremented (used by `_bootstrap()` to avoid invalidating itself).
|
|
95
|
+
*/
|
|
96
|
+
private _teardown;
|
|
97
|
+
get src(): string | null;
|
|
98
|
+
set src(value: string | null);
|
|
99
|
+
get source(): MediaInput | null;
|
|
100
|
+
set source(value: MediaInput | null);
|
|
101
|
+
get autoplay(): boolean;
|
|
102
|
+
set autoplay(value: boolean);
|
|
103
|
+
get muted(): boolean;
|
|
104
|
+
set muted(value: boolean);
|
|
105
|
+
get loop(): boolean;
|
|
106
|
+
set loop(value: boolean);
|
|
107
|
+
get preload(): "none" | "metadata" | "auto";
|
|
108
|
+
set preload(value: "none" | "metadata" | "auto");
|
|
109
|
+
get diagnostics(): boolean;
|
|
110
|
+
set diagnostics(value: boolean);
|
|
111
|
+
get preferredStrategy(): PreferredStrategy;
|
|
112
|
+
set preferredStrategy(value: PreferredStrategy);
|
|
113
|
+
get currentTime(): number;
|
|
114
|
+
set currentTime(value: number);
|
|
115
|
+
get duration(): number;
|
|
116
|
+
get paused(): boolean;
|
|
117
|
+
get ended(): boolean;
|
|
118
|
+
get readyState(): number;
|
|
119
|
+
/**
|
|
120
|
+
* Buffered time ranges for the active source. Mirrors the standard
|
|
121
|
+
* `<video>.buffered` `TimeRanges` API. For the native and remux strategies
|
|
122
|
+
* this reflects the underlying SourceBuffer / progressive download state.
|
|
123
|
+
* For the hybrid and fallback (canvas-rendered) strategies it currently
|
|
124
|
+
* returns an empty TimeRanges; v1.1 will synthesize a coarse range from
|
|
125
|
+
* the decoder's read position.
|
|
126
|
+
*/
|
|
127
|
+
get buffered(): TimeRanges;
|
|
128
|
+
get strategy(): StrategyName | null;
|
|
129
|
+
get strategyClass(): StrategyClass | null;
|
|
130
|
+
get player(): UnifiedPlayer | null;
|
|
131
|
+
get audioTracks(): AudioTrackInfo[];
|
|
132
|
+
get subtitleTracks(): SubtitleTrackInfo[];
|
|
133
|
+
/** Force a (re-)bootstrap if a source is currently set. */
|
|
134
|
+
load(): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Begin or resume playback. If the player isn't ready yet, the call is
|
|
137
|
+
* queued and applied once `ready` fires.
|
|
138
|
+
*/
|
|
139
|
+
play(): Promise<void>;
|
|
140
|
+
pause(): void;
|
|
141
|
+
/**
|
|
142
|
+
* Tear down the element permanently. After destroy(), the element ignores
|
|
143
|
+
* all method calls and attribute changes.
|
|
144
|
+
*/
|
|
145
|
+
destroy(): Promise<void>;
|
|
146
|
+
setAudioTrack(id: number): Promise<void>;
|
|
147
|
+
setSubtitleTrack(id: number | null): Promise<void>;
|
|
148
|
+
getDiagnostics(): DiagnosticsSnapshot | null;
|
|
149
|
+
private _dispatch;
|
|
150
|
+
private _dispatchError;
|
|
151
|
+
}
|
|
152
|
+
declare global {
|
|
153
|
+
interface HTMLElementTagNameMap {
|
|
154
|
+
"avbridge-player": AvbridgePlayerElement;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { AvbridgePlayerElement };
|
package/dist/element.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { createPlayer } from './chunk-FKM7QBZU.js';
|
|
2
|
+
import './chunk-PQTZS7OA.js';
|
|
3
|
+
import './chunk-5ISVAODK.js';
|
|
4
|
+
import './chunk-J5MCMN3S.js';
|
|
5
|
+
|
|
6
|
+
// src/element/avbridge-player.ts
|
|
7
|
+
var PREFERRED_STRATEGY_VALUES = /* @__PURE__ */ new Set([
|
|
8
|
+
"auto",
|
|
9
|
+
"native",
|
|
10
|
+
"remux",
|
|
11
|
+
"hybrid",
|
|
12
|
+
"fallback"
|
|
13
|
+
]);
|
|
14
|
+
var HTMLElementCtor = typeof HTMLElement !== "undefined" ? HTMLElement : class {
|
|
15
|
+
};
|
|
16
|
+
var AvbridgePlayerElement = class extends HTMLElementCtor {
|
|
17
|
+
static observedAttributes = [
|
|
18
|
+
"src",
|
|
19
|
+
"autoplay",
|
|
20
|
+
"muted",
|
|
21
|
+
"loop",
|
|
22
|
+
"preload",
|
|
23
|
+
"diagnostics",
|
|
24
|
+
"preferstrategy"
|
|
25
|
+
];
|
|
26
|
+
// ── Internal state ─────────────────────────────────────────────────────
|
|
27
|
+
/** The shadow DOM `<video>` element that strategies render into. */
|
|
28
|
+
_videoEl;
|
|
29
|
+
/** Active player session, if any. Cleared on teardown. */
|
|
30
|
+
_player = null;
|
|
31
|
+
/**
|
|
32
|
+
* Monotonic counter incremented on every (re)bootstrap. Async bootstrap
|
|
33
|
+
* work captures the current ID; if it doesn't match by the time the work
|
|
34
|
+
* resolves, the work is discarded.
|
|
35
|
+
*/
|
|
36
|
+
_bootstrapId = 0;
|
|
37
|
+
/** True after destroy() — element is permanently unusable. */
|
|
38
|
+
_destroyed = false;
|
|
39
|
+
/** Internal source state. Either string-form (src) OR rich (source). */
|
|
40
|
+
_src = null;
|
|
41
|
+
_source = null;
|
|
42
|
+
/**
|
|
43
|
+
* Set when the `source` property setter is in the middle of clearing the
|
|
44
|
+
* `src` attribute as part of mutual exclusion. The attributeChangedCallback
|
|
45
|
+
* checks this flag and skips its normal "clear source" side effect, which
|
|
46
|
+
* would otherwise wipe the value we just set.
|
|
47
|
+
*/
|
|
48
|
+
_suppressSrcAttrCallback = false;
|
|
49
|
+
/** Last-known runtime state surfaced via getters. */
|
|
50
|
+
_strategy = null;
|
|
51
|
+
_strategyClass = null;
|
|
52
|
+
_audioTracks = [];
|
|
53
|
+
_subtitleTracks = [];
|
|
54
|
+
/** Strategy preference (does not currently affect routing — reserved). */
|
|
55
|
+
_preferredStrategy = "auto";
|
|
56
|
+
/** Set if currentTime was assigned before the player was ready. */
|
|
57
|
+
_pendingSeek = null;
|
|
58
|
+
/** Set if play() was called before the player was ready. */
|
|
59
|
+
_pendingPlay = false;
|
|
60
|
+
// ── Construction & lifecycle ───────────────────────────────────────────
|
|
61
|
+
constructor() {
|
|
62
|
+
super();
|
|
63
|
+
const root = this.attachShadow({ mode: "open" });
|
|
64
|
+
this._videoEl = document.createElement("video");
|
|
65
|
+
this._videoEl.setAttribute("part", "video");
|
|
66
|
+
this._videoEl.style.cssText = "width:100%;height:100%;display:block;background:#000;";
|
|
67
|
+
this._videoEl.playsInline = true;
|
|
68
|
+
root.appendChild(this._videoEl);
|
|
69
|
+
this._videoEl.addEventListener("progress", () => {
|
|
70
|
+
if (this._destroyed) return;
|
|
71
|
+
this._dispatch("progress", { buffered: this._videoEl.buffered });
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
connectedCallback() {
|
|
75
|
+
if (this._destroyed) return;
|
|
76
|
+
const source = this._activeSource();
|
|
77
|
+
if (source != null) {
|
|
78
|
+
void this._bootstrap(source);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
disconnectedCallback() {
|
|
82
|
+
if (this._destroyed) return;
|
|
83
|
+
this._bootstrapId++;
|
|
84
|
+
void this._teardown();
|
|
85
|
+
}
|
|
86
|
+
attributeChangedCallback(name, _oldValue, newValue) {
|
|
87
|
+
if (this._destroyed) return;
|
|
88
|
+
switch (name) {
|
|
89
|
+
case "src":
|
|
90
|
+
if (this._suppressSrcAttrCallback) break;
|
|
91
|
+
this._setSrcInternal(newValue);
|
|
92
|
+
break;
|
|
93
|
+
case "autoplay":
|
|
94
|
+
case "muted":
|
|
95
|
+
case "loop":
|
|
96
|
+
if (newValue == null) this._videoEl.removeAttribute(name);
|
|
97
|
+
else this._videoEl.setAttribute(name, newValue);
|
|
98
|
+
break;
|
|
99
|
+
case "preload":
|
|
100
|
+
if (newValue == null) this._videoEl.removeAttribute("preload");
|
|
101
|
+
else this._videoEl.setAttribute("preload", newValue);
|
|
102
|
+
break;
|
|
103
|
+
case "diagnostics":
|
|
104
|
+
break;
|
|
105
|
+
case "preferstrategy":
|
|
106
|
+
if (newValue && PREFERRED_STRATEGY_VALUES.has(newValue)) {
|
|
107
|
+
this._preferredStrategy = newValue;
|
|
108
|
+
} else {
|
|
109
|
+
this._preferredStrategy = "auto";
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// ── Source handling ────────────────────────────────────────────────────
|
|
115
|
+
/** Returns the currently-active source (src or source), whichever is set. */
|
|
116
|
+
_activeSource() {
|
|
117
|
+
if (this._source != null) return this._source;
|
|
118
|
+
if (this._src != null) return this._src;
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/** Internal src setter — separate from the property setter so the
|
|
122
|
+
* attributeChangedCallback can use it without re-entering reflection. */
|
|
123
|
+
_setSrcInternal(value) {
|
|
124
|
+
if (value === this._src && this._source == null) return;
|
|
125
|
+
this._src = value;
|
|
126
|
+
this._source = null;
|
|
127
|
+
this._onSourceChanged();
|
|
128
|
+
}
|
|
129
|
+
/** Called whenever the active source changes (src or source). */
|
|
130
|
+
_onSourceChanged() {
|
|
131
|
+
if (this._destroyed) return;
|
|
132
|
+
const source = this._activeSource();
|
|
133
|
+
if (source == null) {
|
|
134
|
+
this._bootstrapId++;
|
|
135
|
+
void this._teardown();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (this.isConnected) {
|
|
139
|
+
void this._bootstrap(source);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// ── Bootstrap (the only place a UnifiedPlayer is created) ──────────────
|
|
143
|
+
async _bootstrap(source) {
|
|
144
|
+
if (this._destroyed) return;
|
|
145
|
+
const id = ++this._bootstrapId;
|
|
146
|
+
await this._teardown(id);
|
|
147
|
+
if (id !== this._bootstrapId || this._destroyed) return;
|
|
148
|
+
this._dispatch("loadstart", {});
|
|
149
|
+
let player;
|
|
150
|
+
try {
|
|
151
|
+
player = await createPlayer({
|
|
152
|
+
source,
|
|
153
|
+
target: this._videoEl
|
|
154
|
+
});
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (id !== this._bootstrapId || this._destroyed) return;
|
|
157
|
+
this._dispatchError(err);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (id !== this._bootstrapId || this._destroyed || !this.isConnected) {
|
|
161
|
+
try {
|
|
162
|
+
await player.destroy();
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this._player = player;
|
|
168
|
+
player.on("strategy", ({ strategy, reason }) => {
|
|
169
|
+
const cls = player.getDiagnostics().strategyClass;
|
|
170
|
+
this._strategy = strategy;
|
|
171
|
+
this._strategyClass = cls === "pending" ? null : cls;
|
|
172
|
+
this._dispatch("strategychange", {
|
|
173
|
+
strategy,
|
|
174
|
+
strategyClass: this._strategyClass,
|
|
175
|
+
reason,
|
|
176
|
+
diagnostics: player.getDiagnostics()
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
player.on("strategychange", ({ from, to, reason, currentTime }) => {
|
|
180
|
+
this._dispatch("strategychange", {
|
|
181
|
+
from,
|
|
182
|
+
strategy: to,
|
|
183
|
+
strategyClass: player.getDiagnostics().strategyClass === "pending" ? null : player.getDiagnostics().strategyClass,
|
|
184
|
+
reason,
|
|
185
|
+
currentTime,
|
|
186
|
+
diagnostics: player.getDiagnostics()
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
player.on("tracks", ({ video: _v, audio, subtitle }) => {
|
|
190
|
+
this._audioTracks = audio;
|
|
191
|
+
this._subtitleTracks = subtitle;
|
|
192
|
+
this._dispatch("trackschange", {
|
|
193
|
+
audioTracks: audio,
|
|
194
|
+
subtitleTracks: subtitle
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
player.on("error", (err) => {
|
|
198
|
+
this._dispatchError(err);
|
|
199
|
+
});
|
|
200
|
+
player.on("timeupdate", ({ currentTime }) => {
|
|
201
|
+
this._dispatch("timeupdate", { currentTime });
|
|
202
|
+
});
|
|
203
|
+
player.on("ended", () => {
|
|
204
|
+
this._dispatch("ended", {});
|
|
205
|
+
});
|
|
206
|
+
player.on("ready", () => {
|
|
207
|
+
this._dispatch("ready", { diagnostics: player.getDiagnostics() });
|
|
208
|
+
if (this._pendingSeek != null) {
|
|
209
|
+
const t = this._pendingSeek;
|
|
210
|
+
this._pendingSeek = null;
|
|
211
|
+
void player.seek(t).catch(() => {
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (this._pendingPlay) {
|
|
215
|
+
this._pendingPlay = false;
|
|
216
|
+
void player.play().catch(() => {
|
|
217
|
+
});
|
|
218
|
+
} else if (this.autoplay) {
|
|
219
|
+
void player.play().catch(() => {
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Tear down the active player and reset runtime state. Idempotent.
|
|
226
|
+
* If `currentBootstrapId` is provided, the bootstrap counter is NOT
|
|
227
|
+
* incremented (used by `_bootstrap()` to avoid invalidating itself).
|
|
228
|
+
*/
|
|
229
|
+
async _teardown(currentBootstrapId) {
|
|
230
|
+
if (currentBootstrapId == null) {
|
|
231
|
+
this._bootstrapId++;
|
|
232
|
+
}
|
|
233
|
+
const player = this._player;
|
|
234
|
+
this._player = null;
|
|
235
|
+
this._strategy = null;
|
|
236
|
+
this._strategyClass = null;
|
|
237
|
+
this._audioTracks = [];
|
|
238
|
+
this._subtitleTracks = [];
|
|
239
|
+
if (player) {
|
|
240
|
+
try {
|
|
241
|
+
await player.destroy();
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// ── Public properties ──────────────────────────────────────────────────
|
|
247
|
+
get src() {
|
|
248
|
+
return this._src;
|
|
249
|
+
}
|
|
250
|
+
set src(value) {
|
|
251
|
+
if (value == null) {
|
|
252
|
+
this.removeAttribute("src");
|
|
253
|
+
} else {
|
|
254
|
+
this.setAttribute("src", value);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
get source() {
|
|
258
|
+
return this._source;
|
|
259
|
+
}
|
|
260
|
+
set source(value) {
|
|
261
|
+
if (value === this._source && this._src == null) return;
|
|
262
|
+
this._source = value;
|
|
263
|
+
if (value != null) {
|
|
264
|
+
this._src = null;
|
|
265
|
+
if (this.hasAttribute("src")) {
|
|
266
|
+
this._suppressSrcAttrCallback = true;
|
|
267
|
+
try {
|
|
268
|
+
this.removeAttribute("src");
|
|
269
|
+
} finally {
|
|
270
|
+
this._suppressSrcAttrCallback = false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
this._onSourceChanged();
|
|
275
|
+
}
|
|
276
|
+
get autoplay() {
|
|
277
|
+
return this.hasAttribute("autoplay");
|
|
278
|
+
}
|
|
279
|
+
set autoplay(value) {
|
|
280
|
+
if (value) this.setAttribute("autoplay", "");
|
|
281
|
+
else this.removeAttribute("autoplay");
|
|
282
|
+
}
|
|
283
|
+
get muted() {
|
|
284
|
+
return this.hasAttribute("muted");
|
|
285
|
+
}
|
|
286
|
+
set muted(value) {
|
|
287
|
+
if (value) this.setAttribute("muted", "");
|
|
288
|
+
else this.removeAttribute("muted");
|
|
289
|
+
}
|
|
290
|
+
get loop() {
|
|
291
|
+
return this.hasAttribute("loop");
|
|
292
|
+
}
|
|
293
|
+
set loop(value) {
|
|
294
|
+
if (value) this.setAttribute("loop", "");
|
|
295
|
+
else this.removeAttribute("loop");
|
|
296
|
+
}
|
|
297
|
+
get preload() {
|
|
298
|
+
const v = this.getAttribute("preload");
|
|
299
|
+
return v === "none" || v === "metadata" || v === "auto" ? v : "auto";
|
|
300
|
+
}
|
|
301
|
+
set preload(value) {
|
|
302
|
+
this.setAttribute("preload", value);
|
|
303
|
+
}
|
|
304
|
+
get diagnostics() {
|
|
305
|
+
return this.hasAttribute("diagnostics");
|
|
306
|
+
}
|
|
307
|
+
set diagnostics(value) {
|
|
308
|
+
if (value) this.setAttribute("diagnostics", "");
|
|
309
|
+
else this.removeAttribute("diagnostics");
|
|
310
|
+
}
|
|
311
|
+
get preferredStrategy() {
|
|
312
|
+
return this._preferredStrategy;
|
|
313
|
+
}
|
|
314
|
+
set preferredStrategy(value) {
|
|
315
|
+
if (PREFERRED_STRATEGY_VALUES.has(value)) {
|
|
316
|
+
this.setAttribute("preferstrategy", value);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
get currentTime() {
|
|
320
|
+
return this._player?.getCurrentTime() ?? 0;
|
|
321
|
+
}
|
|
322
|
+
set currentTime(value) {
|
|
323
|
+
if (this._player) {
|
|
324
|
+
void this._player.seek(value).catch(() => {
|
|
325
|
+
});
|
|
326
|
+
} else {
|
|
327
|
+
this._pendingSeek = value;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
get duration() {
|
|
331
|
+
return this._player?.getDuration() ?? NaN;
|
|
332
|
+
}
|
|
333
|
+
get paused() {
|
|
334
|
+
return this._videoEl.paused;
|
|
335
|
+
}
|
|
336
|
+
get ended() {
|
|
337
|
+
return this._videoEl.ended;
|
|
338
|
+
}
|
|
339
|
+
get readyState() {
|
|
340
|
+
return this._videoEl.readyState;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Buffered time ranges for the active source. Mirrors the standard
|
|
344
|
+
* `<video>.buffered` `TimeRanges` API. For the native and remux strategies
|
|
345
|
+
* this reflects the underlying SourceBuffer / progressive download state.
|
|
346
|
+
* For the hybrid and fallback (canvas-rendered) strategies it currently
|
|
347
|
+
* returns an empty TimeRanges; v1.1 will synthesize a coarse range from
|
|
348
|
+
* the decoder's read position.
|
|
349
|
+
*/
|
|
350
|
+
get buffered() {
|
|
351
|
+
return this._videoEl.buffered;
|
|
352
|
+
}
|
|
353
|
+
get strategy() {
|
|
354
|
+
return this._strategy;
|
|
355
|
+
}
|
|
356
|
+
get strategyClass() {
|
|
357
|
+
return this._strategyClass;
|
|
358
|
+
}
|
|
359
|
+
get player() {
|
|
360
|
+
return this._player;
|
|
361
|
+
}
|
|
362
|
+
get audioTracks() {
|
|
363
|
+
return this._audioTracks;
|
|
364
|
+
}
|
|
365
|
+
get subtitleTracks() {
|
|
366
|
+
return this._subtitleTracks;
|
|
367
|
+
}
|
|
368
|
+
// ── Public methods ─────────────────────────────────────────────────────
|
|
369
|
+
/** Force a (re-)bootstrap if a source is currently set. */
|
|
370
|
+
async load() {
|
|
371
|
+
if (this._destroyed) return;
|
|
372
|
+
const source = this._activeSource();
|
|
373
|
+
if (source == null) return;
|
|
374
|
+
await this._bootstrap(source);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Begin or resume playback. If the player isn't ready yet, the call is
|
|
378
|
+
* queued and applied once `ready` fires.
|
|
379
|
+
*/
|
|
380
|
+
async play() {
|
|
381
|
+
if (this._destroyed) return;
|
|
382
|
+
if (this._player) {
|
|
383
|
+
await this._player.play();
|
|
384
|
+
} else {
|
|
385
|
+
this._pendingPlay = true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
pause() {
|
|
389
|
+
if (this._destroyed) return;
|
|
390
|
+
this._pendingPlay = false;
|
|
391
|
+
this._player?.pause();
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Tear down the element permanently. After destroy(), the element ignores
|
|
395
|
+
* all method calls and attribute changes.
|
|
396
|
+
*/
|
|
397
|
+
async destroy() {
|
|
398
|
+
if (this._destroyed) return;
|
|
399
|
+
this._destroyed = true;
|
|
400
|
+
await this._teardown();
|
|
401
|
+
this._dispatch("destroy", {});
|
|
402
|
+
}
|
|
403
|
+
async setAudioTrack(id) {
|
|
404
|
+
if (this._destroyed || !this._player) return;
|
|
405
|
+
await this._player.setAudioTrack(id);
|
|
406
|
+
}
|
|
407
|
+
async setSubtitleTrack(id) {
|
|
408
|
+
if (this._destroyed || !this._player) return;
|
|
409
|
+
await this._player.setSubtitleTrack(id);
|
|
410
|
+
}
|
|
411
|
+
getDiagnostics() {
|
|
412
|
+
return this._player?.getDiagnostics() ?? null;
|
|
413
|
+
}
|
|
414
|
+
// ── Event helpers ──────────────────────────────────────────────────────
|
|
415
|
+
_dispatch(name, detail) {
|
|
416
|
+
this.dispatchEvent(new CustomEvent(name, { detail, bubbles: false }));
|
|
417
|
+
}
|
|
418
|
+
_dispatchError(err) {
|
|
419
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
420
|
+
this._dispatch("error", { error, diagnostics: this._player?.getDiagnostics() ?? null });
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// src/element.ts
|
|
425
|
+
if (typeof customElements !== "undefined" && !customElements.get("avbridge-player")) {
|
|
426
|
+
customElements.define("avbridge-player", AvbridgePlayerElement);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export { AvbridgePlayerElement };
|
|
430
|
+
//# sourceMappingURL=element.js.map
|
|
431
|
+
//# sourceMappingURL=element.js.map
|
|
@@ -0,0 +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"]}
|