@editframe/elements 0.18.27-beta.0 → 0.19.2-beta.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.
Files changed (73) hide show
  1. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +10 -0
  2. package/dist/elements/EFMedia/AssetMediaEngine.js +13 -1
  3. package/dist/elements/EFMedia/JitMediaEngine.d.ts +10 -0
  4. package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
  5. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +16 -12
  6. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +1 -1
  7. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -4
  8. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +1 -1
  9. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -4
  10. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -1
  11. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +3 -2
  12. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +16 -12
  13. package/dist/elements/EFMedia.d.ts +2 -3
  14. package/dist/elements/EFMedia.js +0 -4
  15. package/dist/elements/EFTemporal.d.ts +9 -6
  16. package/dist/elements/EFTemporal.js +15 -12
  17. package/dist/elements/EFTimegroup.browsertest.d.ts +26 -0
  18. package/dist/elements/EFTimegroup.d.ts +12 -9
  19. package/dist/elements/EFTimegroup.js +114 -65
  20. package/dist/elements/EFVideo.d.ts +5 -1
  21. package/dist/elements/EFVideo.js +16 -8
  22. package/dist/elements/EFWaveform.js +2 -3
  23. package/dist/elements/FetchContext.browsertest.d.ts +0 -0
  24. package/dist/elements/FetchMixin.js +14 -9
  25. package/dist/elements/TimegroupController.js +2 -1
  26. package/dist/elements/updateAnimations.browsertest.d.ts +0 -0
  27. package/dist/elements/updateAnimations.d.ts +19 -9
  28. package/dist/elements/updateAnimations.js +64 -25
  29. package/dist/gui/ContextMixin.js +34 -27
  30. package/dist/gui/EFConfiguration.d.ts +1 -1
  31. package/dist/gui/EFConfiguration.js +1 -0
  32. package/dist/gui/EFFilmstrip.d.ts +1 -0
  33. package/dist/gui/EFFilmstrip.js +12 -14
  34. package/dist/gui/TWMixin.js +1 -1
  35. package/dist/style.css +1 -1
  36. package/dist/transcoding/cache/URLTokenDeduplicator.d.ts +38 -0
  37. package/dist/transcoding/cache/URLTokenDeduplicator.js +66 -0
  38. package/dist/transcoding/cache/URLTokenDeduplicator.test.d.ts +1 -0
  39. package/dist/transcoding/types/index.d.ts +10 -0
  40. package/package.json +2 -2
  41. package/src/elements/EFMedia/AssetMediaEngine.ts +16 -2
  42. package/src/elements/EFMedia/JitMediaEngine.ts +14 -0
  43. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -1
  44. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +11 -4
  45. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -4
  46. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +4 -1
  47. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -5
  48. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +2 -2
  49. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +7 -3
  50. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +11 -4
  51. package/src/elements/EFMedia.browsertest.ts +13 -4
  52. package/src/elements/EFMedia.ts +6 -10
  53. package/src/elements/EFTemporal.ts +21 -26
  54. package/src/elements/EFTimegroup.browsertest.ts +186 -2
  55. package/src/elements/EFTimegroup.ts +190 -94
  56. package/src/elements/EFVideo.browsertest.ts +53 -132
  57. package/src/elements/EFVideo.ts +26 -13
  58. package/src/elements/EFWaveform.ts +2 -3
  59. package/src/elements/FetchContext.browsertest.ts +396 -0
  60. package/src/elements/FetchMixin.ts +25 -8
  61. package/src/elements/TimegroupController.ts +2 -1
  62. package/src/elements/updateAnimations.browsertest.ts +559 -0
  63. package/src/elements/updateAnimations.ts +113 -50
  64. package/src/gui/ContextMixin.browsertest.ts +4 -9
  65. package/src/gui/ContextMixin.ts +52 -33
  66. package/src/gui/EFConfiguration.ts +1 -1
  67. package/src/gui/EFFilmstrip.ts +15 -18
  68. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +182 -0
  69. package/src/transcoding/cache/URLTokenDeduplicator.ts +101 -0
  70. package/src/transcoding/types/index.ts +11 -0
  71. package/test/EFVideo.framegen.browsertest.ts +1 -1
  72. package/test/setup.ts +2 -0
  73. package/types.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { provide } from "@lit/context";
