@editframe/elements 0.18.26-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.
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +10 -0
- package/dist/elements/EFMedia/AssetMediaEngine.js +13 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +1 -5
- 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 +12 -9
- package/dist/elements/EFTimegroup.js +114 -65
- 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/BaseMediaEngine.ts +0 -6
- 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 +190 -94
- 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 +559 -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,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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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(
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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(
|
|
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
|
-
|
|
429
|
-
|
|
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
|
-
//
|
|
449
|
-
const
|
|
450
|
-
const
|
|
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
|
|
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 (
|
|
553
|
+
if (actualDurationMs <= 0) {
|
|
554
|
+
return; // Skip if no valid audio duration
|
|
456
555
|
}
|
|
457
556
|
|
|
458
557
|
bufferSource.start(
|
|
459
|
-
ctxStartMs / 1000,
|
|
460
|
-
|
|
461
|
-
|
|
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 ([]
|
|
650
|
+
task: async ([]) => {
|
|
551
651
|
if (this.isRootTimegroup) {
|
|
552
|
-
await this.waitForFrameTasks(
|
|
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
|
-
|
|
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
|
-
}
|
|
661
|
+
onComplete: () => {},
|
|
662
|
+
task: async ([targetTime]) => {
|
|
663
|
+
if (!this.isRootTimegroup) {
|
|
664
|
+
return;
|
|
576
665
|
}
|
|
577
|
-
|
|
578
|
-
|
|
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
|
}
|