@maravilla-labs/frames 0.3.2 → 0.4.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/README.md CHANGED
@@ -115,15 +115,25 @@ a WAAPI `Animation` object via `Element.getAnimations()`, and drives
115
115
  its `currentTime` exactly like every other slot. The visual is the
116
116
  class's native CSS animation; the timing is yours to control.
117
117
 
118
- The stylesheet that defines the keyframes must be loaded before
119
- `defineTimeline` runs. The simplest pattern is to `await` the page
120
- `load` event first inside a `<script type="module">`:
118
+ The stylesheet that defines the keyframes must be loaded before the
119
+ animations are materialised but **you don't need to do anything for
120
+ that**. `defineTimeline` waits internally for render-blocking
121
+ stylesheets and fonts to be ready before it reads the keyframes, so you
122
+ can just call it as soon as your module runs:
121
123
 
122
124
  ```ts
123
- await new Promise(r => addEventListener("load", r, { once: true }));
124
125
  defineTimeline({ /* ... */ });
125
126
  ```
126
127
 
128
+ > **Do not** gate `defineTimeline` behind the window `load` event
129
+ > (`await new Promise(r => addEventListener("load", r))`). `load` is also
130
+ > held open by `<video preload>` and `<img>`, which on a media-heavy page
131
+ > can take long enough to blow past the Maravilla renderer's readiness
132
+ > budget and fail the render with "timeline never became available."
133
+ > `defineTimeline` already waits for exactly what it needs (stylesheets +
134
+ > fonts) and nothing it doesn't (media). If you need that signal yourself,
135
+ > import `stylesheetsReady()`.
136
+
127
137
  This works for any library that exposes its motion as CSS classes
128
138
  plus shipped `@keyframes` — Animate.css, Magic, your own custom
129
139
  keyframes — not just Animate.css specifically.
package/dist/index.d.ts CHANGED
@@ -67,5 +67,26 @@ declare global {
67
67
  __mvFramesRendererPresent?: boolean;
68
68
  }
69
69
  }
70
+ /**
71
+ * Declare the page's timeline. **Safe to call as soon as your module runs** —
72
+ * it internally waits for render-blocking stylesheets and fonts to be ready
73
+ * (so CSS keyframes, e.g. Animate.css, are present before animations are
74
+ * materialised) and only then registers `window.__mvFrames`.
75
+ *
76
+ * It deliberately does **NOT** wait for the window `load` event or for media
77
+ * (`<video preload>`, `<img>`). Those can take a long time — long enough to
78
+ * blow past the Maravilla renderer's readiness budget — and aren't needed to
79
+ * build the timeline. **Do not** gate this behind
80
+ * `window.addEventListener('load', …)`; that was an old recommendation and is
81
+ * the classic cause of "renderer timed out waiting for the timeline" on pages
82
+ * with video. `defineTimeline` now owns that readiness so every app gets it
83
+ * right by default.
84
+ */
70
85
  export declare function defineTimeline(schema: TimelineSchema): void;
86
+ /**
87
+ * Resolve once the document's render-blocking stylesheets are applied and web
88
+ * fonts are ready — **without** waiting on the window `load` event or media.
89
+ * Exposed for advanced callers; [`defineTimeline`] already awaits it.
90
+ */
91
+ export declare function stylesheetsReady(): Promise<void>;
71
92
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,yDAAyD;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,uDAAuD;IACvD,IAAI,EAAE,OAAO,CAAC;IACd,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,gCAAgC;IAChC,IAAI,EAAE,KAAK,CAAC;IACZ,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,cAAc,GAAG,UAAU,GAAG,aAAa,CAAC;AAChE,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,KAAK,EAAE,CAAA;CAAE,CAAC;AAEzE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,EAAE;YAAE,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAC;QACrD,yBAAyB,CAAC,EAAE,OAAO,CAAC;KACrC;CACF;AAaD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CA6H3D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,yDAAyD;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,uDAAuD;IACvD,IAAI,EAAE,OAAO,CAAC;IACd,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,gCAAgC;IAChC,IAAI,EAAE,KAAK,CAAC;IACZ,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,cAAc,GAAG,UAAU,GAAG,aAAa,CAAC;AAChE,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,KAAK,EAAE,CAAA;CAAE,CAAC;AAEzE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,EAAE;YAAE,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;SAAE,CAAC;QACrD,yBAAyB,CAAC,EAAE,OAAO,CAAC;KACrC;CACF;AAaD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAW3D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CA2BhD"}
package/dist/index.js CHANGED
@@ -7,7 +7,60 @@
7
7
  * - Inside the Maravilla renderer, the timeline is stepped frame by
