@editframe/elements 0.18.20-beta.0 → 0.18.22-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.
- package/dist/elements/EFAudio.d.ts +1 -12
- package/dist/elements/EFAudio.js +3 -18
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +3 -3
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +15 -9
- package/dist/elements/EFMedia/BufferedSeekingInput.js +76 -78
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +12 -10
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +2 -18
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +12 -10
- package/dist/elements/EFTemporal.d.ts +0 -1
- package/dist/elements/EFTemporal.js +4 -8
- package/dist/elements/EFTimegroup.d.ts +4 -4
- package/dist/elements/EFTimegroup.js +52 -60
- package/dist/elements/EFVideo.d.ts +1 -32
- package/dist/elements/EFVideo.js +13 -51
- package/dist/elements/SampleBuffer.js +1 -1
- package/dist/gui/ContextMixin.browsertest.d.ts +1 -1
- package/dist/gui/ContextMixin.js +1 -1
- package/package.json +2 -2
- package/src/elements/EFAudio.browsertest.ts +0 -3
- package/src/elements/EFAudio.ts +3 -22
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +39 -1
- package/src/elements/EFMedia/AssetMediaEngine.ts +5 -4
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +90 -185
- package/src/elements/EFMedia/BufferedSeekingInput.ts +119 -130
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +21 -21
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +10 -5
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +33 -34
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +22 -20
- package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +0 -3
- package/src/elements/EFMedia.browsertest.ts +72 -60
- package/src/elements/EFTemporal.ts +5 -15
- package/src/elements/EFTimegroup.browsertest.ts +9 -4
- package/src/elements/EFTimegroup.ts +79 -95
- package/src/elements/EFVideo.browsertest.ts +172 -160
- package/src/elements/EFVideo.ts +17 -73
- package/src/elements/SampleBuffer.ts +1 -2
- package/src/gui/ContextMixin.browsertest.ts +5 -2
- package/src/gui/ContextMixin.ts +7 -0
- package/test/EFVideo.framegen.browsertest.ts +0 -54
- package/types.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { css } from "lit";
|
|
2
2
|
import { customElement } from "lit/decorators.js";
|
|
3
3
|
import type { VideoSample } from "mediabunny";
|
|
4
|
-
import { describe, vi } from "vitest";
|
|
4
|
+
import { afterEach, beforeEach, describe, vi } from "vitest";
|
|
5
5
|
import { test as baseTest } from "../../test/useMSW.js";
|
|
6
6
|
|
|
7
7
|
import type { EFConfiguration } from "../gui/EFConfiguration.js";
|
|
@@ -97,15 +97,11 @@ describe("JIT Media Engine", () => {
|
|
|
97
97
|
jitVideo,
|
|
98
98
|
expect,
|
|
99
99
|
}) => {
|
|
100
|
-
|
|
100
|
+
await timegroup.waitForMediaDurations();
|
|
101
101
|
timegroup.currentTimeMs = 2200;
|
|
102
|
-
|
|
102
|
+
await timegroup.seekTask.taskComplete;
|
|
103
103
|
const sample = await jitVideo.videoSeekTask.taskComplete;
|
|
104
|
-
|
|
105
|
-
expect(sample).toBeDefined();
|
|
106
|
-
// Based on the pattern: 0ms→0, 3000ms→2.96, 5000ms→4.96
|
|
107
|
-
// For 2200ms, we expect timestamp 2.16
|
|
108
|
-
expect(sample?.timestamp).toEqual(2.16);
|
|
104
|
+
expect(sample?.timestamp).toBeCloseTo(2.2, 1);
|
|
109
105
|
});
|
|
110
106
|
});
|
|
111
107
|
|
|
@@ -115,8 +111,25 @@ describe("JIT Media Engine", () => {
|
|
|
115
111
|
jitVideo,
|
|
116
112
|
expect,
|
|
117
113
|
}) => {
|
|
114
|
+
// Debug: Check what segment should be loaded for 0ms
|
|
115
|
+
const mediaEngine = await (jitVideo as any).mediaEngineTask.taskComplete;
|
|
116
|
+
const videoRendition = mediaEngine?.getVideoRendition();
|
|
117
|
+
const expectedSegmentId = mediaEngine?.computeSegmentId(
|
|
118
|
+
0,
|
|
119
|
+
videoRendition,
|
|
120
|
+
);
|
|
121
|
+
console.log(`MediaEngine.computeSegmentId(0ms) = ${expectedSegmentId}`);
|
|
122
|
+
|
|
118
123
|
timegroup.currentTimeMs = 0;
|
|
124
|
+
await timegroup.seekTask.taskComplete;
|
|
125
|
+
|
|
126
|
+
// Check what segment actually got loaded
|
|
127
|
+
const actualSegmentId = (jitVideo as any).videoSegmentIdTask.value;
|
|
128
|
+
console.log(`videoSegmentIdTask.value = ${actualSegmentId}`);
|
|
129
|
+
|
|
119
130
|
const frame = await (jitVideo as any).videoSeekTask.taskComplete;
|
|
131
|
+
console.log(`Frame timestamp when seeking to 0ms: ${frame?.timestamp}`);
|
|
132
|
+
|
|
120
133
|
expect(frame).toBeDefined();
|
|
121
134
|
expect(frame?.timestamp).toEqual(0);
|
|
122
135
|
});
|
|
@@ -126,11 +139,11 @@ describe("JIT Media Engine", () => {
|
|
|
126
139
|
jitVideo,
|
|
127
140
|
expect,
|
|
128
141
|
}) => {
|
|
142
|
+
await timegroup.waitForMediaDurations();
|
|
129
143
|
timegroup.currentTimeMs = 3_000;
|
|
130
|
-
|
|
131
|
-
const frame = await
|
|
132
|
-
expect(frame).
|
|
133
|
-
expect(frame?.timestamp).toEqual(2.96); // Updated: improved mediabunny processing changed frame timing
|
|
144
|
+
await timegroup.seekTask.taskComplete;
|
|
145
|
+
const frame = await jitVideo.videoSeekTask.taskComplete;
|
|
146
|
+
expect(frame?.timestamp).toBeCloseTo(3, 1);
|
|
134
147
|
});
|
|
135
148
|
|
|
136
149
|
test("seeks to 5 seconds and loads frame", async ({
|
|
@@ -138,11 +151,11 @@ describe("JIT Media Engine", () => {
|
|
|
138
151
|
jitVideo,
|
|
139
152
|
expect,
|
|
140
153
|
}) => {
|
|
154
|
+
await timegroup.waitForMediaDurations();
|
|
141
155
|
timegroup.currentTimeMs = 5_000;
|
|
142
|
-
|
|
143
|
-
const frame = await
|
|
144
|
-
expect(frame).
|
|
145
|
-
expect(frame?.timestamp).toEqual(4.96); // Updated: improved mediabunny processing changed frame timing
|
|
156
|
+
await timegroup.seekTask.taskComplete;
|
|
157
|
+
const frame = await jitVideo.videoSeekTask.taskComplete;
|
|
158
|
+
expect(frame?.timestamp).toBeCloseTo(5, 1);
|
|
146
159
|
});
|
|
147
160
|
|
|
148
161
|
test("seeks ahead in 50ms increments", async ({
|
|
@@ -150,48 +163,47 @@ describe("JIT Media Engine", () => {
|
|
|
150
163
|
jitVideo,
|
|
151
164
|
expect,
|
|
152
165
|
}) => {
|
|
166
|
+
await timegroup.waitForMediaDurations();
|
|
153
167
|
timegroup.currentTimeMs = 0;
|
|
154
168
|
let frame: VideoSample | undefined;
|
|
155
169
|
for (let i = 0; i <= 3000; i += 50) {
|
|
156
170
|
timegroup.currentTimeMs = i;
|
|
157
|
-
|
|
158
|
-
frame = await
|
|
171
|
+
await timegroup.seekTask.taskComplete;
|
|
172
|
+
frame = await jitVideo.videoSeekTask.taskComplete;
|
|
159
173
|
expect(frame).toBeDefined();
|
|
160
174
|
}
|
|
161
|
-
expect(frame?.timestamp).
|
|
175
|
+
expect(frame?.timestamp).toBeCloseTo(3, 1);
|
|
162
176
|
});
|
|
163
177
|
});
|
|
164
178
|
|
|
165
179
|
describe("boundary seeking", () => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// const segmentId2 = await jitVideo.audioSegmentIdTask.taskComplete
|
|
176
|
-
// console.log({ segmentId2, start2, end2 })
|
|
177
|
-
|
|
178
|
-
// timegroup.currentTimeMs = 2.0266666666666664 * 1000
|
|
179
|
-
// jitVideo.desiredSeekTimeMs = 2.0266666666666664 * 1000
|
|
180
|
-
// await jitVideo.videoSeekTask.taskComplete
|
|
181
|
-
// const segment3 = await jitVideo.audioInputTask.taskComplete;
|
|
182
|
-
// const segment3Audio = await segment3.getFirstAudioTrack()
|
|
183
|
-
// const start3 = await segment3Audio?.getFirstTimestamp();
|
|
184
|
-
// const end3 = await segment3Audio?.computeDuration();
|
|
185
|
-
// const segmentId3 = await jitVideo.audioSegmentIdTask.taskComplete;
|
|
186
|
-
// console.log({ segmentId3, start3, end3 })
|
|
187
|
-
// await expect(jitVideo.videoSegmentIdTask.taskComplete).resolves.toBe(2);
|
|
188
|
-
// });
|
|
180
|
+
test.skip("segment 2 track range and segment 3 track range have no gap between them", async ({
|
|
181
|
+
expect,
|
|
182
|
+
jitVideo,
|
|
183
|
+
timegroup,
|
|
184
|
+
}) => {
|
|
185
|
+
// SKIP: audioSeekTask is not part of the audio rendering pipeline
|
|
186
|
+
await timegroup.waitForMediaDurations();
|
|
187
|
+
timegroup.currentTimeMs = 1000;
|
|
188
|
+
await timegroup.frameTask.taskComplete;
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
timegroup.currentTimeMs = 2026.6666666666663;
|
|
191
|
+
await timegroup.frameTask.taskComplete;
|
|
192
|
+
const sample = await jitVideo.videoSeekTask.taskComplete;
|
|
193
|
+
expect(sample?.timestamp).toBeCloseTo(2, 1);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("Can seek audio to 4025.0000000000005ms in head-moov-480p.mp4", async ({
|
|
197
|
+
expect,
|
|
198
|
+
jitVideo,
|
|
199
|
+
timegroup,
|
|
200
|
+
}) => {
|
|
201
|
+
await timegroup.waitForMediaDurations();
|
|
202
|
+
timegroup.currentTimeMs = 2026.6666666666663;
|
|
203
|
+
await expect(
|
|
204
|
+
jitVideo.audioSeekTask.taskComplete,
|
|
205
|
+
).resolves.to.not.toThrowError();
|
|
206
|
+
});
|
|
195
207
|
|
|
196
208
|
test("can seek audio to 4050ms in head-moov-480p.mp4", async ({
|
|
197
209
|
expect,
|
|
@@ -213,20 +225,20 @@ describe("JIT Media Engine", () => {
|
|
|
213
225
|
});
|
|
214
226
|
|
|
215
227
|
describe("EFMedia", () => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
beforeEach(() => {
|
|
229
|
+
// Clean up DOM
|
|
230
|
+
while (document.body.children.length) {
|
|
231
|
+
document.body.children[0]?.remove();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
222
234
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
afterEach(() => {
|
|
236
|
+
// Clean up any remaining elements
|
|
237
|
+
const elements = document.querySelectorAll("test-media");
|
|
238
|
+
for (const element of elements) {
|
|
239
|
+
element.remove();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
230
242
|
|
|
231
243
|
const test = baseTest.extend<{
|
|
232
244
|
element: TestMedia;
|
|
@@ -236,32 +236,22 @@ export const deepGetElementsWithFrameTasks = (
|
|
|
236
236
|
};
|
|
237
237
|
|
|
238
238
|
let temporalCache: Map<Element, TemporalMixinInterface[]>;
|
|
239
|
-
let modifiedElements = new WeakSet<Element>();
|
|
240
|
-
|
|
241
239
|
const resetTemporalCache = () => {
|
|
242
240
|
temporalCache = new Map();
|
|
243
|
-
modifiedElements = new WeakSet();
|
|
244
241
|
if (typeof requestAnimationFrame !== "undefined") {
|
|
245
242
|
requestAnimationFrame(resetTemporalCache);
|
|
246
243
|
}
|
|
247
244
|
};
|
|
248
245
|
resetTemporalCache();
|
|
249
246
|
|
|
250
|
-
export const clearTemporalCacheForElement = (element: Element) => {
|
|
251
|
-
temporalCache.delete(element);
|
|
252
|
-
modifiedElements.add(element);
|
|
253
|
-
};
|
|
254
|
-
|
|
255
247
|
export const shallowGetTemporalElements = (
|
|
256
248
|
element: Element,
|
|
257
249
|
temporals: TemporalMixinInterface[] = [],
|
|
258
250
|
) => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
temporals.length = 0;
|
|
264
|
-
|
|
251
|
+
const cachedResult = temporalCache.get(element);
|
|
252
|
+
if (cachedResult) {
|
|
253
|
+
return cachedResult;
|
|
254
|
+
}
|
|
265
255
|
for (const child of element.children) {
|
|
266
256
|
if (isEFTemporal(child)) {
|
|
267
257
|
temporals.push(child);
|
|
@@ -269,7 +259,7 @@ export const shallowGetTemporalElements = (
|
|
|
269
259
|
shallowGetTemporalElements(child, temporals);
|
|
270
260
|
}
|
|
271
261
|
}
|
|
272
|
-
|
|
262
|
+
temporalCache.set(element, temporals);
|
|
273
263
|
return temporals;
|
|
274
264
|
};
|
|
275
265
|
|
|
@@ -348,12 +348,17 @@ describe("startTimeMs", () => {
|
|
|
348
348
|
describe("setting currentTime", () => {
|
|
349
349
|
test("persists in localStorage if the timegroup has an id and is in the dom", async () => {
|
|
350
350
|
const timegroup = renderTimegroup(
|
|
351
|
-
html`<ef-timegroup id="
|
|
351
|
+
html`<ef-timegroup id="localStorage-test" mode="fixed" duration="10s"></ef-timegroup>`,
|
|
352
352
|
);
|
|
353
|
+
localStorage.removeItem(timegroup.storageKey);
|
|
353
354
|
document.body.appendChild(timegroup);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
355
|
+
await timegroup.waitForMediaDurations();
|
|
356
|
+
|
|
357
|
+
timegroup.currentTime = 5_000; // 5000 seconds, should clamp to 10s
|
|
358
|
+
await timegroup.seekTask.taskComplete;
|
|
359
|
+
|
|
360
|
+
const storedValue = localStorage.getItem(timegroup.storageKey);
|
|
361
|
+
assert.equal(storedValue, "10"); // Should store 10 (clamped from 5000 to duration)
|
|
357
362
|
timegroup.remove();
|
|
358
363
|
});
|
|
359
364
|
|
|
@@ -52,10 +52,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
52
52
|
|
|
53
53
|
#currentTime = 0;
|
|
54
54
|
|
|
55
|
-
// Frame update locking mechanism (only for root timegroups)
|
|
56
|
-
private isFrameUpdateInProgress = false;
|
|
57
|
-
private queuedTimeUpdate: number | null = null;
|
|
58
|
-
|
|
59
55
|
@property({
|
|
60
56
|
type: String,
|
|
61
57
|
attribute: "mode",
|
|
@@ -73,26 +69,34 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
73
69
|
fit: "none" | "contain" | "cover" = "none";
|
|
74
70
|
|
|
75
71
|
#resizeObserver?: ResizeObserver;
|
|
76
|
-
|
|
72
|
+
|
|
73
|
+
#seekInProgress = false;
|
|
74
|
+
|
|
75
|
+
#pendingSeekTime: number | undefined;
|
|
77
76
|
|
|
78
77
|
@property({ type: Number, attribute: "currenttime" })
|
|
79
78
|
set currentTime(time: number) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// Only apply locking mechanism for root timegroups to prevent cascade overload
|
|
83
|
-
if (this.isRootTimegroup && this.isFrameUpdateInProgress) {
|
|
84
|
-
// Queue the latest time update - only keep the most recent
|
|
85
|
-
this.queuedTimeUpdate = newTime;
|
|
79
|
+
if (this.#seekInProgress) {
|
|
80
|
+
this.#pendingSeekTime = time;
|
|
86
81
|
return;
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.#
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
this.#seekInProgress = true;
|
|
85
|
+
this.#pendingSeekTime = time;
|
|
86
|
+
|
|
87
|
+
this.seekTask.run().finally(() => {
|
|
88
|
+
this.#seekInProgress = false;
|
|
89
|
+
if (
|
|
90
|
+
this.#pendingSeekTime !== undefined &&
|
|
91
|
+
this.#pendingSeekTime !== time
|
|
92
|
+
) {
|
|
93
|
+
const pendingTime = this.#pendingSeekTime;
|
|
94
|
+
this.#pendingSeekTime = undefined;
|
|
95
|
+
this.currentTime = pendingTime;
|
|
96
|
+
} else {
|
|
97
|
+
this.#pendingSeekTime = undefined;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
get currentTime() {
|
|
@@ -114,34 +118,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
114
118
|
return this.closest("ef-timegroup") === this;
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
/**
|
|
118
|
-
* Executes time update with frame locking for root timegroups
|
|
119
|
-
*/
|
|
120
|
-
async #executeTimeUpdate(time: number) {
|
|
121
|
-
this.isFrameUpdateInProgress = true;
|
|
122
|
-
this.#currentTime = time;
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
// Save to localStorage
|
|
126
|
-
this.#saveTimeToLocalStorage(time);
|
|
127
|
-
|
|
128
|
-
// Wait for any pending frame tasks to complete before allowing next update
|
|
129
|
-
await this.waitForFrameTasks();
|
|
130
|
-
} catch (error) {
|
|
131
|
-
console.error("⚠️ [TIME_UPDATE_ERROR] Error during frame update:", error);
|
|
132
|
-
} finally {
|
|
133
|
-
this.isFrameUpdateInProgress = false;
|
|
134
|
-
|
|
135
|
-
// Process queued update if any (ensures latest scrub position is processed)
|
|
136
|
-
if (this.queuedTimeUpdate !== null && this.queuedTimeUpdate !== time) {
|
|
137
|
-
const nextTime = this.queuedTimeUpdate;
|
|
138
|
-
this.queuedTimeUpdate = null;
|
|
139
|
-
// Schedule on next tick to avoid recursive call stack
|
|
140
|
-
setTimeout(() => this.#executeTimeUpdate(nextTime), 0);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
121
|
/**
|
|
146
122
|
* Saves time to localStorage (extracted for reuse)
|
|
147
123
|
*/
|
|
@@ -186,44 +162,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
186
162
|
this.wrapWithWorkbench();
|
|
187
163
|
}
|
|
188
164
|
|
|
189
|
-
// Set up observer to detect child changes that affect duration
|
|
190
|
-
this.#childObserver = new MutationObserver((mutations) => {
|
|
191
|
-
let shouldUpdate = false;
|
|
192
|
-
|
|
193
|
-
for (const mutation of mutations) {
|
|
194
|
-
if (mutation.type === "childList") {
|
|
195
|
-
// Child added/removed - this affects duration for contain/sequence modes
|
|
196
|
-
shouldUpdate = true;
|
|
197
|
-
} else if (mutation.type === "attributes") {
|
|
198
|
-
// Attribute changes that might affect duration
|
|
199
|
-
if (
|
|
200
|
-
mutation.attributeName === "duration" ||
|
|
201
|
-
mutation.attributeName === "mode"
|
|
202
|
-
) {
|
|
203
|
-
shouldUpdate = true;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (shouldUpdate) {
|
|
209
|
-
// Clear the temporal cache for this element to ensure childTemporals is up to date
|
|
210
|
-
import("./EFTemporal.js").then(({ clearTemporalCacheForElement }) => {
|
|
211
|
-
clearTemporalCacheForElement(this);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Trigger an update to recalculate computed properties
|
|
215
|
-
this.requestUpdate();
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// Observe this element for child changes
|
|
220
|
-
this.#childObserver.observe(this, {
|
|
221
|
-
childList: true,
|
|
222
|
-
subtree: true,
|
|
223
|
-
attributes: true,
|
|
224
|
-
attributeFilter: ["duration", "mode"],
|
|
225
|
-
});
|
|
226
|
-
|
|
227
165
|
requestAnimationFrame(() => {
|
|
228
166
|
this.updateAnimations();
|
|
229
167
|
});
|
|
@@ -232,7 +170,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
232
170
|
disconnectedCallback() {
|
|
233
171
|
super.disconnectedCallback();
|
|
234
172
|
this.#resizeObserver?.disconnect();
|
|
235
|
-
this.#childObserver?.disconnect();
|
|
236
173
|
}
|
|
237
174
|
|
|
238
175
|
get storageKey() {
|
|
@@ -300,24 +237,45 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
300
237
|
}
|
|
301
238
|
}
|
|
302
239
|
|
|
303
|
-
async getPendingFrameTasks() {
|
|
304
|
-
await this.
|
|
240
|
+
async getPendingFrameTasks(signal?: AbortSignal) {
|
|
241
|
+
await this.waitForNestedUpdates(signal);
|
|
242
|
+
signal?.throwIfAborted();
|
|
305
243
|
const temporals = deepGetElementsWithFrameTasks(this);
|
|
306
244
|
return temporals
|
|
307
245
|
.map((temporal) => temporal.frameTask)
|
|
308
246
|
.filter((task) => task.status < TaskStatus.COMPLETE);
|
|
309
247
|
}
|
|
310
248
|
|
|
311
|
-
async
|
|
249
|
+
async waitForNestedUpdates(signal?: AbortSignal) {
|
|
250
|
+
const limit = 10;
|
|
251
|
+
let steps = 0;
|
|
252
|
+
let isComplete = true;
|
|
253
|
+
while (true) {
|
|
254
|
+
steps++;
|
|
255
|
+
if (steps > limit) {
|
|
256
|
+
throw new Error("Reached update depth limit.");
|
|
257
|
+
}
|
|
258
|
+
isComplete = await this.updateComplete;
|
|
259
|
+
signal?.throwIfAborted();
|
|
260
|
+
if (isComplete) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async waitForFrameTasks(signal?: AbortSignal) {
|
|
312
267
|
const limit = 10;
|
|
313
268
|
let step = 0;
|
|
314
|
-
await this.
|
|
269
|
+
await this.waitForNestedUpdates(signal);
|
|
315
270
|
while (step < limit) {
|
|
316
271
|
step++;
|
|
317
|
-
let pendingTasks = await this.getPendingFrameTasks();
|
|
272
|
+
let pendingTasks = await this.getPendingFrameTasks(signal);
|
|
273
|
+
signal?.throwIfAborted();
|
|
318
274
|
await Promise.all(pendingTasks.map((task) => task.taskComplete));
|
|
275
|
+
signal?.throwIfAborted();
|
|
319
276
|
await this.updateComplete;
|
|
320
|
-
|
|
277
|
+
signal?.throwIfAborted();
|
|
278
|
+
pendingTasks = await this.getPendingFrameTasks(signal);
|
|
321
279
|
if (pendingTasks.length === 0) {
|
|
322
280
|
break;
|
|
323
281
|
}
|
|
@@ -589,11 +547,37 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
589
547
|
frameTask = new Task(this, {
|
|
590
548
|
autoRun: EF_INTERACTIVE,
|
|
591
549
|
args: () => [this.ownCurrentTimeMs, this.currentTimeMs] as const,
|
|
592
|
-
task: async ([], { signal
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
550
|
+
task: async ([], { signal }) => {
|
|
551
|
+
if (this.isRootTimegroup) {
|
|
552
|
+
await this.waitForFrameTasks(signal);
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
seekTask = new Task(this, {
|
|
558
|
+
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
|
+
}
|
|
596
576
|
}
|
|
577
|
+
|
|
578
|
+
// Run frame task and wait for completion
|
|
579
|
+
await this.frameTask.run();
|
|
580
|
+
this.#saveTimeToLocalStorage(newTime);
|
|
597
581
|
},
|
|
598
582
|
});
|
|
599
583
|
}
|