@framv/video 0.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/dist/cdn.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import "./element.js";
2
+ export { FramvVideoElement } from "./element.js";
3
+ export { Player } from "./player.js";
4
+ //# sourceMappingURL=cdn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
package/dist/cdn.js ADDED
@@ -0,0 +1,4 @@
1
+ import "./element.js";
2
+ export { FramvVideoElement } from "./element.js";
3
+ export { Player } from "./player.js";
4
+ //# sourceMappingURL=cdn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdn.js","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { Player } from "./player.js";
2
+ /**
3
+ * `<framv-video>` — renders HTML content as a video.
4
+ *
5
+ * Attributes:
6
+ * width — canvas width in pixels (default: 1920)
7
+ * height — canvas height in pixels (default: 1080)
8
+ * fps — frames per second (default: 30)
9
+ * duration — total duration in seconds (default: auto from animations)
10
+ * format — export format: mp4, webm (default: mp4)
11
+ * quality — export quality 0..1 (default: 0.95)
12
+ * controls — always show toolbar
13
+ * autoplay — start playing immediately
14
+ * loop — loop playback
15
+ *
16
+ * @example
17
+ * ```html
18
+ * <framv-video width="1920" height="1080" fps="30" duration="5" controls>
19
+ * <div>Your content here</div>
20
+ * </framv-video>
21
+ * ```
22
+ */
23
+ export declare class FramvVideoElement extends HTMLElement {
24
+ static observedAttributes: string[];
25
+ private _player;
26
+ private _stage;
27
+ private _toolbar;
28
+ private _btn;
29
+ private _range;
30
+ private _time;
31
+ private _exportBtn;
32
+ private _shadow;
33
+ private _dragging;
34
+ private _exporting;
35
+ constructor();
36
+ get width(): number;
37
+ get height(): number;
38
+ get fps(): number;
39
+ get duration(): number;
40
+ get format(): string;
41
+ get quality(): number;
42
+ get player(): Player | null;
43
+ connectedCallback(): void;
44
+ disconnectedCallback(): void;
45
+ attributeChangedCallback(name: string, _old: string, value: string): void;
46
+ private _adaptSize;
47
+ private _export;
48
+ }
49
+ //# sourceMappingURL=element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA8DrC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;IAChD,MAAM,CAAC,kBAAkB,WAA6C;IAEtE,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;;IAO3B,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD,IAAI,GAAG,IAAI,MAAM,CAEhB;IACD,IAAI,QAAQ,IAAI,MAAM,CAErB;IACD,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD,IAAI,OAAO,IAAI,MAAM,CAEpB;IACD,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,CAE1B;IAED,iBAAiB,IAAI,IAAI;IAgFzB,oBAAoB,IAAI,IAAI;IAK5B,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAoBzE,OAAO,CAAC,UAAU;YASJ,OAAO;CAoDtB"}
@@ -0,0 +1,283 @@
1
+ import { exportElement } from "@framv/core";
2
+ import { Player } from "./player.js";
3
+ const STYLES = `
4
+ :host { display: block; position: relative; overflow: hidden; }
5
+ .framv-stage {
6
+ position: relative;
7
+ width: var(--framv-w, 1920px);
8
+ height: var(--framv-h, 1080px);
9
+ transform-origin: top left;
10
+ overflow: hidden;
11
+ }
12
+ .framv-toolbar {
13
+ display: flex; align-items: center; gap: 10px;
14
+ padding: 8px 12px;
15
+ background: rgba(0,0,0,0.7); backdrop-filter: blur(6px);
16
+ color: #fff; font: 13px/1 system-ui, monospace;
17
+ position: absolute; bottom: 0; left: 0; right: 0;
18
+ opacity: 0; transition: opacity 0.2s;
19
+ z-index: 10;
20
+ }
21
+ :host(:hover) .framv-toolbar,
22
+ :host([controls]) .framv-toolbar { opacity: 1; }
23
+ .framv-toolbar button {
24
+ background: none; border: 1px solid rgba(255,255,255,0.2);
25
+ color: inherit; cursor: pointer;
26
+ padding: 4px 10px; font: inherit; border-radius: 4px;
27
+ }
28
+ .framv-toolbar button:hover { background: rgba(255,255,255,0.15); }
29
+ .framv-toolbar .btn-export {
30
+ border-color: #ff79c6; color: #ff79c6;
31
+ }
32
+ input[type=range] {
33
+ flex: 1; accent-color: #ff79c6; cursor: pointer; height: 3px;
34
+ }
35
+ .framv-time { white-space: nowrap; font-variant-numeric: tabular-nums; opacity: 0.8; min-width: 100px; text-align: center; }
36
+ .framv-badge {
37
+ position: absolute; top: 8px; right: 8px;
38
+ background: rgba(0,0,0,0.6); color: #fff;
39
+ padding: 2px 8px; border-radius: 4px;
40
+ font: 11px system-ui; letter-spacing: 0.5px;
41
+ text-transform: uppercase; z-index: 5;
42
+ }
43
+ .framv-exporting-overlay {
44
+ position: absolute; inset: 0;
45
+ background: rgba(0,0,0,0.7);
46
+ display: flex; align-items: center; justify-content: center;
47
+ color: #fff; font: 16px system-ui; z-index: 20;
48
+ }
49
+ .framv-exporting-overlay .spinner {
50
+ width: 32px; height: 32px; border: 3px solid rgba(255,255,255,0.3);
51
+ border-top-color: #ff79c6; border-radius: 50%;
52
+ animation: framv-spin 0.8s linear infinite;
53
+ margin-right: 12px;
54
+ }
55
+ @keyframes framv-spin { to { transform: rotate(360deg); } }
56
+ `;
57
+ const fmt = (s) => {
58
+ const m = Math.floor(s / 60);
59
+ return `${m}:${String(Math.floor(s % 60)).padStart(2, "0")}`;
60
+ };
61
+ /**
62
+ * `<framv-video>` — renders HTML content as a video.
63
+ *
64
+ * Attributes:
65
+ * width — canvas width in pixels (default: 1920)
66
+ * height — canvas height in pixels (default: 1080)
67
+ * fps — frames per second (default: 30)
68
+ * duration — total duration in seconds (default: auto from animations)
69
+ * format — export format: mp4, webm (default: mp4)
70
+ * quality — export quality 0..1 (default: 0.95)
71
+ * controls — always show toolbar
72
+ * autoplay — start playing immediately
73
+ * loop — loop playback
74
+ *
75
+ * @example
76
+ * ```html
77
+ * <framv-video width="1920" height="1080" fps="30" duration="5" controls>
78
+ * <div>Your content here</div>
79
+ * </framv-video>
80
+ * ```
81
+ */
82
+ export class FramvVideoElement extends HTMLElement {
83
+ static observedAttributes = ["duration", "width", "height", "format"];
84
+ _player = null;
85
+ _stage;
86
+ _toolbar;
87
+ _btn;
88
+ _range;
89
+ _time;
90
+ _exportBtn;
91
+ _shadow;
92
+ _dragging = false;
93
+ _exporting = false;
94
+ constructor() {
95
+ super();
96
+ this._shadow = this.attachShadow({ mode: "open" });
97
+ }
98
+ get width() {
99
+ return parseInt(this.getAttribute("width") ?? "1920");
100
+ }
101
+ get height() {
102
+ return parseInt(this.getAttribute("height") ?? "1080");
103
+ }
104
+ get fps() {
105
+ return parseInt(this.getAttribute("fps") ?? "30");
106
+ }
107
+ get duration() {
108
+ return parseFloat(this.getAttribute("duration") ?? "5");
109
+ }
110
+ get format() {
111
+ return this.getAttribute("format") ?? "mp4";
112
+ }
113
+ get quality() {
114
+ return parseFloat(this.getAttribute("quality") ?? "0.95");
115
+ }
116
+ get player() {
117
+ return this._player;
118
+ }
119
+ connectedCallback() {
120
+ const w = this.width;
121
+ const h = this.height;
122
+ this._shadow.innerHTML = `
123
+ <style>${STYLES}</style>
124
+ <div class="framv-badge">framv video · ${this.format.toUpperCase()} · ${w}x${h}</div>
125
+ <div class="framv-stage" style="--framv-w:${w}px;--framv-h:${h}px">
126
+ <slot></slot>
127
+ </div>
128
+ <div class="framv-toolbar">
129
+ <button class="btn-play">▶</button>
130
+ <input type="range" min="0" max="1000" value="0" step="1">
131
+ <span class="framv-time">0:00 / 0:00</span>
132
+ <button class="btn-export">⬇ Export ${this.format.toUpperCase()}</button>
133
+ </div>
134
+ `;
135
+ this._stage = this._shadow.querySelector(".framv-stage");
136
+ this._toolbar = this._shadow.querySelector(".framv-toolbar");
137
+ this._btn = this._shadow.querySelector(".btn-play");
138
+ this._range = this._shadow.querySelector("input");
139
+ this._time = this._shadow.querySelector(".framv-time");
140
+ this._exportBtn = this._shadow.querySelector(".btn-export");
141
+ // Adapt stage size to container
142
+ this._adaptSize();
143
+ this._player = new Player(this);
144
+ const d = this.duration;
145
+ if (d > 0)
146
+ this._player.setDuration(d);
147
+ else
148
+ this._player.setDuration(5);
149
+ this._player.on("play", () => {
150
+ this._btn.textContent = "⏸";
151
+ });
152
+ this._player.on("pause", () => {
153
+ this._btn.textContent = "▶";
154
+ });
155
+ this._player.on("ended", () => {
156
+ this._btn.textContent = "↺";
157
+ if (this.hasAttribute("loop")) {
158
+ this._player.seek(0).then(() => this._player.play());
159
+ }
160
+ });
161
+ this._player.on("timeupdate", (t = 0) => {
162
+ if (!this._dragging)
163
+ this._range.value = String(Math.round((t / (this._player.duration || 1)) * 1000));
164
+ this._time.textContent = `${fmt(t)} / ${fmt(this._player.duration)}`;
165
+ });
166
+ this._btn.addEventListener("click", () => {
167
+ if (this._exporting)
168
+ return;
169
+ if (this._player.playing) {
170
+ this._player.pause();
171
+ }
172
+ else if (this._player.currentTime >= this._player.duration && this._player.duration > 0) {
173
+ this._player.seek(0).then(() => this._player.play());
174
+ }
175
+ else {
176
+ this._player.play();
177
+ }
178
+ });
179
+ this._range.addEventListener("mousedown", () => {
180
+ this._dragging = true;
181
+ });
182
+ this._range.addEventListener("mouseup", () => {
183
+ this._dragging = false;
184
+ });
185
+ this._range.addEventListener("input", () => {
186
+ const t = (Number(this._range.value) / 1000) * (this._player.duration || 1);
187
+ this._player.seek(t);
188
+ });
189
+ this._exportBtn.addEventListener("click", () => this._export());
190
+ if (this.hasAttribute("autoplay"))
191
+ this._player.play();
192
+ // Resize observer
193
+ new ResizeObserver(() => this._adaptSize()).observe(this);
194
+ }
195
+ disconnectedCallback() {
196
+ this._player?.destroy();
197
+ this._player = null;
198
+ }
199
+ attributeChangedCallback(name, _old, value) {
200
+ if (name === "duration" && this._player) {
201
+ this._player.setDuration(parseFloat(value) || 0);
202
+ }
203
+ if (name === "format" && this._toolbar) {
204
+ const badge = this._shadow.querySelector(".framv-badge");
205
+ if (badge)
206
+ badge.textContent = `framv video · ${this.format.toUpperCase()} · ${this.width}x${this.height}`;
207
+ this._exportBtn.textContent = `⬇ Export ${this.format.toUpperCase()}`;
208
+ }
209
+ if ((name === "width" || name === "height") && this._stage) {
210
+ const w = this.width;
211
+ const h = this.height;
212
+ this._stage.style.setProperty("--framv-w", `${w}px`);
213
+ this._stage.style.setProperty("--framv-h", `${h}px`);
214
+ const badge = this._shadow.querySelector(".framv-badge");
215
+ if (badge)
216
+ badge.textContent = `framv video · ${this.format.toUpperCase()} · ${w}x${h}`;
217
+ this._adaptSize();
218
+ }
219
+ }
220
+ _adaptSize() {
221
+ const w = this.width;
222
+ const h = this.height;
223
+ const containerW = this.clientWidth || w;
224
+ const scale = Math.min(containerW / w, 1);
225
+ this._stage.style.transform = `scale(${scale})`;
226
+ this.style.minHeight = `${h * scale}px`;
227
+ }
228
+ async _export() {
229
+ if (this._exporting)
230
+ return;
231
+ this._exporting = true;
232
+ const overlay = document.createElement("div");
233
+ overlay.className = "framv-exporting-overlay";
234
+ overlay.innerHTML = '<div class="spinner"></div><span>Exporting... 0%</span>';
235
+ const label = overlay.querySelector("span");
236
+ this._shadow.appendChild(overlay);
237
+ // Build a temporary container with light DOM content for the core renderer
238
+ const container = document.createElement("div");
239
+ container.style.width = `${this.width}px`;
240
+ container.style.height = `${this.height}px`;
241
+ container.style.position = "relative";
242
+ container.style.overflow = "hidden";
243
+ Array.from(this.children).forEach((child) => container.appendChild(child.cloneNode(true)));
244
+ try {
245
+ const blob = await exportElement({
246
+ element: container,
247
+ settings: {
248
+ format: this.format,
249
+ fps: this.fps,
250
+ start: 0,
251
+ end: this.duration,
252
+ width: this.width,
253
+ height: this.height,
254
+ quality: this.quality,
255
+ },
256
+ onProgress: (p) => {
257
+ label.textContent = `Exporting... ${Math.round(p * 100)}%`;
258
+ return true;
259
+ },
260
+ });
261
+ const url = URL.createObjectURL(blob);
262
+ const a = document.createElement("a");
263
+ a.href = url;
264
+ a.download = `framv-video.${this.format}`;
265
+ this._shadow.appendChild(a);
266
+ a.click();
267
+ a.remove();
268
+ URL.revokeObjectURL(url);
269
+ }
270
+ catch (err) {
271
+ console.error("Export failed:", err);
272
+ label.textContent = "Export failed. Check console.";
273
+ }
274
+ finally {
275
+ overlay.remove();
276
+ this._exporting = false;
277
+ }
278
+ }
279
+ }
280
+ if (!customElements.get("framv-video")) {
281
+ customElements.define("framv-video", FramvVideoElement);
282
+ }
283
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.js","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDd,CAAC;AAEF,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE;IACxB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7B,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAC/D,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,iBAAkB,SAAQ,WAAW;IAChD,MAAM,CAAC,kBAAkB,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE9D,OAAO,GAAkB,IAAI,CAAC;IAC9B,MAAM,CAAkB;IACxB,QAAQ,CAAkB;IAC1B,IAAI,CAAqB;IACzB,MAAM,CAAoB;IAC1B,KAAK,CAAmB;IACxB,UAAU,CAAqB;IAC/B,OAAO,CAAa;IACpB,SAAS,GAAG,KAAK,CAAC;IAClB,UAAU,GAAG,KAAK,CAAC;IAE3B;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,KAAK;QACP,OAAO,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,MAAM;QACR,OAAO,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,GAAG;QACL,OAAO,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO;QACT,OAAO,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,iBAAiB;QACf,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QAEtB,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG;eACd,MAAM;+CAC0B,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC;kDAClC,CAAC,gBAAgB,CAAC;;;;;;;8CAOtB,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE;;KAElE,CAAC;QAEF,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,cAAc,CAAE,CAAC;QAC1D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAE,CAAC;QAC9D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAE,CAAC;QACrD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAE,CAAC;QACnD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,aAAa,CAAE,CAAC;QACxD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,aAAa,CAAE,CAAC;QAE7D,gCAAgC;QAChC,IAAI,CAAC,UAAU,EAAE,CAAC;QAElB,IAAI,CAAC,OAAO,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;;YAClC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAEjC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YAC3B,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC5B,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC5B,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;YAC5B,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9B,IAAI,CAAC,OAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE;YACtC,IAAI,CAAC,IAAI,CAAC,SAAS;gBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,OAAQ,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YACxG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxE,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YACvC,IAAI,IAAI,CAAC,UAAU;gBAAE,OAAO;YAC5B,IAAI,IAAI,CAAC,OAAQ,CAAC,OAAO,EAAE,CAAC;gBAC1B,IAAI,CAAC,OAAQ,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC;iBAAM,IAAI,IAAI,CAAC,OAAQ,CAAC,WAAW,IAAI,IAAI,CAAC,OAAQ,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAQ,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;gBAC7F,IAAI,CAAC,OAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAQ,CAAC,IAAI,EAAE,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,GAAG,EAAE;YAC7C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,GAAG,EAAE;YAC3C,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YACzC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAQ,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;YAC7E,IAAI,CAAC,OAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAEhE,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAEvD,kBAAkB;QAClB,IAAI,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,oBAAoB;QAClB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED,wBAAwB,CAAC,IAAY,EAAE,IAAY,EAAE,KAAa;QAChE,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACxC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,cAAc,CAAgB,CAAC;YACxE,IAAI,KAAK;gBAAE,KAAK,CAAC,WAAW,GAAG,iBAAiB,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC3G,IAAI,CAAC,UAAU,CAAC,WAAW,GAAG,YAAY,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;QACxE,CAAC;QACD,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC3D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YACrB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;YACtB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YACrD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,cAAc,CAAgB,CAAC;YACxE,IAAI,KAAK;gBAAE,KAAK,CAAC,WAAW,GAAG,iBAAiB,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACxF,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC;IACH,CAAC;IAEO,UAAU;QAChB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,SAAS,KAAK,GAAG,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,KAAK,IAAI,CAAC;IAC1C,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QAC5B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QAEvB,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9C,OAAO,CAAC,SAAS,GAAG,yBAAyB,CAAC;QAC9C,OAAO,CAAC,SAAS,GAAG,yDAAyD,CAAC;QAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAElC,2EAA2E;QAC3E,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAChD,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC;QAC1C,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC;QAC5C,SAAS,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC;QACtC,SAAS,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAE3F,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC;gBAC/B,OAAO,EAAE,SAAS;gBAClB,QAAQ,EAAE;oBACR,MAAM,EAAE,IAAI,CAAC,MAAwB;oBACrC,GAAG,EAAE,IAAI,CAAC,GAAG;oBACb,KAAK,EAAE,CAAC;oBACR,GAAG,EAAE,IAAI,CAAC,QAAQ;oBAClB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,OAAO,EAAE,IAAI,CAAC,OAAO;iBACtB;gBACD,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;oBAChB,KAAK,CAAC,WAAW,GAAG,gBAAgB,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC;oBAC3D,OAAO,IAAI,CAAC;gBACd,CAAC;aACF,CAAC,CAAC;YAEH,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YACtC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;YACb,CAAC,CAAC,QAAQ,GAAG,eAAe,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1C,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAC5B,CAAC,CAAC,KAAK,EAAE,CAAC;YACV,CAAC,CAAC,MAAM,EAAE,CAAC;YACX,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;YACrC,KAAK,CAAC,WAAW,GAAG,+BAA+B,CAAC;QACtD,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;;AAGH,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;IACvC,cAAc,CAAC,MAAM,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { FramvVideoElement } from "./element.js";
2
+ export { Player } from "./player.js";
3
+ export type { PlayerEvent, PlayerEventCallback } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { FramvVideoElement } from "./element.js";
2
+ export { Player } from "./player.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { PlayerEvent, PlayerEventCallback } from "./types.js";
2
+ /**
3
+ * Player wraps any HTMLElement or SVGSVGElement and provides seek/play/pause
4
+ * controls for its timeline animations.
5
+ *
6
+ * Works with:
7
+ * - CSS @keyframes / Web Animations API on any HTML element
8
+ * - SVG SMIL animations (setCurrentTime / unpauseAnimations / pauseAnimations)
9
+ * - HTMLMediaElement (audio/video) with optional `data-media-start` offset
10
+ * - Custom elements that expose a `draw(time: number)` method
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const player = new Player(document.querySelector('#content')!);
15
+ * player.setDuration(10);
16
+ * await player.seek(2.5);
17
+ * await player.play();
18
+ * player.on('timeupdate', (t) => console.log(t));
19
+ * ```
20
+ */
21
+ export declare class Player {
22
+ /** The root element whose animations this player controls. Can be any HTML element (div, section, etc.) or SVGSVGElement. */
23
+ private _element;
24
+ private _currentTime;
25
+ private _duration;
26
+ private _playing;
27
+ private _rafId;
28
+ private _lastRafTime;
29
+ private _listeners;
30
+ constructor(element: HTMLElement | SVGSVGElement);
31
+ get currentTime(): number;
32
+ get duration(): number;
33
+ get playing(): boolean;
34
+ setDuration(duration: number): void;
35
+ play(): Promise<void>;
36
+ pause(): void;
37
+ seek(time: number): Promise<void>;
38
+ destroy(): void;
39
+ on(event: PlayerEvent, cb: PlayerEventCallback): () => void;
40
+ private _startRafLoop;
41
+ private _stopRafLoop;
42
+ private _playElement;
43
+ private _pauseElement;
44
+ private _seekElement;
45
+ private _playMedia;
46
+ private _seekMedia;
47
+ private _getMediaStartTime;
48
+ private _emit;
49
+ }
50
+ //# sourceMappingURL=player.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,MAAM;IACjB,6HAA6H;IAC7H,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,YAAY,CAAuB;IAE3C,OAAO,CAAC,UAAU,CAAyD;gBAE/D,OAAO,EAAE,WAAW,GAAG,aAAa;IAMhD,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQ7B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,KAAK,IAAI,IAAI;IASP,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASvC,OAAO,IAAI,IAAI;IAOf,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,mBAAmB,GAAG,MAAM,IAAI;IAa3D,OAAO,CAAC,aAAa;IAqCrB,OAAO,CAAC,YAAY;YAUN,YAAY;IAgB1B,OAAO,CAAC,aAAa;YAmBP,YAAY;YAmBZ,UAAU;IAyBxB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,KAAK;CAGd"}
package/dist/player.js ADDED
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Player wraps any HTMLElement or SVGSVGElement and provides seek/play/pause
3
+ * controls for its timeline animations.
4
+ *
5
+ * Works with:
6
+ * - CSS @keyframes / Web Animations API on any HTML element
7
+ * - SVG SMIL animations (setCurrentTime / unpauseAnimations / pauseAnimations)
8
+ * - HTMLMediaElement (audio/video) with optional `data-media-start` offset
9
+ * - Custom elements that expose a `draw(time: number)` method
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const player = new Player(document.querySelector('#content')!);
14
+ * player.setDuration(10);
15
+ * await player.seek(2.5);
16
+ * await player.play();
17
+ * player.on('timeupdate', (t) => console.log(t));
18
+ * ```
19
+ */
20
+ export class Player {
21
+ /** The root element whose animations this player controls. Can be any HTML element (div, section, etc.) or SVGSVGElement. */
22
+ _element;
23
+ _currentTime = 0;
24
+ _duration = 0;
25
+ _playing = false;
26
+ _rafId = null;
27
+ _lastRafTime = null;
28
+ _listeners = new Map();
29
+ constructor(element) {
30
+ this._element = element;
31
+ }
32
+ // ─── State ───────────────────────────────────────────────────────────────
33
+ get currentTime() {
34
+ return this._currentTime;
35
+ }
36
+ get duration() {
37
+ return this._duration;
38
+ }
39
+ get playing() {
40
+ return this._playing;
41
+ }
42
+ setDuration(duration) {
43
+ if (duration >= 0) {
44
+ this._duration = duration;
45
+ }
46
+ }
47
+ // ─── Controls ────────────────────────────────────────────────────────────
48
+ async play() {
49
+ if (this._playing)
50
+ return;
51
+ this._playing = true;
52
+ await this._playElement(this._element);
53
+ this._startRafLoop();
54
+ this._emit("play");
55
+ }
56
+ pause() {
57
+ if (!this._playing)
58
+ return;
59
+ this._playing = false;
60
+ this._stopRafLoop();
61
+ this._pauseElement(this._element);
62
+ this._emit("pause");
63
+ }
64
+ async seek(time) {
65
+ const clamped = this._duration > 0 ? Math.max(0, Math.min(time, this._duration)) : Math.max(0, time);
66
+ this._currentTime = clamped;
67
+ await this._seekElement(this._element, clamped);
68
+ this._emit("seek", clamped);
69
+ this._emit("timeupdate", clamped);
70
+ }
71
+ destroy() {
72
+ this.pause();
73
+ this._listeners.clear();
74
+ }
75
+ // ─── Events ──────────────────────────────────────────────────────────────
76
+ on(event, cb) {
77
+ if (!this._listeners.has(event)) {
78
+ this._listeners.set(event, new Set());
79
+ }
80
+ this._listeners.get(event).add(cb);
81
+ return () => {
82
+ this._listeners.get(event)?.delete(cb);
83
+ };
84
+ }
85
+ // ─── Internal: rAF loop ──────────────────────────────────────────────────
86
+ _startRafLoop() {
87
+ this._lastRafTime = null;
88
+ const tick = (now) => {
89
+ if (!this._playing)
90
+ return;
91
+ if (this._lastRafTime !== null) {
92
+ const delta = (now - this._lastRafTime) / 1000; // seconds
93
+ this._currentTime += delta;
94
+ if (this._duration > 0 && this._currentTime >= this._duration) {
95
+ this._currentTime = this._duration;
96
+ this._emit("timeupdate", this._currentTime);
97
+ this._playing = false;
98
+ this._stopRafLoop();
99
+ this._pauseElement(this._element);
100
+ this._emit("ended");
101
+ return;
102
+ }
103
+ }
104
+ this._lastRafTime = now;
105
+ // Call draw(time) on elements that expose it
106
+ const all = [this._element, ...Array.from(this._element.querySelectorAll("*"))];
107
+ for (const el of all) {
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
+ if (typeof el.draw === "function")
110
+ el.draw(this._currentTime);
111
+ }
112
+ this._emit("timeupdate", this._currentTime);
113
+ this._rafId = requestAnimationFrame(tick);
114
+ };
115
+ this._rafId = requestAnimationFrame(tick);
116
+ }
117
+ _stopRafLoop() {
118
+ if (this._rafId !== null) {
119
+ cancelAnimationFrame(this._rafId);
120
+ this._rafId = null;
121
+ }
122
+ this._lastRafTime = null;
123
+ }
124
+ // ─── Internal: element control ───────────────────────────────────────────
125
+ async _playElement(element) {
126
+ const all = [element, ...Array.from(element.querySelectorAll("*"))];
127
+ for (const el of all) {
128
+ if (el instanceof SVGSVGElement) {
129
+ el.unpauseAnimations();
130
+ }
131
+ else if (el instanceof HTMLMediaElement) {
132
+ await this._playMedia(el);
133
+ }
134
+ else if (el.getAnimations?.().length > 0) {
135
+ el.getAnimations().forEach((a) => a.play());
136
+ }
137
+ }
138
+ // Script-driven elements (custom `draw` method) are handled by the rAF loop
139
+ }
140
+ _pauseElement(element) {
141
+ const all = [element, ...Array.from(element.querySelectorAll("*"))];
142
+ for (const el of all) {
143
+ if (el instanceof SVGSVGElement) {
144
+ el.pauseAnimations();
145
+ }
146
+ else if (el instanceof HTMLMediaElement) {
147
+ el.pause();
148
+ const extEl = el;
149
+ if (extEl._autoplayTimeout) {
150
+ clearTimeout(extEl._autoplayTimeout);
151
+ delete extEl._autoplayTimeout;
152
+ }
153
+ }
154
+ else if (el.getAnimations?.().length > 0) {
155
+ el.getAnimations().forEach((a) => a.pause());
156
+ }
157
+ }
158
+ }
159
+ async _seekElement(element, time) {
160
+ const all = [element, ...Array.from(element.querySelectorAll("*"))];
161
+ const promises = [];
162
+ for (const el of all) {
163
+ if (el instanceof SVGSVGElement) {
164
+ el.setCurrentTime(time);
165
+ }
166
+ else if (el instanceof HTMLMediaElement) {
167
+ promises.push(this._seekMedia(el, time));
168
+ }
169
+ else if (el.getAnimations?.().length > 0) {
170
+ el.getAnimations().forEach((a) => {
171
+ a.currentTime = time * 1000;
172
+ });
173
+ }
174
+ }
175
+ await Promise.all(promises);
176
+ }
177
+ async _playMedia(el) {
178
+ const startTime = this._getMediaStartTime(el);
179
+ const now = this._currentTime;
180
+ if (startTime > 0) {
181
+ if (now >= startTime) {
182
+ await this._seekMedia(el, now);
183
+ await el.play().catch(() => undefined);
184
+ }
185
+ else {
186
+ const extEl = el;
187
+ extEl._autoplayTimeout = setTimeout(async () => {
188
+ await el.play().catch(() => undefined);
189
+ }, (startTime - now) * 1000);
190
+ }
191
+ }
192
+ else if (startTime < 0) {
193
+ await this._seekMedia(el, now);
194
+ await el.play().catch(() => undefined);
195
+ }
196
+ else {
197
+ await el.play().catch(() => undefined);
198
+ }
199
+ }
200
+ _seekMedia(el, containerTime) {
201
+ return new Promise((resolve) => {
202
+ const startTime = this._getMediaStartTime(el);
203
+ let adjusted;
204
+ if (startTime > 0) {
205
+ adjusted = containerTime >= startTime ? containerTime - startTime : 0;
206
+ }
207
+ else if (startTime < 0) {
208
+ adjusted = containerTime + Math.abs(startTime);
209
+ }
210
+ else {
211
+ adjusted = containerTime;
212
+ }
213
+ const target = el.loop && el.duration > 0 ? adjusted % el.duration : Math.min(adjusted, el.duration || 0);
214
+ const onSeeked = () => {
215
+ el.removeEventListener("seeked", onSeeked);
216
+ resolve();
217
+ };
218
+ el.addEventListener("seeked", onSeeked);
219
+ el.currentTime = target;
220
+ });
221
+ }
222
+ _getMediaStartTime(el) {
223
+ const attr = el.getAttribute("data-media-start");
224
+ return attr ? parseFloat(attr) : 0;
225
+ }
226
+ // ─── Internal: emit ──────────────────────────────────────────────────────
227
+ _emit(event, time) {
228
+ this._listeners.get(event)?.forEach((cb) => cb(time));
229
+ }
230
+ }
231
+ //# sourceMappingURL=player.js.map