8
8
  * frame to produce deterministic video output.
9
9
  */
10
+ /**
11
+ * Declare the page's timeline. **Safe to call as soon as your module runs** —
12
+ * it internally waits for render-blocking stylesheets and fonts to be ready
13
+ * (so CSS keyframes, e.g. Animate.css, are present before animations are
14
+ * materialised) and only then registers `window.__mvFrames`.
15
+ *
16
+ * It deliberately does **NOT** wait for the window `load` event or for media
17
+ * (`<video preload>`, `<img>`). Those can take a long time — long enough to
18
+ * blow past the Maravilla renderer's readiness budget — and aren't needed to
19
+ * build the timeline. **Do not** gate this behind
20
+ * `window.addEventListener('load', …)`; that was an old recommendation and is
21
+ * the classic cause of "renderer timed out waiting for the timeline" on pages
22
+ * with video. `defineTimeline` now owns that readiness so every app gets it
23
+ * right by default.
24
+ */
10
25
  export function defineTimeline(schema) {
26
+ stylesheetsReady()
27
+ .then(() => installTimeline(schema))
28
+ .catch((err) => {
29
+ // Surface in the page console (the renderer captures console output) so
30
+ // a bad selector / missing keyframes is debuggable. On failure
31
+ // `__mvFrames` simply never registers and the renderer reports a clean
32
+ // readiness timeout rather than hanging.
33
+ // eslint-disable-next-line no-console
34
+ console.error("[@maravilla-labs/frames] defineTimeline failed:", err);
35
+ });
36
+ }
37
+ /**
38
+ * Resolve once the document's render-blocking stylesheets are applied and web
39
+ * fonts are ready — **without** waiting on the window `load` event or media.
40
+ * Exposed for advanced callers; [`defineTimeline`] already awaits it.
41
+ */
42
+ export function stylesheetsReady() {
43
+ if (typeof document === "undefined")
44
+ return Promise.resolve();
45
+ const domReady = document.readyState === "loading"
46
+ ? new Promise((resolve) => document.addEventListener("DOMContentLoaded", () => resolve(), { once: true }))
47
+ : Promise.resolve();
48
+ return domReady
49
+ .then(() => {
50
+ // A <link rel="stylesheet"> has a null `.sheet` until it has loaded +
51
+ // parsed. Wait for any still-pending ones; `error` resolves too so a
52
+ // missing/broken stylesheet can't hang the timeline.
53
+ const pending = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
54
+ .filter((link) => !link.sheet)
55
+ .map((link) => new Promise((resolve) => {
56
+ link.addEventListener("load", () => resolve(), { once: true });
57
+ link.addEventListener("error", () => resolve(), { once: true });
58
+ }));
59
+ return Promise.all(pending);
60
+ })
61
+ .then(() => (document.fonts ? document.fonts.ready.then(() => undefined) : undefined));
62
+ }
63
+ function installTimeline(schema) {
11
64
  const rendererPresent = typeof window !== "undefined" && window.__mvFramesRendererPresent === true;
12
65
  const slots = schema.instructions.flatMap((i) => {
13
66
  const el = document.querySelector(i.selector);
@@ -80,19 +133,28 @@ export function defineTimeline(schema) {
80
133
  });
81
134
  window.__mvFrames = {
82
135
  async applyState(t) {
136
+ const videoWaits = [];
83
137
  for (const s of slots) {
84
138
  if (s.kind === "css-pending")
85
139
  continue; // only relevant in browser preview
86
140
  const local = clamp(t - s.at, 0, s.duration);
87
141
  if (s.kind === "video") {
88
142
  s.el.pause();
89
- s.el.currentTime = (s.seek + local) / 1000;
143
+ const target = (s.seek + local) / 1000;
144
+ // Seeking is async: the decoded frame for `target` is NOT ready the
145
+ // instant we assign `currentTime`. The renderer captures each frame
146
+ // with a synchronous CDP `beginFrame` right after `applyState`
147
+ // resolves, so we MUST wait for the seek to land and the frame to
148
+ // decode — otherwise we capture a stale/blank frame, or (under
149
+ // deterministic `--enable-begin-frame-control`) race the media
150
+ // pipeline and wedge/crash the headless tab.
151
+ videoWaits.push(seekVideo(s.el, target));
90
152
  }
91
153
  else {
92
154
  s.anim.currentTime = local;
93
155
  }
94
156
  }
95
- await document.fonts.ready;
157
+ await Promise.all([document.fonts.ready, ...videoWaits]);
96
158
  },
97
159
  };
98
160
  if (!rendererPresent) {
@@ -132,4 +194,51 @@ export function defineTimeline(schema) {
132
194
  function clamp(v, lo, hi) {
133
195
  return v < lo ? lo : v > hi ? hi : v;
134
196
  }
197
+ /**
198
+ * Seek `el` to `targetSec` and resolve only once the seeked frame has
199
+ * actually decoded — so a subsequent capture grabs the right frame instead
200
+ * of racing an in-flight seek.
201
+ *
202
+ * Resolution waits for the `seeked` event, then for one decoded video frame
203
+ * via `requestVideoFrameCallback` where available (fallback: a microtask, so
204
+ * we still yield to the decode). A timeout guards against `seeked` never
205
+ * firing (e.g. seeking to the exact current position, or an unsupported
206
+ * codec) so `applyState` can't hang the whole render. Already-satisfied
207
+ * seeks (`readyState` high enough and time already at target) resolve fast.
208
+ */
209
+ function seekVideo(el, targetSec) {
210
+ const HAVE_CURRENT_DATA = 2;
211
+ // If we're already at (or within a frame of) the target with data ready,
212
+ // there's nothing to wait for — assigning currentTime won't fire `seeked`.
213
+ const alreadyThere = Math.abs(el.currentTime - targetSec) < 1e-3 && el.readyState >= HAVE_CURRENT_DATA;
214
+ el.currentTime = targetSec;
215
+ if (alreadyThere)
216
+ return Promise.resolve();
217
+ return new Promise((resolve) => {
218
+ let done = false;
219
+ const finish = () => {
220
+ if (done)
221
+ return;
222
+ done = true;
223
+ el.removeEventListener("seeked", onSeeked);
224
+ clearTimeout(timer);
225
+ resolve();
226
+ };
227
+ const afterSeeked = () => {
228
+ // Wait for one decoded frame to be presented when the API exists.
229
+ const rvfc = el.requestVideoFrameCallback;
230
+ if (typeof rvfc === "function") {
231
+ rvfc.call(el, () => finish());
232
+ }
233
+ else {
234
+ // No rVFC: yield a microtask so the decode can settle, then finish.
235
+ Promise.resolve().then(finish);
236
+ }
237
+ };
238
+ const onSeeked = () => afterSeeked();
239
+ el.addEventListener("seeked", onSeeked, { once: true });
240
+ // Guard: never let a missing `seeked` stall the render.
241
+ const timer = setTimeout(finish, 2000);
242
+ });
243
+ }
135
244
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maravilla-labs/frames",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Tiny vanilla helper for declaring Web Animations API timelines that the Maravilla runtime renderer drives frame-accurately.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,11 +18,6 @@
18
18
  "README.md",
19
19
  "LICENSE"
20
20
  ],
21
- "scripts": {
22
- "build": "tsc",
23
- "typecheck": "tsc --noEmit",
24
- "prepublishOnly": "tsc"
25
- },
26
21
  "keywords": [
27
22
  "maravilla",
28
23
  "frames",
@@ -37,5 +32,9 @@
37
32
  },
38
33
  "publishConfig": {
39
34
  "access": "public"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "typecheck": "tsc --noEmit"
40
39
  }
41
- }
40
+ }
package/src/index.ts CHANGED
@@ -79,7 +79,69 @@ type CssPendingSlot = {
79
79
  };
80
80
  type Slot = AnimSlot | VideoSlot | CssPendingSlot;
81
81
 
82
+ /**
83
+ * Declare the page's timeline. **Safe to call as soon as your module runs** —
84
+ * it internally waits for render-blocking stylesheets and fonts to be ready
85
+ * (so CSS keyframes, e.g. Animate.css, are present before animations are
86
+ * materialised) and only then registers `window.__mvFrames`.
87
+ *
88
+ * It deliberately does **NOT** wait for the window `load` event or for media
89
+ * (`<video preload>`, `<img>`). Those can take a long time — long enough to
90
+ * blow past the Maravilla renderer's readiness budget — and aren't needed to
91
+ * build the timeline. **Do not** gate this behind
92
+ * `window.addEventListener('load', …)`; that was an old recommendation and is
93
+ * the classic cause of "renderer timed out waiting for the timeline" on pages
94
+ * with video. `defineTimeline` now owns that readiness so every app gets it
95
+ * right by default.
96
+ */
82
97
  export function defineTimeline(schema: TimelineSchema): void {
98
+ stylesheetsReady()
99
+ .then(() => installTimeline(schema))
100
+ .catch((err) => {
101
+ // Surface in the page console (the renderer captures console output) so
102
+ // a bad selector / missing keyframes is debuggable. On failure
103
+ // `__mvFrames` simply never registers and the renderer reports a clean
104
+ // readiness timeout rather than hanging.
105
+ // eslint-disable-next-line no-console
106
+ console.error("[@maravilla-labs/frames] defineTimeline failed:", err);
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Resolve once the document's render-blocking stylesheets are applied and web
112
+ * fonts are ready — **without** waiting on the window `load` event or media.
113
+ * Exposed for advanced callers; [`defineTimeline`] already awaits it.
114
+ */
115
+ export function stylesheetsReady(): Promise<void> {
116
+ if (typeof document === "undefined") return Promise.resolve();
117
+ const domReady =
118
+ document.readyState === "loading"
119
+ ? new Promise<void>((resolve) =>
120
+ document.addEventListener("DOMContentLoaded", () => resolve(), { once: true }),
121
+ )
122
+ : Promise.resolve();
123
+ return domReady
124
+ .then(() => {
125
+ // A <link rel="stylesheet"> has a null `.sheet` until it has loaded +
126
+ // parsed. Wait for any still-pending ones; `error` resolves too so a
127
+ // missing/broken stylesheet can't hang the timeline.
128
+ const pending = Array.from(
129
+ document.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]'),
130
+ )
131
+ .filter((link) => !link.sheet)
132
+ .map(
133
+ (link) =>
134
+ new Promise<void>((resolve) => {
135
+ link.addEventListener("load", () => resolve(), { once: true });
136
+ link.addEventListener("error", () => resolve(), { once: true });
137
+ }),
138
+ );
139
+ return Promise.all(pending);
140
+ })
141
+ .then(() => (document.fonts ? document.fonts.ready.then(() => undefined) : undefined));
142
+ }
143
+
144
+ function installTimeline(schema: TimelineSchema): void {
83
145
  const rendererPresent =
84
146
  typeof window !== "undefined" && window.__mvFramesRendererPresent === true;
85
147
 
@@ -159,17 +221,26 @@ export function defineTimeline(schema: TimelineSchema): void {
159
221
 
160
222
  window.__mvFrames = {
161
223
  async applyState(t: number) {
224
+ const videoWaits: Promise<void>[] = [];
162
225
  for (const s of slots) {
163
226
  if (s.kind === "css-pending") continue; // only relevant in browser preview
164
227
  const local = clamp(t - s.at, 0, s.duration);
165
228
  if (s.kind === "video") {
166
229
  s.el.pause();
167
- s.el.currentTime = (s.seek + local) / 1000;
230
+ const target = (s.seek + local) / 1000;
231
+ // Seeking is async: the decoded frame for `target` is NOT ready the
232
+ // instant we assign `currentTime`. The renderer captures each frame
233
+ // with a synchronous CDP `beginFrame` right after `applyState`
234
+ // resolves, so we MUST wait for the seek to land and the frame to
235
+ // decode — otherwise we capture a stale/blank frame, or (under
236
+ // deterministic `--enable-begin-frame-control`) race the media
237
+ // pipeline and wedge/crash the headless tab.
238
+ videoWaits.push(seekVideo(s.el, target));
168
239
  } else {
169
240
  s.anim.currentTime = local;
170
241
  }
171
242
  }
172
- await document.fonts.ready;
243
+ await Promise.all([document.fonts.ready, ...videoWaits]);
173
244
  },
174
245
  };
175
246
 
@@ -209,3 +280,52 @@ export function defineTimeline(schema: TimelineSchema): void {
209
280
  function clamp(v: number, lo: number, hi: number): number {
210
281
  return v < lo ? lo : v > hi ? hi : v;
211
282
  }
283
+
284
+ /**
285
+ * Seek `el` to `targetSec` and resolve only once the seeked frame has
286
+ * actually decoded — so a subsequent capture grabs the right frame instead
287
+ * of racing an in-flight seek.
288
+ *
289
+ * Resolution waits for the `seeked` event, then for one decoded video frame
290
+ * via `requestVideoFrameCallback` where available (fallback: a microtask, so
291
+ * we still yield to the decode). A timeout guards against `seeked` never
292
+ * firing (e.g. seeking to the exact current position, or an unsupported
293
+ * codec) so `applyState` can't hang the whole render. Already-satisfied
294
+ * seeks (`readyState` high enough and time already at target) resolve fast.
295
+ */
296
+ function seekVideo(el: HTMLVideoElement, targetSec: number): Promise<void> {
297
+ const HAVE_CURRENT_DATA = 2;
298
+ // If we're already at (or within a frame of) the target with data ready,
299
+ // there's nothing to wait for — assigning currentTime won't fire `seeked`.
300
+ const alreadyThere =
301
+ Math.abs(el.currentTime - targetSec) < 1e-3 && el.readyState >= HAVE_CURRENT_DATA;
302
+ el.currentTime = targetSec;
303
+ if (alreadyThere) return Promise.resolve();
304
+
305
+ return new Promise<void>((resolve) => {
306
+ let done = false;
307
+ const finish = () => {
308
+ if (done) return;
309
+ done = true;
310
+ el.removeEventListener("seeked", onSeeked);
311
+ clearTimeout(timer);
312
+ resolve();
313
+ };
314
+ const afterSeeked = () => {
315
+ // Wait for one decoded frame to be presented when the API exists.
316
+ const rvfc = (el as unknown as {
317
+ requestVideoFrameCallback?: (cb: () => void) => number;
318
+ }).requestVideoFrameCallback;
319
+ if (typeof rvfc === "function") {
320
+ rvfc.call(el, () => finish());
321
+ } else {
322
+ // No rVFC: yield a microtask so the decode can settle, then finish.
323
+ Promise.resolve().then(finish);
324
+ }
325
+ };
326
+ const onSeeked = () => afterSeeked();
327
+ el.addEventListener("seeked", onSeeked, { once: true });
328
+ // Guard: never let a missing `seeked` stall the render.
329
+ const timer = setTimeout(finish, 2000);
330
+ });
331
+ }