@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,10 +1,10 @@
|
|
|
1
1
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
2
2
|
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
3
3
|
import { durationConverter } from "./durationConverter.js";
|
|
4
|
-
import { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, shallowGetTemporalElements, timegroupContext } from "./EFTemporal.js";
|
|
5
|
-
import { updateAnimations } from "./updateAnimations.js";
|
|
4
|
+
import { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, resetTemporalCache, shallowGetTemporalElements, timegroupContext } from "./EFTemporal.js";
|
|
6
5
|
import { deepGetMediaElements } from "./EFMedia.js";
|
|
7
6
|
import { TimegroupController } from "./TimegroupController.js";
|
|
7
|
+
import { evaluateTemporalState, updateAnimations } from "./updateAnimations.js";
|
|
8
8
|
import { provide } from "@lit/context";
|
|
9
9
|
import { Task, TaskStatus } from "@lit/task";
|
|
10
10
|
import debug from "debug";
|
|
@@ -13,6 +13,10 @@ import { customElement, property } from "lit/decorators.js";
|
|
|
13
13
|
import _decorate from "@oxc-project/runtime/helpers/decorate";
|
|
14
14
|
var _EFTimegroup;
|
|
15
15
|
const log = debug("ef:elements:EFTimegroup");
|
|
16
|
+
let sequenceDurationCache = /* @__PURE__ */ new WeakMap();
|
|
17
|
+
const flushSequenceDurationCache = () => {
|
|
18
|
+
sequenceDurationCache = /* @__PURE__ */ new WeakMap();
|
|
19
|
+
};
|
|
16
20
|
const shallowGetTimegroups = (element, groups = []) => {
|
|
17
21
|
for (const child of Array.from(element.children)) if (child instanceof EFTimegroup) groups.push(child);
|
|
18
22
|
else shallowGetTimegroups(child, groups);
|
|
@@ -25,28 +29,33 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
25
29
|
constructor(..._args) {
|
|
26
30
|
super(..._args);
|
|
27
31
|
this._timeGroupContext = this;
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
32
|
+
this._mode = "contain";
|
|
33
|
+
this._overlapMs = 0;
|
|
30
34
|
this.fit = "none";
|
|
35
|
+
this.mediaDurationsPromise = void 0;
|
|
31
36
|
this.frameTask = new Task(this, {
|
|
32
|
-
autoRun:
|
|
37
|
+
autoRun: false,
|
|
33
38
|
args: () => [this.ownCurrentTimeMs, this.currentTimeMs],
|
|
34
|
-
task: async ([]
|
|
35
|
-
if (this.isRootTimegroup)
|
|
39
|
+
task: async ([]) => {
|
|
40
|
+
if (this.isRootTimegroup) {
|
|
41
|
+
await this.waitForFrameTasks();
|
|
42
|
+
updateAnimations(this);
|
|
43
|
+
}
|
|
36
44
|
}
|
|
37
45
|
});
|
|
38
46
|
this.seekTask = new Task(this, {
|
|
47
|
+
autoRun: false,
|
|
39
48
|
args: () => [this.#pendingSeekTime ?? this.#currentTime],
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this
|
|
49
|
+
onComplete: () => {},
|
|
50
|
+
task: async ([targetTime]) => {
|
|
51
|
+
if (!this.isRootTimegroup) return;
|
|
52
|
+
await this.waitForMediaDurations();
|
|
53
|
+
const newTime = Math.max(0, Math.min(targetTime ?? 0, this.durationMs / 1e3));
|
|
43
54
|
this.requestUpdate("currentTime");
|
|
44
|
-
await this.updateComplete;
|
|
45
|
-
signal.throwIfAborted();
|
|
46
|
-
const videoElements = this.querySelectorAll("ef-video");
|
|
47
|
-
for (const video of videoElements) if (video.videoSeekTask) video.videoSeekTask.run();
|
|
48
55
|
await this.frameTask.run();
|
|
49
56
|
this.#saveTimeToLocalStorage(newTime);
|
|
57
|
+
this.#seekInProgress = false;
|
|
58
|
+
return newTime;
|
|
50
59
|
}
|
|
51
60
|
});
|
|
52
61
|
}
|
|
@@ -62,72 +71,105 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
62
71
|
}
|
|
63
72
|
`;
|
|
64
73
|
}
|
|
65
|
-
#currentTime = 0;
|
|
74
|
+
#currentTime = void 0;
|
|
75
|
+
set mode(value) {
|
|
76
|
+
sequenceDurationCache.delete(this);
|
|
77
|
+
this._mode = value;
|
|
78
|
+
}
|
|
79
|
+
get mode() {
|
|
80
|
+
return this._mode;
|
|
81
|
+
}
|
|
82
|
+
set overlapMs(value) {
|
|
83
|
+
sequenceDurationCache.delete(this);
|
|
84
|
+
this._overlapMs = value;
|
|
85
|
+
}
|
|
86
|
+
get overlapMs() {
|
|
87
|
+
return this._overlapMs;
|
|
88
|
+
}
|
|
66
89
|
#resizeObserver;
|
|
67
90
|
#seekInProgress = false;
|
|
68
91
|
#pendingSeekTime;
|
|
92
|
+
#processingPendingSeek = false;
|
|
69
93
|
set currentTime(time) {
|
|
94
|
+
time = Math.max(0, time);
|
|
95
|
+
if (!this.isRootTimegroup) return;
|
|
96
|
+
if (Number.isNaN(time)) return;
|
|
97
|
+
if (time === this.#currentTime && !this.#processingPendingSeek) return;
|
|
98
|
+
if (this.#pendingSeekTime === time) return;
|
|
70
99
|
if (this.#seekInProgress) {
|
|
71
100
|
this.#pendingSeekTime = time;
|
|
101
|
+
this.#currentTime = time;
|
|
72
102
|
return;
|
|
73
103
|
}
|
|
104
|
+
this.#currentTime = time;
|
|
74
105
|
this.#seekInProgress = true;
|
|
75
|
-
this.#pendingSeekTime = time;
|
|
76
106
|
this.seekTask.run().finally(() => {
|
|
77
|
-
this.#seekInProgress = false;
|
|
78
107
|
if (this.#pendingSeekTime !== void 0 && this.#pendingSeekTime !== time) {
|
|
79
108
|
const pendingTime = this.#pendingSeekTime;
|
|
80
109
|
this.#pendingSeekTime = void 0;
|
|
81
|
-
this
|
|
110
|
+
this.#processingPendingSeek = true;
|
|
111
|
+
try {
|
|
112
|
+
this.currentTime = pendingTime;
|
|
113
|
+
} finally {
|
|
114
|
+
this.#processingPendingSeek = false;
|
|
115
|
+
}
|
|
82
116
|
} else this.#pendingSeekTime = void 0;
|
|
83
117
|
});
|
|
84
118
|
}
|
|
85
119
|
get currentTime() {
|
|
86
|
-
return this.#currentTime;
|
|
87
|
-
}
|
|
88
|
-
get currentTimeMs() {
|
|
89
|
-
return this.currentTime * 1e3;
|
|
120
|
+
return this.#currentTime ?? 0;
|
|
90
121
|
}
|
|
91
122
|
set currentTimeMs(ms) {
|
|
92
123
|
this.currentTime = ms / 1e3;
|
|
93
124
|
}
|
|
125
|
+
get currentTimeMs() {
|
|
126
|
+
return this.currentTime * 1e3;
|
|
127
|
+
}
|
|
94
128
|
/**
|
|
95
129
|
* Determines if this is a root timegroup (no parent timegroups)
|
|
96
130
|
*/
|
|
97
131
|
get isRootTimegroup() {
|
|
98
|
-
return this.
|
|
132
|
+
return !this.parentTimegroup;
|
|
99
133
|
}
|
|
100
134
|
/**
|
|
101
135
|
* Saves time to localStorage (extracted for reuse)
|
|
102
136
|
*/
|
|
103
137
|
#saveTimeToLocalStorage(time) {
|
|
104
138
|
try {
|
|
105
|
-
if (this.id && this.isConnected) localStorage.setItem(this.storageKey, time.toString());
|
|
139
|
+
if (this.id && this.isConnected && !Number.isNaN(time)) localStorage.setItem(this.storageKey, time.toString());
|
|
106
140
|
} catch (error) {
|
|
107
141
|
log("Failed to save time to localStorage", error);
|
|
108
142
|
}
|
|
109
143
|
}
|
|
110
144
|
render() {
|
|
111
|
-
return html`<slot></slot> `;
|
|
145
|
+
return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;
|
|
112
146
|
}
|
|
147
|
+
#handleSlotChange = () => {
|
|
148
|
+
resetTemporalCache();
|
|
149
|
+
flushSequenceDurationCache();
|
|
150
|
+
flushStartTimeMsCache();
|
|
151
|
+
this.requestUpdate();
|
|
152
|
+
};
|
|
113
153
|
maybeLoadTimeFromLocalStorage() {
|
|
114
154
|
if (this.id) try {
|
|
115
|
-
|
|
155
|
+
const storedValue = localStorage.getItem(this.storageKey);
|
|
156
|
+
if (storedValue === null) return void 0;
|
|
157
|
+
return Number.parseFloat(storedValue);
|
|
116
158
|
} catch (error) {
|
|
117
159
|
log("Failed to load time from localStorage", error);
|
|
118
160
|
}
|
|
119
|
-
return 0;
|
|
120
161
|
}
|
|
121
162
|
connectedCallback() {
|
|
122
163
|
super.connectedCallback();
|
|
123
|
-
|
|
124
|
-
|
|
164
|
+
this.waitForMediaDurations().then(() => {
|
|
165
|
+
if (this.id) {
|
|
166
|
+
const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
|
|
167
|
+
if (maybeLoadedTime !== void 0) this.currentTime = maybeLoadedTime;
|
|
168
|
+
}
|
|
169
|
+
if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) this.seekTask.run();
|
|
125
170
|
});
|
|
126
171
|
if (this.parentTimegroup) new TimegroupController(this.parentTimegroup, this);
|
|
127
172
|
if (this.shouldWrapWithWorkbench()) this.wrapWithWorkbench();
|
|
128
|
-
requestAnimationFrame(() => {
|
|
129
|
-
this.updateAnimations();
|
|
130
|
-
});
|
|
131
173
|
}
|
|
132
174
|
disconnectedCallback() {
|
|
133
175
|
super.disconnectedCallback();
|
|
@@ -152,12 +194,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
152
194
|
}
|
|
153
195
|
case "fixed": return super.durationMs;
|
|
154
196
|
case "sequence": {
|
|
197
|
+
const cachedDuration = sequenceDurationCache.get(this);
|
|
198
|
+
if (cachedDuration !== void 0) return cachedDuration;
|
|
155
199
|
let duration = 0;
|
|
156
200
|
this.childTemporals.forEach((child, index) => {
|
|
157
201
|
if (child instanceof _EFTimegroup && child.mode === "fit") return;
|
|
158
202
|
if (index > 0) duration -= this.overlapMs;
|
|
159
203
|
duration += child.durationMs;
|
|
160
204
|
});
|
|
205
|
+
sequenceDurationCache.set(this, duration);
|
|
161
206
|
return duration;
|
|
162
207
|
}
|
|
163
208
|
case "contain": {
|
|
@@ -176,7 +221,21 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
176
221
|
await this.waitForNestedUpdates(signal);
|
|
177
222
|
signal?.throwIfAborted();
|
|
178
223
|
const temporals = deepGetElementsWithFrameTasks(this);
|
|
179
|
-
|
|
224
|
+
const timelineTimeMs = (this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1e3;
|
|
225
|
+
const activeTemporals = temporals.filter((temporal) => {
|
|
226
|
+
if (!("startTimeMs" in temporal) || !("endTimeMs" in temporal)) return true;
|
|
227
|
+
const epsilon = .001;
|
|
228
|
+
const startTimeMs = temporal.startTimeMs;
|
|
229
|
+
const endTimeMs = temporal.endTimeMs;
|
|
230
|
+
const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;
|
|
231
|
+
const elementEndsAfterStart = endTimeMs > timelineTimeMs;
|
|
232
|
+
return elementStartsBeforeEnd && elementEndsAfterStart;
|
|
233
|
+
});
|
|
234
|
+
const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);
|
|
235
|
+
frameTasks.forEach((task) => {
|
|
236
|
+
task.run();
|
|
237
|
+
});
|
|
238
|
+
return frameTasks.filter((task) => task.status < TaskStatus.COMPLETE);
|
|
180
239
|
}
|
|
181
240
|
async waitForNestedUpdates(signal) {
|
|
182
241
|
const limit = 10;
|
|
@@ -190,21 +249,19 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
190
249
|
if (isComplete) break;
|
|
191
250
|
}
|
|
192
251
|
}
|
|
193
|
-
async waitForFrameTasks(
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (pendingTasks.length === 0) break;
|
|
207
|
-
}
|
|
252
|
+
async waitForFrameTasks() {
|
|
253
|
+
const temporalElements = deepGetElementsWithFrameTasks(this);
|
|
254
|
+
const visibleElements = temporalElements.filter((element) => {
|
|
255
|
+
const temporalState = evaluateTemporalState(element);
|
|
256
|
+
return temporalState.isVisible;
|
|
257
|
+
});
|
|
258
|
+
await Promise.all(visibleElements.map((element) => {
|
|
259
|
+
return element.frameTask.run();
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
async waitForMediaDurations() {
|
|
263
|
+
if (!this.mediaDurationsPromise) this.mediaDurationsPromise = this.#waitForMediaDurations();
|
|
264
|
+
return this.mediaDurationsPromise;
|
|
208
265
|
}
|
|
209
266
|
/**
|
|
210
267
|
* Wait for all media elements to load their initial segments.
|
|
@@ -212,24 +269,18 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
212
269
|
* that caused issues with constructing audio data. We had negative durations
|
|
213
270
|
* in calculations and it was not clear why.
|
|
214
271
|
*/
|
|
215
|
-
async waitForMediaDurations() {
|
|
272
|
+
async #waitForMediaDurations() {
|
|
216
273
|
await this.updateComplete;
|
|
217
274
|
const mediaElements = deepGetMediaElements(this);
|
|
218
|
-
await Promise.all(mediaElements.map((m) => m.mediaEngineTask.
|
|
275
|
+
await Promise.all(mediaElements.map((m) => m.mediaEngineTask.value ? Promise.resolve() : m.mediaEngineTask.run()));
|
|
219
276
|
flushStartTimeMsCache();
|
|
277
|
+
flushSequenceDurationCache();
|
|
220
278
|
this.requestUpdate("currentTime");
|
|
221
279
|
await this.updateComplete;
|
|
222
280
|
}
|
|
223
281
|
get childTemporals() {
|
|
224
282
|
return shallowGetTemporalElements(this);
|
|
225
283
|
}
|
|
226
|
-
updated(changedProperties) {
|
|
227
|
-
super.updated(changedProperties);
|
|
228
|
-
if (changedProperties.has("currentTime") || changedProperties.has("ownCurrentTimeMs")) this.updateAnimations();
|
|
229
|
-
}
|
|
230
|
-
updateAnimations() {
|
|
231
|
-
updateAnimations(this);
|
|
232
|
-
}
|
|
233
284
|
get contextProvider() {
|
|
234
285
|
let parent = this.parentNode;
|
|
235
286
|
while (parent) {
|
|
@@ -276,19 +327,24 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
276
327
|
const mediaLocalFromMs = Math.max(0, fromMs - mediaElement.startTimeMs);
|
|
277
328
|
const mediaLocalToMs = Math.min(mediaElement.endTimeMs - mediaElement.startTimeMs, toMs - mediaElement.startTimeMs);
|
|
278
329
|
if (mediaLocalFromMs >= mediaLocalToMs) return;
|
|
279
|
-
const
|
|
330
|
+
const sourceInMs = mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
|
|
331
|
+
const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
|
|
332
|
+
const mediaSourceToMs = mediaLocalToMs + sourceInMs;
|
|
333
|
+
const audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, abortController.signal);
|
|
280
334
|
if (!audio) throw new Error("Failed to fetch audio");
|
|
281
335
|
const bufferSource = audioContext.createBufferSource();
|
|
282
336
|
bufferSource.buffer = await audioContext.decodeAudioData(await audio.blob.arrayBuffer());
|
|
283
337
|
bufferSource.connect(audioContext.destination);
|
|
284
338
|
const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
339
|
+
const requestedSourceFromMs = mediaSourceFromMs;
|
|
340
|
+
const actualSourceStartMs = audio.startMs;
|
|
341
|
+
const offsetInBufferMs = requestedSourceFromMs - actualSourceStartMs;
|
|
342
|
+
const safeOffsetMs = Math.max(0, offsetInBufferMs);
|
|
343
|
+
const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;
|
|
344
|
+
const availableAudioMs = audio.endMs - audio.startMs;
|
|
345
|
+
const actualDurationMs = Math.min(requestedDurationMs, availableAudioMs - safeOffsetMs);
|
|
346
|
+
if (actualDurationMs <= 0) return;
|
|
347
|
+
bufferSource.start(ctxStartMs / 1e3, safeOffsetMs / 1e3, actualDurationMs / 1e3);
|
|
292
348
|
}));
|
|
293
349
|
}
|
|
294
350
|
async renderAudio(fromMs, toMs) {
|
|
@@ -347,12 +403,12 @@ _decorate([provide({ context: timegroupContext })], EFTimegroup.prototype, "_tim
|
|
|
347
403
|
_decorate([property({
|
|
348
404
|
type: String,
|
|
349
405
|
attribute: "mode"
|
|
350
|
-
})], EFTimegroup.prototype, "mode",
|
|
406
|
+
})], EFTimegroup.prototype, "mode", null);
|
|
351
407
|
_decorate([property({
|
|
352
408
|
type: Number,
|
|
353
409
|
converter: durationConverter,
|
|
354
410
|
attribute: "overlap"
|
|
355
|
-
})], EFTimegroup.prototype, "overlapMs",
|
|
411
|
+
})], EFTimegroup.prototype, "overlapMs", null);
|
|
356
412
|
_decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
|
|
357
413
|
_decorate([property({
|
|
358
414
|
type: Number,
|
|
@@ -49,10 +49,10 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
49
49
|
message: string;
|
|
50
50
|
};
|
|
51
51
|
constructor();
|
|
52
|
+
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
52
53
|
render(): import('lit-html').TemplateResult<1>;
|
|
53
54
|
get canvasElement(): HTMLCanvasElement | undefined;
|
|
54
55
|
frameTask: Task<readonly [number], void>;
|
|
55
|
-
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
56
56
|
/**
|
|
57
57
|
* Start a delayed loading operation for testing
|
|
58
58
|
*/
|
|
@@ -68,6 +68,10 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
68
68
|
*/
|
|
69
69
|
private setLoadingState;
|
|
70
70
|
paintTask: Task<readonly [number], void>;
|
|
71
|
+
/**
|
|
72
|
+
* Clear the canvas when element becomes inactive
|
|
73
|
+
*/
|
|
74
|
+
clearCanvas(): void;
|
|
71
75
|
/**
|
|
72
76
|
* Display a video frame on the canvas
|
|
73
77
|
*/
|
package/dist/elements/EFVideo.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
2
1
|
import { EFMedia } from "./EFMedia.js";
|
|
3
2
|
import { TWMixin } from "../gui/TWMixin2.js";
|
|
4
3
|
import { DelayedLoadingState } from "../DelayedLoadingState.js";
|
|
@@ -99,19 +98,21 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
99
98
|
message: ""
|
|
100
99
|
};
|
|
101
100
|
this.frameTask = new Task(this, {
|
|
102
|
-
autoRun:
|
|
101
|
+
autoRun: false,
|
|
103
102
|
args: () => [this.desiredSeekTimeMs],
|
|
104
103
|
onError: (error) => {
|
|
105
104
|
console.error("frameTask error", error);
|
|
106
105
|
},
|
|
107
106
|
onComplete: () => {},
|
|
108
|
-
task: async ([_desiredSeekTimeMs]
|
|
107
|
+
task: async ([_desiredSeekTimeMs]) => {
|
|
108
|
+
this.unifiedVideoSeekTask.run();
|
|
109
109
|
await this.unifiedVideoSeekTask.taskComplete;
|
|
110
|
+
this.paintTask.run();
|
|
110
111
|
await this.paintTask.taskComplete;
|
|
111
|
-
if (signal.aborted) return;
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
114
|
this.paintTask = new Task(this, {
|
|
115
|
+
autoRun: false,
|
|
115
116
|
args: () => [this.desiredSeekTimeMs],
|
|
116
117
|
onError: (error) => {
|
|
117
118
|
console.error("paintTask error", error);
|
|
@@ -146,6 +147,9 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
146
147
|
this.setLoadingState(isLoading, null, message);
|
|
147
148
|
});
|
|
148
149
|
}
|
|
150
|
+
updated(changedProperties) {
|
|
151
|
+
super.updated(changedProperties);
|
|
152
|
+
}
|
|
149
153
|
render() {
|
|
150
154
|
return html`
|
|
151
155
|
<canvas ${ref(this.canvasRef)}></canvas>
|
|
@@ -169,9 +173,6 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
169
173
|
if (shadowCanvas) return shadowCanvas;
|
|
170
174
|
return void 0;
|
|
171
175
|
}
|
|
172
|
-
updated(changedProperties) {
|
|
173
|
-
super.updated(changedProperties);
|
|
174
|
-
}
|
|
175
176
|
/**
|
|
176
177
|
* Start a delayed loading operation for testing
|
|
177
178
|
*/
|
|
@@ -195,6 +196,14 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
195
196
|
};
|
|
196
197
|
}
|
|
197
198
|
/**
|
|
199
|
+
* Clear the canvas when element becomes inactive
|
|
200
|
+
*/
|
|
201
|
+
clearCanvas() {
|
|
202
|
+
if (!this.canvasElement) return;
|
|
203
|
+
const ctx = this.canvasElement.getContext("2d");
|
|
204
|
+
if (ctx) ctx.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
198
207
|
* Display a video frame on the canvas
|
|
199
208
|
*/
|
|
200
209
|
displayFrame(frame, seekToMs) {
|
|
@@ -225,7 +234,6 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
225
234
|
log("trace: displayFrame aborted - null frame format");
|
|
226
235
|
throw new Error(`Frame display failed: Video frame has null format at time ${seekToMs}ms. This indicates corrupted or incompatible video data.`);
|
|
227
236
|
}
|
|
228
|
-
log("trace: drawing frame to canvas", frame.timestamp / 1e3);
|
|
229
237
|
ctx.drawImage(frame, 0, 0, this.canvasElement.width, this.canvasElement.height);
|
|
230
238
|
log("trace: frame drawn to canvas", { seekToMs });
|
|
231
239
|
return seekToMs;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
2
1
|
import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
3
2
|
import { EFTemporal } from "./EFTemporal.js";
|
|
4
3
|
import { TargetController } from "./TargetController.js";
|
|
@@ -24,7 +23,7 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
|
|
|
24
23
|
this.lineWidth = 4;
|
|
25
24
|
this.targetController = new TargetController(this);
|
|
26
25
|
this.frameTask = new Task(this, {
|
|
27
|
-
autoRun:
|
|
26
|
+
autoRun: false,
|
|
28
27
|
args: () => {
|
|
29
28
|
return [this.targetElement, this.targetElement?.frequencyDataTask.value];
|
|
30
29
|
},
|
|
@@ -95,7 +94,7 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
|
|
|
95
94
|
`;
|
|
96
95
|
}
|
|
97
96
|
render() {
|
|
98
|
-
return html`<canvas ${ref(this.canvasRef)}></canvas
|
|
97
|
+
return html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
99
98
|
}
|
|
100
99
|
connectedCallback() {
|
|
101
100
|
super.connectedCallback();
|
|
File without changes
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import { fetchContext } from "../gui/fetchContext.js";
|
|
2
|
-
import { consume } from "@lit/context";
|
|
3
|
-
import _decorate from "@oxc-project/runtime/helpers/decorate";
|
|
4
|
-
import { state } from "lit/decorators/state.js";
|
|
5
1
|
function FetchMixin(superClass) {
|
|
6
2
|
class FetchElement extends superClass {
|
|
7
3
|
constructor(..._args) {
|
|
8
4
|
super(..._args);
|
|
9
|
-
this.fetch =
|
|
5
|
+
this.fetch = (url, init) => {
|
|
6
|
+
try {
|
|
7
|
+
const workbench = this.closest("ef-workbench");
|
|
8
|
+
if (workbench?.fetch) return workbench.fetch(url, init);
|
|
9
|
+
const preview = this.closest("ef-preview");
|
|
10
|
+
if (preview?.fetch) return preview.fetch(url, init);
|
|
11
|
+
const configuration = this.closest("ef-configuration");
|
|
12
|
+
if (configuration?.fetch) return configuration.fetch(url, init);
|
|
13
|
+
return window.fetch(url, init);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error("FetchMixin error", url, error);
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
10
19
|
}
|
|
11
20
|
}
|
|
12
|
-
_decorate([consume({
|
|
13
|
-
context: fetchContext,
|
|
14
|
-
subscribe: true
|
|
15
|
-
}), state()], FetchElement.prototype, "fetch", void 0);
|
|
16
21
|
return FetchElement;
|
|
17
22
|
}
|
|
18
23
|
export { FetchMixin };
|
|
@@ -12,7 +12,8 @@ var TimegroupController = class {
|
|
|
12
12
|
}
|
|
13
13
|
hostUpdated() {
|
|
14
14
|
this.child.requestUpdate();
|
|
15
|
-
|
|
15
|
+
const newChildTimeMs = this.host.currentTimeMs - (this.child.startTimeMs ?? 0);
|
|
16
|
+
this.child.currentTimeMs = newChildTimeMs;
|
|
16
17
|
}
|
|
17
18
|
};
|
|
18
19
|
export { TimegroupController };
|
|
File without changes
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { TemporalMixinInterface } from './EFTemporal.ts';
|
|
2
|
+
export type AnimatableElement = TemporalMixinInterface & HTMLElement;
|
|
3
|
+
/**
|
|
4
|
+
* Represents the temporal state of an element relative to the timeline
|
|
5
|
+
*/
|
|
6
|
+
interface TemporalState {
|
|
7
|
+
progress: number;
|
|
8
|
+
isVisible: boolean;
|
|
9
|
+
timelineTimeMs: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Evaluates what the element's state should be based on the timeline
|
|
13
|
+
*/
|
|
14
|
+
export declare const evaluateTemporalState: (element: AnimatableElement) => TemporalState;
|
|
15
|
+
/**
|
|
16
|
+
* Main function: synchronizes DOM element with timeline
|
|
17
|
+
*/
|
|
18
|
+
export declare const updateAnimations: (element: AnimatableElement) => void;
|
|
19
|
+
export {};
|
|
@@ -1,42 +1,81 @@
|
|
|
1
|
-
import { isEFTemporal } from "./EFTemporal.js";
|
|
2
|
-
const
|
|
3
|
-
|
|
1
|
+
import { deepGetTemporalElements, isEFTemporal } from "./EFTemporal.js";
|
|
2
|
+
const ANIMATION_PRECISION_OFFSET = .001;
|
|
3
|
+
const DEFAULT_ANIMATION_ITERATIONS = 1;
|
|
4
|
+
const PROGRESS_PROPERTY = "--ef-progress";
|
|
5
|
+
const DURATION_PROPERTY = "--ef-duration";
|
|
6
|
+
const TRANSITION_DURATION_PROPERTY = "--ef-transition-duration";
|
|
7
|
+
const TRANSITION_OUT_START_PROPERTY = "--ef-transition-out-start";
|
|
8
|
+
const TIMEGROUP_TAGNAME = "ef-timegroup";
|
|
9
|
+
/**
|
|
10
|
+
* Evaluates what the element's state should be based on the timeline
|
|
11
|
+
*/
|
|
12
|
+
const evaluateTemporalState = (element) => {
|
|
4
13
|
const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
const progress = element.durationMs <= 0 ? 1 : Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs));
|
|
15
|
+
const isVisible = element.startTimeMs <= timelineTimeMs && element.endTimeMs > timelineTimeMs;
|
|
16
|
+
return {
|
|
17
|
+
progress,
|
|
18
|
+
isVisible,
|
|
19
|
+
timelineTimeMs
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Updates the visual state (CSS + display) to match temporal state
|
|
24
|
+
*/
|
|
25
|
+
const updateVisualState = (element, state) => {
|
|
26
|
+
element.style.setProperty(PROGRESS_PROPERTY, `${state.progress * 100}%`);
|
|
27
|
+
if (!state.isVisible) {
|
|
28
|
+
if (element.style.display !== "none") element.style.display = "none";
|
|
7
29
|
return;
|
|
8
30
|
}
|
|
9
|
-
element.style.display = "";
|
|
10
|
-
|
|
31
|
+
if (element.style.display === "none") element.style.display = "";
|
|
32
|
+
element.style.setProperty(DURATION_PROPERTY, `${element.durationMs}ms`);
|
|
33
|
+
element.style.setProperty(TRANSITION_DURATION_PROPERTY, `${element.parentTimegroup?.overlapMs ?? 0}ms`);
|
|
34
|
+
element.style.setProperty(TRANSITION_OUT_START_PROPERTY, `${element.durationMs - (element.parentTimegroup?.overlapMs ?? 0)}ms`);
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Coordinates animations to match timeline
|
|
38
|
+
*/
|
|
39
|
+
const coordinateAnimations = (element) => {
|
|
11
40
|
const animations = element.getAnimations({ subtree: true });
|
|
12
|
-
element.style.setProperty("--ef-duration", `${element.durationMs}ms`);
|
|
13
|
-
element.style.setProperty("--ef-transition-duration", `${element.parentTimegroup?.overlapMs ?? 0}ms`);
|
|
14
|
-
element.style.setProperty("--ef-transition-out-start", `${element.durationMs - (element.parentTimegroup?.overlapMs ?? 0)}ms`);
|
|
15
41
|
for (const animation of animations) {
|
|
16
42
|
if (animation.playState === "running") animation.pause();
|
|
17
43
|
const effect = animation.effect;
|
|
18
44
|
if (!(effect && effect instanceof KeyframeEffect)) continue;
|
|
19
45
|
const target = effect.target;
|
|
20
|
-
if (!target) continue;
|
|
21
|
-
|
|
22
|
-
const timing = effect.getTiming();
|
|
23
|
-
const duration = Number(timing.duration) ?? 0;
|
|
24
|
-
const delay = Number(timing.delay) ?? 0;
|
|
25
|
-
const iterations = Number(timing.iterations) ?? 1;
|
|
26
|
-
const timeTarget = isEFTemporal(target) ? target : target.closest("ef-timegroup");
|
|
46
|
+
if (!target || target.closest(TIMEGROUP_TAGNAME) !== element) continue;
|
|
47
|
+
const timeTarget = isEFTemporal(target) ? target : target.closest(TIMEGROUP_TAGNAME);
|
|
27
48
|
if (!timeTarget) continue;
|
|
28
|
-
const
|
|
29
|
-
|
|
49
|
+
const timing = effect.getTiming();
|
|
50
|
+
const duration = Number(timing.duration) || 0;
|
|
51
|
+
const delay = Number(timing.delay) || 0;
|
|
52
|
+
const iterations = Number(timing.iterations) || DEFAULT_ANIMATION_ITERATIONS;
|
|
53
|
+
if (duration <= 0) {
|
|
30
54
|
animation.currentTime = 0;
|
|
31
55
|
continue;
|
|
32
56
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
animation.currentTime = duration - .01;
|
|
57
|
+
const currentTime = timeTarget.ownCurrentTimeMs ?? 0;
|
|
58
|
+
if (currentTime < delay) {
|
|
59
|
+
animation.currentTime = 0;
|
|
37
60
|
continue;
|
|
38
61
|
}
|
|
39
|
-
|
|
62
|
+
const adjustedTime = currentTime - delay;
|
|
63
|
+
const currentIteration = Math.floor(adjustedTime / duration);
|
|
64
|
+
const currentIterationTime = adjustedTime % duration;
|
|
65
|
+
if (currentIteration >= iterations) animation.currentTime = duration - ANIMATION_PRECISION_OFFSET;
|
|
66
|
+
else animation.currentTime = Math.min(currentIterationTime, duration - ANIMATION_PRECISION_OFFSET) + delay;
|
|
40
67
|
}
|
|
41
68
|
};
|
|
42
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Main function: synchronizes DOM element with timeline
|
|
71
|
+
*/
|
|
72
|
+
const updateAnimations = (element) => {
|
|
73
|
+
const temporalState = evaluateTemporalState(element);
|
|
74
|
+
deepGetTemporalElements(element).forEach((temporalElement) => {
|
|
75
|
+
const temporalState$1 = evaluateTemporalState(temporalElement);
|
|
76
|
+
updateVisualState(temporalElement, temporalState$1);
|
|
77
|
+
});
|
|
78
|
+
updateVisualState(element, temporalState);
|
|
79
|
+
if (temporalState.isVisible) coordinateAnimations(element);
|
|
80
|
+
};
|
|
81
|
+
export { evaluateTemporalState, updateAnimations };
|