2
2
  import { Task, TaskStatus } from "@lit/task";
3
3
  import debug from "debug";
4
- import { css, html, LitElement, type PropertyValueMap } from "lit";
4
+ import { css, html, LitElement } from "lit";
5
5
  import { customElement, property } from "lit/decorators.js";
6
6
 
7
7
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
@@ -12,14 +12,22 @@ import {
12
12
  deepGetElementsWithFrameTasks,
13
13
  EFTemporal,
14
14
  flushStartTimeMsCache,
15
+ resetTemporalCache,
15
16
  shallowGetTemporalElements,
16
17
  timegroupContext,
17
18
  } from "./EFTemporal.js";
18
19
  import { TimegroupController } from "./TimegroupController.js";
19
- import { updateAnimations } from "./updateAnimations.ts";
20
+ import { evaluateTemporalState, updateAnimations } from "./updateAnimations.ts";
20
21
 
21
22
  const log = debug("ef:elements:EFTimegroup");
22
23
 
24
+ // Cache for sequence mode duration calculations to avoid O(n) recalculation
25
+ let sequenceDurationCache: WeakMap<EFTimegroup, number> = new WeakMap();
26
+
27
+ export const flushSequenceDurationCache = () => {
28
+ sequenceDurationCache = new WeakMap();
29
+ };
30
+
23
31
  export const shallowGetTimegroups = (
24
32
  element: Element,
25
33
  groups: EFTimegroup[] = [],
@@ -50,20 +58,40 @@ export class EFTimegroup extends EFTemporal(LitElement) {
50
58
  @provide({ context: timegroupContext })
51
59
  _timeGroupContext = this;
52
60
 
53
- #currentTime = 0;
61
+ #currentTime: number | undefined = undefined;
54
62
 
55
63
  @property({
56
64
  type: String,
57
65
  attribute: "mode",
58
66
  })
59
- mode: "fit" | "fixed" | "sequence" | "contain" = "contain";
67
+ set mode(value: "fit" | "fixed" | "sequence" | "contain") {
68
+ // Invalidate duration cache when mode changes
69
+ sequenceDurationCache.delete(this);
70
+ this._mode = value;
71
+ }
72
+
73
+ get mode() {
74
+ return this._mode;
75
+ }
76
+
77
+ private _mode: "fit" | "fixed" | "sequence" | "contain" = "contain";
60
78
 
61
79
  @property({
62
80
  type: Number,
63
81
  converter: durationConverter,
64
82
  attribute: "overlap",
65
83
  })
66
- overlapMs = 0;
84
+ set overlapMs(value: number) {
85
+ // Invalidate duration cache when overlap changes
86
+ sequenceDurationCache.delete(this);
87
+ this._overlapMs = value;
88
+ }
89
+
90
+ get overlapMs() {
91
+ return this._overlapMs;
92
+ }
93
+
94
+ private _overlapMs = 0;
67
95
 
68
96
  @property({ type: String })
69
97
  fit: "none" | "contain" | "cover" = "none";
@@ -74,25 +102,48 @@ export class EFTimegroup extends EFTemporal(LitElement) {
74
102
 
75
103
  #pendingSeekTime: number | undefined;
76
104
 
105
+ #processingPendingSeek = false;
106
+
77
107
  @property({ type: Number, attribute: "currenttime" })
78
108
  set currentTime(time: number) {
109
+ if (!this.isRootTimegroup) {
110
+ return;
111
+ }
112
+ if (Number.isNaN(time)) {
113
+ return;
114
+ }
115
+ if (time === this.#currentTime && !this.#processingPendingSeek) {
116
+ return;
117
+ }
118
+ if (this.#pendingSeekTime === time) {
119
+ return;
120
+ }
121
+
79
122
  if (this.#seekInProgress) {
123
+ console.trace("pending seek to", time);
80
124
  this.#pendingSeekTime = time;
125
+ this.#currentTime = time;
81
126
  return;
82
127
  }
128
+ console.trace("seeking to", time);
83
129
 
130
+ this.#currentTime = time;
131
+ // This will be set to false in the seekTask
84
132
  this.#seekInProgress = true;
85
- this.#pendingSeekTime = time;
86
133
 
87
134
  this.seekTask.run().finally(() => {
88
- this.#seekInProgress = false;
89
135
  if (
90
136
  this.#pendingSeekTime !== undefined &&
91
137
  this.#pendingSeekTime !== time
92
138
  ) {
93
139
  const pendingTime = this.#pendingSeekTime;
94
140
  this.#pendingSeekTime = undefined;
95
- this.currentTime = pendingTime;
141
+ this.#processingPendingSeek = true;
142
+ try {
143
+ this.currentTime = pendingTime;
144
+ } finally {
145
+ this.#processingPendingSeek = false;
146
+ }
96
147
  } else {
97
148
  this.#pendingSeekTime = undefined;
98
149
  }
@@ -100,22 +151,22 @@ export class EFTimegroup extends EFTemporal(LitElement) {
100
151
  }
101
152
 
102
153
  get currentTime() {
103
- return this.#currentTime;
104
- }
105
-
106
- get currentTimeMs() {
107
- return this.currentTime * 1000;
154
+ return this.#currentTime ?? 0;
108
155
  }
109
156
 
110
157
  set currentTimeMs(ms: number) {
111
158
  this.currentTime = ms / 1000;
112
159
  }
113
160
 
161
+ get currentTimeMs() {
162
+ return this.currentTime * 1000;
163
+ }
164
+
114
165
  /**
115
166
  * Determines if this is a root timegroup (no parent timegroups)
116
167
  */
117
168
  get isRootTimegroup(): boolean {
118
- return this.closest("ef-timegroup") === this;
169
+ return !this.parentTimegroup;
119
170
  }
120
171
 
121
172
  /**
@@ -123,7 +174,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
123
174
  */
124
175
  #saveTimeToLocalStorage(time: number) {
125
176
  try {
126
- if (this.id && this.isConnected) {
177
+ if (this.id && this.isConnected && !Number.isNaN(time)) {
127
178
  localStorage.setItem(this.storageKey, time.toString());
128
179
  }
129
180
  } catch (error) {
@@ -132,25 +183,41 @@ export class EFTimegroup extends EFTemporal(LitElement) {
132
183
  }
133
184
 
134
185
  render() {
135
- return html`<slot></slot> `;
186
+ return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;
136
187
  }
137
188
 
189
+ #handleSlotChange = () => {
190
+ // Invalidate caches when slot content changes
191
+ resetTemporalCache();
192
+ flushSequenceDurationCache();
193
+ flushStartTimeMsCache();
194
+
195
+ // Request update to trigger recalculation of dependent properties
196
+ this.requestUpdate();
197
+ };
198
+
138
199
  maybeLoadTimeFromLocalStorage() {
139
200
  if (this.id) {
140
201
  try {
141
- return Number.parseFloat(localStorage.getItem(this.storageKey) || "0");
202
+ const storedValue = localStorage.getItem(this.storageKey);
203
+ if (storedValue === null) {
204
+ return undefined;
205
+ }
206
+ return Number.parseFloat(storedValue);
142
207
  } catch (error) {
143
208
  log("Failed to load time from localStorage", error);
144
209
  }
145
210
  }
146
- return 0;
147
211
  }
148
212
 
149
213
  connectedCallback() {
150
214
  super.connectedCallback();
151
215
  if (this.id) {
152
216
  this.waitForMediaDurations().then(() => {
153
- this.currentTime = this.maybeLoadTimeFromLocalStorage();
217
+ const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
218
+ if (maybeLoadedTime !== undefined) {
219
+ this.currentTime = maybeLoadedTime;
220
+ }
154
221
  });
155
222
  }
156
223
 
@@ -161,10 +228,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
161
228
  if (this.shouldWrapWithWorkbench()) {
162
229
  this.wrapWithWorkbench();
163
230
  }
164
-
165
- requestAnimationFrame(() => {
166
- this.updateAnimations();
167
- });
168
231
  }
169
232
 
170
233
  disconnectedCallback() {
@@ -205,6 +268,12 @@ export class EFTimegroup extends EFTemporal(LitElement) {
205
268
  case "fixed":
206
269
  return super.durationMs;
207
270
  case "sequence": {
271
+ // Check cache first to avoid expensive O(n) recalculation
272
+ const cachedDuration = sequenceDurationCache.get(this);
273
+ if (cachedDuration !== undefined) {
274
+ return cachedDuration;
275
+ }
276
+
208
277
  let duration = 0;
209
278
  this.childTemporals.forEach((child, index) => {
210
279
  if (child instanceof EFTimegroup && child.mode === "fit") {
@@ -215,6 +284,9 @@ export class EFTimegroup extends EFTemporal(LitElement) {
215
284
  }
216
285
  duration += child.durationMs;
217
286
  });
287
+
288
+ // Cache the calculated duration
289
+ sequenceDurationCache.set(this, duration);
218
290
  return duration;
219
291
  }
220
292
  case "contain": {
@@ -241,9 +313,34 @@ export class EFTimegroup extends EFTemporal(LitElement) {
241
313
  await this.waitForNestedUpdates(signal);
242
314
  signal?.throwIfAborted();
243
315
  const temporals = deepGetElementsWithFrameTasks(this);
244
- return temporals
245
- .map((temporal) => temporal.frameTask)
246
- .filter((task) => task.status < TaskStatus.COMPLETE);
316
+
317
+ // Filter to only include temporally visible elements for frame processing
318
+ // (but keep all elements for duration calculations)
319
+ // Use the target timeline time if we're in the middle of seeking
320
+ const timelineTimeMs =
321
+ (this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1000;
322
+ const activeTemporals = temporals.filter((temporal) => {
323
+ // Skip timeline filtering if temporal doesn't have timeline position info
324
+ if (!("startTimeMs" in temporal) || !("endTimeMs" in temporal)) {
325
+ return true; // Keep non-temporal elements
326
+ }
327
+
328
+ // Only process frame tasks for elements that overlap the current timeline
329
+ // Use same epsilon logic as seek task for consistency
330
+ const epsilon = 0.001; // 1µs offset to break ties at boundaries
331
+ const startTimeMs = (temporal as any).startTimeMs as number;
332
+ const endTimeMs = (temporal as any).endTimeMs as number;
333
+ const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;
334
+ const elementEndsAfterStart = endTimeMs > timelineTimeMs; // Exclusive end for clean transitions
335
+ return elementStartsBeforeEnd && elementEndsAfterStart;
336
+ });
337
+
338
+ const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);
339
+ frameTasks.forEach((task) => {
340
+ task.run();
341
+ });
342
+
343
+ return frameTasks.filter((task) => task.status < TaskStatus.COMPLETE);
247
344
  }
248
345
 
249
346
  async waitForNestedUpdates(signal?: AbortSignal) {
@@ -263,23 +360,20 @@ export class EFTimegroup extends EFTemporal(LitElement) {
263
360
  }
264
361
  }
265
362
 
266
- async waitForFrameTasks(signal?: AbortSignal) {
267
- const limit = 10;
268
- let step = 0;
269
- await this.waitForNestedUpdates(signal);
270
- while (step < limit) {
271
- step++;
272
- let pendingTasks = await this.getPendingFrameTasks(signal);
273
- signal?.throwIfAborted();
274
- await Promise.all(pendingTasks.map((task) => task.taskComplete));
275
- signal?.throwIfAborted();
276
- await this.updateComplete;
277
- signal?.throwIfAborted();
278
- pendingTasks = await this.getPendingFrameTasks(signal);
279
- if (pendingTasks.length === 0) {
280
- break;
281
- }
282
- }
363
+ async waitForFrameTasks() {
364
+ const temporalElements = deepGetElementsWithFrameTasks(this);
365
+
366
+ // Filter to only include temporally visible elements for frame processing
367
+ const visibleElements = temporalElements.filter((element) => {
368
+ const temporalState = evaluateTemporalState(element);
369
+ return temporalState.isVisible;
370
+ });
371
+
372
+ await Promise.all(
373
+ visibleElements.map((element) => {
374
+ return element.frameTask.run();
375
+ }),
376
+ );
283
377
  }
284
378
 
285
379
  /**
@@ -295,7 +389,11 @@ export class EFTimegroup extends EFTemporal(LitElement) {
295
389
  const mediaElements = deepGetMediaElements(this);
296
390
  // Then, we must await the fragmentIndexTask to ensure all media elements have their
297
391
  // fragment index loaded, which is where their duration is parsed from.
298
- await Promise.all(mediaElements.map((m) => m.mediaEngineTask.taskComplete));
392
+ await Promise.all(
393
+ mediaElements.map((m) =>
394
+ m.mediaEngineTask.value ? Promise.resolve() : m.mediaEngineTask.run(),
395
+ ),
396
+ );
299
397
 
300
398
  // After waiting for durations, we must force some updates to cascade and ensure all temporal elements
301
399
  // have correct durations and start times. It is not ideal that we have to do this inside here,
@@ -305,6 +403,9 @@ export class EFTimegroup extends EFTemporal(LitElement) {
305
403
  // startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.
306
404
  flushStartTimeMsCache();
307
405
 
406
+ // Flush duration cache since child durations may have changed
407
+ flushSequenceDurationCache();
408
+
308
409
  // Request an update to the currentTime of this group, ensuring that time updates will cascade
309
410
  // down to children, forcing sequence groups to arrange correctly.
310
411
  // This also makes the filmstrip update correctly.
@@ -319,22 +420,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
319
420
  return shallowGetTemporalElements(this);
320
421
  }
321
422
 
322
- protected updated(
323
- changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
324
- ): void {
325
- super.updated(changedProperties);
326
- if (
327
- changedProperties.has("currentTime") ||
328
- changedProperties.has("ownCurrentTimeMs")
329
- ) {
330
- this.updateAnimations();
331
- }
332
- }
333
-
334
- private updateAnimations() {
335
- updateAnimations(this);
336
- }
337
-
338
423
  get contextProvider() {
339
424
  let parent = this.parentNode;
340
425
  while (parent) {
@@ -424,9 +509,15 @@ export class EFTimegroup extends EFTemporal(LitElement) {
424
509
  return;
425
510
  }
426
511
 
512
+ // Convert from local timeline to source media timeline (accounting for sourcein/sourceout)
513
+ const sourceInMs =
514
+ mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
515
+ const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
516
+ const mediaSourceToMs = mediaLocalToMs + sourceInMs;
517
+
427
518
  const audio = await mediaElement.fetchAudioSpanningTime(
428
- mediaLocalFromMs, // ✅ Now using media element's local timeline
429
- mediaLocalToMs, // ✅ Now using media element's local timeline
519
+ mediaSourceFromMs, // ✅ Now using source media timeline with sourcein/sourceout
520
+ mediaSourceToMs, // ✅ Now using source media timeline with sourcein/sourceout
430
521
  abortController.signal,
431
522
  );
432
523
  if (!audio) {
@@ -441,24 +532,32 @@ export class EFTimegroup extends EFTemporal(LitElement) {
441
532
 
442
533
  // Calculate timing for placing this audio in the output context
443
534
  const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
444
- const ctxEndMs = mediaElement.endTimeMs - fromMs;
445
- const ctxDurationMs = ctxEndMs - ctxStartMs;
446
535
 
447
536
  // Calculate offset within the fetched audio buffer
448
- // Since we now use local timeline coordinates, audio.startMs is relative to media start
449
- const requestedOffsetInMedia = mediaLocalFromMs; // Already in local timeline
450
- const actualOffsetInBuffer = requestedOffsetInMedia - audio.startMs; // Both in local timeline
537
+ // audio.startMs is now in source timeline, convert back to compare properly
538
+ const requestedSourceFromMs = mediaSourceFromMs;
539
+ const actualSourceStartMs = audio.startMs;
540
+ const offsetInBufferMs = requestedSourceFromMs - actualSourceStartMs;
451
541
 
452
542
  // Ensure offset is never negative (this would cause audio scheduling errors)
453
- const safeOffset = Math.max(0, actualOffsetInBuffer);
543
+ const safeOffsetMs = Math.max(0, offsetInBufferMs);
544
+
545
+ // Calculate exact duration to play from the buffer (don't exceed what we need)
546
+ const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;
547
+ const availableAudioMs = audio.endMs - audio.startMs;
548
+ const actualDurationMs = Math.min(
549
+ requestedDurationMs,
550
+ availableAudioMs - safeOffsetMs,
551
+ );
454
552
 
455
- if (safeOffset !== actualOffsetInBuffer) {
553
+ if (actualDurationMs <= 0) {
554
+ return; // Skip if no valid audio duration
456
555
  }
457
556
 
458
557
  bufferSource.start(
459
- ctxStartMs / 1000,
460
- safeOffset / 1000,
461
- ctxDurationMs / 1000,
558
+ ctxStartMs / 1000, // When to start in output context (seconds)
559
+ safeOffsetMs / 1000, // Offset into the fetched buffer (seconds)
560
+ actualDurationMs / 1000, // How long to play from buffer (seconds)
462
561
  );
463
562
  }),
464
563
  );
@@ -545,39 +644,36 @@ export class EFTimegroup extends EFTemporal(LitElement) {
545
644
  }
546
645
 
547
646
  frameTask = new Task(this, {
548
- autoRun: EF_INTERACTIVE,
647
+ // autoRun: EF_INTERACTIVE,
648
+ autoRun: false,
549
649
  args: () => [this.ownCurrentTimeMs, this.currentTimeMs] as const,
550
- task: async ([], { signal }) => {
650
+ task: async ([]) => {
551
651
  if (this.isRootTimegroup) {
552
- await this.waitForFrameTasks(signal);
652
+ await this.waitForFrameTasks();
653
+ updateAnimations(this);
553
654
  }
554
655
  },
555
656
  });
556
657
 
557
658
  seekTask = new Task(this, {
659
+ autoRun: false,
558
660
  args: () => [this.#pendingSeekTime ?? this.#currentTime] as const,
559
- task: async ([targetTime], { signal }) => {
560
- const newTime = Math.max(0, Math.min(targetTime, this.durationMs / 1000));
561
- this.#currentTime = newTime;
562
- this.requestUpdate("currentTime");
563
-
564
- // Wait for update to propagate to child elements
565
- await this.updateComplete;
566
- signal.throwIfAborted();
567
-
568
- // Trigger child video seek tasks since they don't auto-run anymore
569
- const videoElements = this.querySelectorAll(
570
- "ef-video",
571
- ) as NodeListOf<any>;
572
- for (const video of videoElements) {
573
- if (video.videoSeekTask) {
574
- video.videoSeekTask.run();
575
- }
661
+ onComplete: () => {},
662
+ task: async ([targetTime]) => {
663
+ if (!this.isRootTimegroup) {
664
+ return;
576
665
  }
577
-
578
- // Run frame task and wait for completion
666
+ await this.waitForMediaDurations();
667
+ const newTime = Math.max(
668
+ 0,
669
+ Math.min(targetTime ?? 0, this.durationMs / 1000),
670
+ );
671
+ this.requestUpdate("currentTime");
579
672
  await this.frameTask.run();
580
673
  this.#saveTimeToLocalStorage(newTime);
674
+ // This has to be set false here so any following seeks are not treated as pending
675
+ this.#seekInProgress = false;
676
+ return newTime;
581
677
  },
582
678
  });
583
679
  }