@editframe/elements 0.18.27-beta.0 → 0.19.4-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/EFMedia/AssetMediaEngine.d.ts +10 -0
- package/dist/elements/EFMedia/AssetMediaEngine.js +13 -1
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +10 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +16 -12
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -4
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +3 -2
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +16 -12
- package/dist/elements/EFMedia.d.ts +2 -3
- package/dist/elements/EFMedia.js +0 -4
- package/dist/elements/EFTemporal.d.ts +9 -6
- package/dist/elements/EFTemporal.js +15 -12
- package/dist/elements/EFTimegroup.browsertest.d.ts +26 -0
- package/dist/elements/EFTimegroup.d.ts +13 -15
- package/dist/elements/EFTimegroup.js +123 -67
- package/dist/elements/EFVideo.d.ts +5 -1
- package/dist/elements/EFVideo.js +16 -8
- package/dist/elements/EFWaveform.js +2 -3
- package/dist/elements/FetchContext.browsertest.d.ts +0 -0
- package/dist/elements/FetchMixin.js +14 -9
- package/dist/elements/TimegroupController.js +2 -1
- package/dist/elements/updateAnimations.browsertest.d.ts +0 -0
- package/dist/elements/updateAnimations.d.ts +19 -9
- package/dist/elements/updateAnimations.js +64 -25
- package/dist/gui/ContextMixin.js +34 -27
- package/dist/gui/EFConfiguration.d.ts +1 -1
- package/dist/gui/EFConfiguration.js +1 -0
- package/dist/gui/EFFilmstrip.d.ts +1 -0
- package/dist/gui/EFFilmstrip.js +12 -14
- package/dist/gui/TWMixin.js +1 -1
- package/dist/style.css +1 -1
- package/dist/transcoding/cache/URLTokenDeduplicator.d.ts +38 -0
- package/dist/transcoding/cache/URLTokenDeduplicator.js +66 -0
- package/dist/transcoding/cache/URLTokenDeduplicator.test.d.ts +1 -0
- package/dist/transcoding/types/index.d.ts +10 -0
- package/package.json +2 -2
- package/src/elements/EFMedia/AssetMediaEngine.ts +16 -2
- package/src/elements/EFMedia/JitMediaEngine.ts +14 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -1
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +11 -4
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -4
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +4 -1
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +2 -2
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +7 -3
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +11 -4
- package/src/elements/EFMedia.browsertest.ts +13 -4
- package/src/elements/EFMedia.ts +6 -10
- package/src/elements/EFTemporal.ts +21 -26
- package/src/elements/EFTimegroup.browsertest.ts +186 -2
- package/src/elements/EFTimegroup.ts +205 -98
- package/src/elements/EFVideo.browsertest.ts +53 -132
- package/src/elements/EFVideo.ts +26 -13
- package/src/elements/EFWaveform.ts +2 -3
- package/src/elements/FetchContext.browsertest.ts +396 -0
- package/src/elements/FetchMixin.ts +25 -8
- package/src/elements/TimegroupController.ts +2 -1
- package/src/elements/updateAnimations.browsertest.ts +586 -0
- package/src/elements/updateAnimations.ts +113 -50
- package/src/gui/ContextMixin.browsertest.ts +4 -9
- package/src/gui/ContextMixin.ts +52 -33
- package/src/gui/EFConfiguration.ts +1 -1
- package/src/gui/EFFilmstrip.ts +15 -18
- package/src/transcoding/cache/URLTokenDeduplicator.test.ts +182 -0
- package/src/transcoding/cache/URLTokenDeduplicator.ts +101 -0
- package/src/transcoding/types/index.ts +11 -0
- package/test/EFVideo.framegen.browsertest.ts +1 -1
- package/test/setup.ts +2 -0
- 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
|
|
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 =
|
|
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"
|
|
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
|
|
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,47 @@ 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
|
+
time = Math.max(0, time);
|
|
110
|
+
if (!this.isRootTimegroup) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (Number.isNaN(time)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (time === this.#currentTime && !this.#processingPendingSeek) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (this.#pendingSeekTime === time) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
79
123
|
if (this.#seekInProgress) {
|
|
80
124
|
this.#pendingSeekTime = time;
|
|
125
|
+
this.#currentTime = time;
|
|
81
126
|
return;
|
|
82
127
|
}
|
|
83
128
|
|
|
129
|
+
this.#currentTime = time;
|
|
130
|
+
// This will be set to false in the seekTask
|
|
84
131
|
this.#seekInProgress = true;
|
|
85
|
-
this.#pendingSeekTime = time;
|
|
86
132
|
|
|
87
133
|
this.seekTask.run().finally(() => {
|
|
88
|
-
this.#seekInProgress = false;
|
|
89
134
|
if (
|
|
90
135
|
this.#pendingSeekTime !== undefined &&
|
|
91
136
|
this.#pendingSeekTime !== time
|
|
92
137
|
) {
|
|
93
138
|
const pendingTime = this.#pendingSeekTime;
|
|
94
139
|
this.#pendingSeekTime = undefined;
|
|
95
|
-
this
|
|
140
|
+
this.#processingPendingSeek = true;
|
|
141
|
+
try {
|
|
142
|
+
this.currentTime = pendingTime;
|
|
143
|
+
} finally {
|
|
144
|
+
this.#processingPendingSeek = false;
|
|
145
|
+
}
|
|
96
146
|
} else {
|
|
97
147
|
this.#pendingSeekTime = undefined;
|
|
98
148
|
}
|
|
@@ -100,22 +150,22 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
100
150
|
}
|
|
101
151
|
|
|
102
152
|
get currentTime() {
|
|
103
|
-
return this.#currentTime;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
get currentTimeMs() {
|
|
107
|
-
return this.currentTime * 1000;
|
|
153
|
+
return this.#currentTime ?? 0;
|
|
108
154
|
}
|
|
109
155
|
|
|
110
156
|
set currentTimeMs(ms: number) {
|
|
111
157
|
this.currentTime = ms / 1000;
|
|
112
158
|
}
|
|
113
159
|
|
|
160
|
+
get currentTimeMs() {
|
|
161
|
+
return this.currentTime * 1000;
|
|
162
|
+
}
|
|
163
|
+
|
|
114
164
|
/**
|
|
115
165
|
* Determines if this is a root timegroup (no parent timegroups)
|
|
116
166
|
*/
|
|
117
167
|
get isRootTimegroup(): boolean {
|
|
118
|
-
return this.
|
|
168
|
+
return !this.parentTimegroup;
|
|
119
169
|
}
|
|
120
170
|
|
|
121
171
|
/**
|
|
@@ -123,7 +173,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
123
173
|
*/
|
|
124
174
|
#saveTimeToLocalStorage(time: number) {
|
|
125
175
|
try {
|
|
126
|
-
if (this.id && this.isConnected) {
|
|
176
|
+
if (this.id && this.isConnected && !Number.isNaN(time)) {
|
|
127
177
|
localStorage.setItem(this.storageKey, time.toString());
|
|
128
178
|
}
|
|
129
179
|
} catch (error) {
|
|
@@ -132,27 +182,46 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
132
182
|
}
|
|
133
183
|
|
|
134
184
|
render() {
|
|
135
|
-
return html`<slot></slot> `;
|
|
185
|
+
return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;
|
|
136
186
|
}
|
|
137
187
|
|
|
188
|
+
#handleSlotChange = () => {
|
|
189
|
+
// Invalidate caches when slot content changes
|
|
190
|
+
resetTemporalCache();
|
|
191
|
+
flushSequenceDurationCache();
|
|
192
|
+
flushStartTimeMsCache();
|
|
193
|
+
|
|
194
|
+
// Request update to trigger recalculation of dependent properties
|
|
195
|
+
this.requestUpdate();
|
|
196
|
+
};
|
|
197
|
+
|
|
138
198
|
maybeLoadTimeFromLocalStorage() {
|
|
139
199
|
if (this.id) {
|
|
140
200
|
try {
|
|
141
|
-
|
|
201
|
+
const storedValue = localStorage.getItem(this.storageKey);
|
|
202
|
+
if (storedValue === null) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
return Number.parseFloat(storedValue);
|
|
142
206
|
} catch (error) {
|
|
143
207
|
log("Failed to load time from localStorage", error);
|
|
144
208
|
}
|
|
145
209
|
}
|
|
146
|
-
return 0;
|
|
147
210
|
}
|
|
148
211
|
|
|
149
212
|
connectedCallback() {
|
|
150
213
|
super.connectedCallback();
|
|
151
|
-
|
|
152
|
-
this.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
214
|
+
this.waitForMediaDurations().then(() => {
|
|
215
|
+
if (this.id) {
|
|
216
|
+
const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
|
|
217
|
+
if (maybeLoadedTime !== undefined) {
|
|
218
|
+
this.currentTime = maybeLoadedTime;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) {
|
|
222
|
+
this.seekTask.run();
|
|
223
|
+
}
|
|
224
|
+
});
|
|
156
225
|
|
|
157
226
|
if (this.parentTimegroup) {
|
|
158
227
|
new TimegroupController(this.parentTimegroup, this);
|
|
@@ -161,10 +230,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
161
230
|
if (this.shouldWrapWithWorkbench()) {
|
|
162
231
|
this.wrapWithWorkbench();
|
|
163
232
|
}
|
|
164
|
-
|
|
165
|
-
requestAnimationFrame(() => {
|
|
166
|
-
this.updateAnimations();
|
|
167
|
-
});
|
|
168
233
|
}
|
|
169
234
|
|
|
170
235
|
disconnectedCallback() {
|
|
@@ -205,6 +270,12 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
205
270
|
case "fixed":
|
|
206
271
|
return super.durationMs;
|
|
207
272
|
case "sequence": {
|
|
273
|
+
// Check cache first to avoid expensive O(n) recalculation
|
|
274
|
+
const cachedDuration = sequenceDurationCache.get(this);
|
|
275
|
+
if (cachedDuration !== undefined) {
|
|
276
|
+
return cachedDuration;
|
|
277
|
+
}
|
|
278
|
+
|
|
208
279
|
let duration = 0;
|
|
209
280
|
this.childTemporals.forEach((child, index) => {
|
|
210
281
|
if (child instanceof EFTimegroup && child.mode === "fit") {
|
|
@@ -215,6 +286,9 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
215
286
|
}
|
|
216
287
|
duration += child.durationMs;
|
|
217
288
|
});
|
|
289
|
+
|
|
290
|
+
// Cache the calculated duration
|
|
291
|
+
sequenceDurationCache.set(this, duration);
|
|
218
292
|
return duration;
|
|
219
293
|
}
|
|
220
294
|
case "contain": {
|
|
@@ -241,9 +315,34 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
241
315
|
await this.waitForNestedUpdates(signal);
|
|
242
316
|
signal?.throwIfAborted();
|
|
243
317
|
const temporals = deepGetElementsWithFrameTasks(this);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
318
|
+
|
|
319
|
+
// Filter to only include temporally visible elements for frame processing
|
|
320
|
+
// (but keep all elements for duration calculations)
|
|
321
|
+
// Use the target timeline time if we're in the middle of seeking
|
|
322
|
+
const timelineTimeMs =
|
|
323
|
+
(this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1000;
|
|
324
|
+
const activeTemporals = temporals.filter((temporal) => {
|
|
325
|
+
// Skip timeline filtering if temporal doesn't have timeline position info
|
|
326
|
+
if (!("startTimeMs" in temporal) || !("endTimeMs" in temporal)) {
|
|
327
|
+
return true; // Keep non-temporal elements
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Only process frame tasks for elements that overlap the current timeline
|
|
331
|
+
// Use same epsilon logic as seek task for consistency
|
|
332
|
+
const epsilon = 0.001; // 1µs offset to break ties at boundaries
|
|
333
|
+
const startTimeMs = (temporal as any).startTimeMs as number;
|
|
334
|
+
const endTimeMs = (temporal as any).endTimeMs as number;
|
|
335
|
+
const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;
|
|
336
|
+
const elementEndsAfterStart = endTimeMs > timelineTimeMs; // Exclusive end for clean transitions
|
|
337
|
+
return elementStartsBeforeEnd && elementEndsAfterStart;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);
|
|
341
|
+
frameTasks.forEach((task) => {
|
|
342
|
+
task.run();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
return frameTasks.filter((task) => task.status < TaskStatus.COMPLETE);
|
|
247
346
|
}
|
|
248
347
|
|
|
249
348
|
async waitForNestedUpdates(signal?: AbortSignal) {
|
|
@@ -263,23 +362,29 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
263
362
|
}
|
|
264
363
|
}
|
|
265
364
|
|
|
266
|
-
async waitForFrameTasks(
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
365
|
+
async waitForFrameTasks() {
|
|
366
|
+
const temporalElements = deepGetElementsWithFrameTasks(this);
|
|
367
|
+
|
|
368
|
+
// Filter to only include temporally visible elements for frame processing
|
|
369
|
+
const visibleElements = temporalElements.filter((element) => {
|
|
370
|
+
const temporalState = evaluateTemporalState(element);
|
|
371
|
+
return temporalState.isVisible;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await Promise.all(
|
|
375
|
+
visibleElements.map((element) => {
|
|
376
|
+
return element.frameTask.run();
|
|
377
|
+
}),
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
mediaDurationsPromise: Promise<void> | undefined = undefined;
|
|
382
|
+
|
|
383
|
+
async waitForMediaDurations() {
|
|
384
|
+
if (!this.mediaDurationsPromise) {
|
|
385
|
+
this.mediaDurationsPromise = this.#waitForMediaDurations();
|
|
282
386
|
}
|
|
387
|
+
return this.mediaDurationsPromise;
|
|
283
388
|
}
|
|
284
389
|
|
|
285
390
|
/**
|
|
@@ -288,14 +393,18 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
288
393
|
* that caused issues with constructing audio data. We had negative durations
|
|
289
394
|
* in calculations and it was not clear why.
|
|
290
395
|
*/
|
|
291
|
-
async waitForMediaDurations() {
|
|
396
|
+
async #waitForMediaDurations() {
|
|
292
397
|
// We must await updateComplete to ensure all media elements inside this are connected
|
|
293
398
|
// and will match deepGetMediaElements
|
|
294
399
|
await this.updateComplete;
|
|
295
400
|
const mediaElements = deepGetMediaElements(this);
|
|
296
401
|
// Then, we must await the fragmentIndexTask to ensure all media elements have their
|
|
297
402
|
// fragment index loaded, which is where their duration is parsed from.
|
|
298
|
-
await Promise.all(
|
|
403
|
+
await Promise.all(
|
|
404
|
+
mediaElements.map((m) =>
|
|
405
|
+
m.mediaEngineTask.value ? Promise.resolve() : m.mediaEngineTask.run(),
|
|
406
|
+
),
|
|
407
|
+
);
|
|
299
408
|
|
|
300
409
|
// After waiting for durations, we must force some updates to cascade and ensure all temporal elements
|
|
301
410
|
// have correct durations and start times. It is not ideal that we have to do this inside here,
|
|
@@ -305,6 +414,9 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
305
414
|
// startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.
|
|
306
415
|
flushStartTimeMsCache();
|
|
307
416
|
|
|
417
|
+
// Flush duration cache since child durations may have changed
|
|
418
|
+
flushSequenceDurationCache();
|
|
419
|
+
|
|
308
420
|
// Request an update to the currentTime of this group, ensuring that time updates will cascade
|
|
309
421
|
// down to children, forcing sequence groups to arrange correctly.
|
|
310
422
|
// This also makes the filmstrip update correctly.
|
|
@@ -319,22 +431,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
319
431
|
return shallowGetTemporalElements(this);
|
|
320
432
|
}
|
|
321
433
|
|
|
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
434
|
get contextProvider() {
|
|
339
435
|
let parent = this.parentNode;
|
|
340
436
|
while (parent) {
|
|
@@ -424,9 +520,15 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
424
520
|
return;
|
|
425
521
|
}
|
|
426
522
|
|
|
523
|
+
// Convert from local timeline to source media timeline (accounting for sourcein/sourceout)
|
|
524
|
+
const sourceInMs =
|
|
525
|
+
mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
|
|
526
|
+
const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
|
|
527
|
+
const mediaSourceToMs = mediaLocalToMs + sourceInMs;
|
|
528
|
+
|
|
427
529
|
const audio = await mediaElement.fetchAudioSpanningTime(
|
|
428
|
-
|
|
429
|
-
|
|
530
|
+
mediaSourceFromMs, // ✅ Now using source media timeline with sourcein/sourceout
|
|
531
|
+
mediaSourceToMs, // ✅ Now using source media timeline with sourcein/sourceout
|
|
430
532
|
abortController.signal,
|
|
431
533
|
);
|
|
432
534
|
if (!audio) {
|
|
@@ -441,24 +543,32 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
441
543
|
|
|
442
544
|
// Calculate timing for placing this audio in the output context
|
|
443
545
|
const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
|
|
444
|
-
const ctxEndMs = mediaElement.endTimeMs - fromMs;
|
|
445
|
-
const ctxDurationMs = ctxEndMs - ctxStartMs;
|
|
446
546
|
|
|
447
547
|
// Calculate offset within the fetched audio buffer
|
|
448
|
-
//
|
|
449
|
-
const
|
|
450
|
-
const
|
|
548
|
+
// audio.startMs is now in source timeline, convert back to compare properly
|
|
549
|
+
const requestedSourceFromMs = mediaSourceFromMs;
|
|
550
|
+
const actualSourceStartMs = audio.startMs;
|
|
551
|
+
const offsetInBufferMs = requestedSourceFromMs - actualSourceStartMs;
|
|
451
552
|
|
|
452
553
|
// Ensure offset is never negative (this would cause audio scheduling errors)
|
|
453
|
-
const
|
|
554
|
+
const safeOffsetMs = Math.max(0, offsetInBufferMs);
|
|
555
|
+
|
|
556
|
+
// Calculate exact duration to play from the buffer (don't exceed what we need)
|
|
557
|
+
const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;
|
|
558
|
+
const availableAudioMs = audio.endMs - audio.startMs;
|
|
559
|
+
const actualDurationMs = Math.min(
|
|
560
|
+
requestedDurationMs,
|
|
561
|
+
availableAudioMs - safeOffsetMs,
|
|
562
|
+
);
|
|
454
563
|
|
|
455
|
-
if (
|
|
564
|
+
if (actualDurationMs <= 0) {
|
|
565
|
+
return; // Skip if no valid audio duration
|
|
456
566
|
}
|
|
457
567
|
|
|
458
568
|
bufferSource.start(
|
|
459
|
-
ctxStartMs / 1000,
|
|
460
|
-
|
|
461
|
-
|
|
569
|
+
ctxStartMs / 1000, // When to start in output context (seconds)
|
|
570
|
+
safeOffsetMs / 1000, // Offset into the fetched buffer (seconds)
|
|
571
|
+
actualDurationMs / 1000, // How long to play from buffer (seconds)
|
|
462
572
|
);
|
|
463
573
|
}),
|
|
464
574
|
);
|
|
@@ -545,39 +655,36 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
545
655
|
}
|
|
546
656
|
|
|
547
657
|
frameTask = new Task(this, {
|
|
548
|
-
autoRun: EF_INTERACTIVE,
|
|
658
|
+
// autoRun: EF_INTERACTIVE,
|
|
659
|
+
autoRun: false,
|
|
549
660
|
args: () => [this.ownCurrentTimeMs, this.currentTimeMs] as const,
|
|
550
|
-
task: async ([]
|
|
661
|
+
task: async ([]) => {
|
|
551
662
|
if (this.isRootTimegroup) {
|
|
552
|
-
await this.waitForFrameTasks(
|
|
663
|
+
await this.waitForFrameTasks();
|
|
664
|
+
updateAnimations(this);
|
|
553
665
|
}
|
|
554
666
|
},
|
|
555
667
|
});
|
|
556
668
|
|
|
557
669
|
seekTask = new Task(this, {
|
|
670
|
+
autoRun: false,
|
|
558
671
|
args: () => [this.#pendingSeekTime ?? this.#currentTime] as const,
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
this
|
|
562
|
-
|
|
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
|
-
}
|
|
672
|
+
onComplete: () => {},
|
|
673
|
+
task: async ([targetTime]) => {
|
|
674
|
+
if (!this.isRootTimegroup) {
|
|
675
|
+
return;
|
|
576
676
|
}
|
|
577
|
-
|
|
578
|
-
|
|
677
|
+
await this.waitForMediaDurations();
|
|
678
|
+
const newTime = Math.max(
|
|
679
|
+
0,
|
|
680
|
+
Math.min(targetTime ?? 0, this.durationMs / 1000),
|
|
681
|
+
);
|
|
682
|
+
this.requestUpdate("currentTime");
|
|
579
683
|
await this.frameTask.run();
|
|
580
684
|
this.#saveTimeToLocalStorage(newTime);
|
|
685
|
+
// This has to be set false here so any following seeks are not treated as pending
|
|
686
|
+
this.#seekInProgress = false;
|
|
687
|
+
return newTime;
|
|
581
688
|
},
|
|
582
689
|
});
|
|
583
690
|
}
|