@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,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,32 @@ 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";
|
|
31
35
|
this.frameTask = new Task(this, {
|
|
32
|
-
autoRun:
|
|
36
|
+
autoRun: false,
|
|
33
37
|
args: () => [this.ownCurrentTimeMs, this.currentTimeMs],
|
|
34
|
-
task: async ([]
|
|
35
|
-
if (this.isRootTimegroup)
|
|
38
|
+
task: async ([]) => {
|
|
39
|
+
if (this.isRootTimegroup) {
|
|
40
|
+
await this.waitForFrameTasks();
|
|
41
|
+
updateAnimations(this);
|
|
42
|
+
}
|
|
36
43
|
}
|
|
37
44
|
});
|
|
38
45
|
this.seekTask = new Task(this, {
|
|
46
|
+
autoRun: false,
|
|
39
47
|
args: () => [this.#pendingSeekTime ?? this.#currentTime],
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this
|
|
48
|
+
onComplete: () => {},
|
|
49
|
+
task: async ([targetTime]) => {
|
|
50
|
+
if (!this.isRootTimegroup) return;
|
|
51
|
+
await this.waitForMediaDurations();
|
|
52
|
+
const newTime = Math.max(0, Math.min(targetTime ?? 0, this.durationMs / 1e3));
|
|
43
53
|
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
54
|
await this.frameTask.run();
|
|
49
55
|
this.#saveTimeToLocalStorage(newTime);
|
|
56
|
+
this.#seekInProgress = false;
|
|
57
|
+
return newTime;
|
|
50
58
|
}
|
|
51
59
|
});
|
|
52
60
|
}
|
|
@@ -62,72 +70,103 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
62
70
|
}
|
|
63
71
|
`;
|
|
64
72
|
}
|
|
65
|
-
#currentTime = 0;
|
|
73
|
+
#currentTime = void 0;
|
|
74
|
+
set mode(value) {
|
|
75
|
+
sequenceDurationCache.delete(this);
|
|
76
|
+
this._mode = value;
|
|
77
|
+
}
|
|
78
|
+
get mode() {
|
|
79
|
+
return this._mode;
|
|
80
|
+
}
|
|
81
|
+
set overlapMs(value) {
|
|
82
|
+
sequenceDurationCache.delete(this);
|
|
83
|
+
this._overlapMs = value;
|
|
84
|
+
}
|
|
85
|
+
get overlapMs() {
|
|
86
|
+
return this._overlapMs;
|
|
87
|
+
}
|
|
66
88
|
#resizeObserver;
|
|
67
89
|
#seekInProgress = false;
|
|
68
90
|
#pendingSeekTime;
|
|
91
|
+
#processingPendingSeek = false;
|
|
69
92
|
set currentTime(time) {
|
|
93
|
+
if (!this.isRootTimegroup) return;
|
|
94
|
+
if (Number.isNaN(time)) return;
|
|
95
|
+
if (time === this.#currentTime && !this.#processingPendingSeek) return;
|
|
96
|
+
if (this.#pendingSeekTime === time) return;
|
|
70
97
|
if (this.#seekInProgress) {
|
|
98
|
+
console.trace("pending seek to", time);
|
|
71
99
|
this.#pendingSeekTime = time;
|
|
100
|
+
this.#currentTime = time;
|
|
72
101
|
return;
|
|
73
102
|
}
|
|
103
|
+
console.trace("seeking to", time);
|
|
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
164
|
if (this.id) this.waitForMediaDurations().then(() => {
|
|
124
|
-
|
|
165
|
+
const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
|
|
166
|
+
if (maybeLoadedTime !== void 0) this.currentTime = maybeLoadedTime;
|
|
125
167
|
});
|
|
126
168
|
if (this.parentTimegroup) new TimegroupController(this.parentTimegroup, this);
|
|
127
169
|
if (this.shouldWrapWithWorkbench()) this.wrapWithWorkbench();
|
|
128
|
-
requestAnimationFrame(() => {
|
|
129
|
-
this.updateAnimations();
|
|
130
|
-
});
|
|
131
170
|
}
|
|
132
171
|
disconnectedCallback() {
|
|
133
172
|
super.disconnectedCallback();
|
|
@@ -152,12 +191,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
152
191
|
}
|
|
153
192
|
case "fixed": return super.durationMs;
|
|
154
193
|
case "sequence": {
|
|
194
|
+
const cachedDuration = sequenceDurationCache.get(this);
|
|
195
|
+
if (cachedDuration !== void 0) return cachedDuration;
|
|
155
196
|
let duration = 0;
|
|
156
197
|
this.childTemporals.forEach((child, index) => {
|
|
157
198
|
if (child instanceof _EFTimegroup && child.mode === "fit") return;
|
|
158
199
|
if (index > 0) duration -= this.overlapMs;
|
|
159
200
|
duration += child.durationMs;
|
|
160
201
|
});
|
|
202
|
+
sequenceDurationCache.set(this, duration);
|
|
161
203
|
return duration;
|
|
162
204
|
}
|
|
163
205
|
case "contain": {
|
|
@@ -176,7 +218,21 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
176
218
|
await this.waitForNestedUpdates(signal);
|
|
177
219
|
signal?.throwIfAborted();
|
|
178
220
|
const temporals = deepGetElementsWithFrameTasks(this);
|
|
179
|
-
|
|
221
|
+
const timelineTimeMs = (this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1e3;
|
|
222
|
+
const activeTemporals = temporals.filter((temporal) => {
|
|
223
|
+
if (!("startTimeMs" in temporal) || !("endTimeMs" in temporal)) return true;
|
|
224
|
+
const epsilon = .001;
|
|
225
|
+
const startTimeMs = temporal.startTimeMs;
|
|
226
|
+
const endTimeMs = temporal.endTimeMs;
|
|
227
|
+
const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;
|
|
228
|
+
const elementEndsAfterStart = endTimeMs > timelineTimeMs;
|
|
229
|
+
return elementStartsBeforeEnd && elementEndsAfterStart;
|
|
230
|
+
});
|
|
231
|
+
const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);
|
|
232
|
+
frameTasks.forEach((task) => {
|
|
233
|
+
task.run();
|
|
234
|
+
});
|
|
235
|
+
return frameTasks.filter((task) => task.status < TaskStatus.COMPLETE);
|
|
180
236
|
}
|
|
181
237
|
async waitForNestedUpdates(signal) {
|
|
182
238
|
const limit = 10;
|
|
@@ -190,21 +246,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
190
246
|
if (isComplete) break;
|
|
191
247
|
}
|
|
192
248
|
}
|
|
193
|
-
async waitForFrameTasks(
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
signal?.throwIfAborted();
|
|
203
|
-
await this.updateComplete;
|
|
204
|
-
signal?.throwIfAborted();
|
|
205
|
-
pendingTasks = await this.getPendingFrameTasks(signal);
|
|
206
|
-
if (pendingTasks.length === 0) break;
|
|
207
|
-
}
|
|
249
|
+
async waitForFrameTasks() {
|
|
250
|
+
const temporalElements = deepGetElementsWithFrameTasks(this);
|
|
251
|
+
const visibleElements = temporalElements.filter((element) => {
|
|
252
|
+
const temporalState = evaluateTemporalState(element);
|
|
253
|
+
return temporalState.isVisible;
|
|
254
|
+
});
|
|
255
|
+
await Promise.all(visibleElements.map((element) => {
|
|
256
|
+
return element.frameTask.run();
|
|
257
|
+
}));
|
|
208
258
|
}
|
|
209
259
|
/**
|
|
210
260
|
* Wait for all media elements to load their initial segments.
|
|
@@ -215,21 +265,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
215
265
|
async waitForMediaDurations() {
|
|
216
266
|
await this.updateComplete;
|
|
217
267
|
const mediaElements = deepGetMediaElements(this);
|
|
218
|
-
await Promise.all(mediaElements.map((m) => m.mediaEngineTask.
|
|
268
|
+
await Promise.all(mediaElements.map((m) => m.mediaEngineTask.value ? Promise.resolve() : m.mediaEngineTask.run()));
|
|
219
269
|
flushStartTimeMsCache();
|
|
270
|
+
flushSequenceDurationCache();
|
|
220
271
|
this.requestUpdate("currentTime");
|
|
221
272
|
await this.updateComplete;
|
|
222
273
|
}
|
|
223
274
|
get childTemporals() {
|
|
224
275
|
return shallowGetTemporalElements(this);
|
|
225
276
|
}
|
|
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
277
|
get contextProvider() {
|
|
234
278
|
let parent = this.parentNode;
|
|
235
279
|
while (parent) {
|
|
@@ -276,19 +320,24 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
276
320
|
const mediaLocalFromMs = Math.max(0, fromMs - mediaElement.startTimeMs);
|
|
277
321
|
const mediaLocalToMs = Math.min(mediaElement.endTimeMs - mediaElement.startTimeMs, toMs - mediaElement.startTimeMs);
|
|
278
322
|
if (mediaLocalFromMs >= mediaLocalToMs) return;
|
|
279
|
-
const
|
|
323
|
+
const sourceInMs = mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
|
|
324
|
+
const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
|
|
325
|
+
const mediaSourceToMs = mediaLocalToMs + sourceInMs;
|
|
326
|
+
const audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, abortController.signal);
|
|
280
327
|
if (!audio) throw new Error("Failed to fetch audio");
|
|
281
328
|
const bufferSource = audioContext.createBufferSource();
|
|
282
329
|
bufferSource.buffer = await audioContext.decodeAudioData(await audio.blob.arrayBuffer());
|
|
283
330
|
bufferSource.connect(audioContext.destination);
|
|
284
331
|
const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
332
|
+
const requestedSourceFromMs = mediaSourceFromMs;
|
|
333
|
+
const actualSourceStartMs = audio.startMs;
|
|
334
|
+
const offsetInBufferMs = requestedSourceFromMs - actualSourceStartMs;
|
|
335
|
+
const safeOffsetMs = Math.max(0, offsetInBufferMs);
|
|
336
|
+
const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;
|
|
337
|
+
const availableAudioMs = audio.endMs - audio.startMs;
|
|
338
|
+
const actualDurationMs = Math.min(requestedDurationMs, availableAudioMs - safeOffsetMs);
|
|
339
|
+
if (actualDurationMs <= 0) return;
|
|
340
|
+
bufferSource.start(ctxStartMs / 1e3, safeOffsetMs / 1e3, actualDurationMs / 1e3);
|
|
292
341
|
}));
|
|
293
342
|
}
|
|
294
343
|
async renderAudio(fromMs, toMs) {
|
|
@@ -347,12 +396,12 @@ _decorate([provide({ context: timegroupContext })], EFTimegroup.prototype, "_tim
|
|
|
347
396
|
_decorate([property({
|
|
348
397
|
type: String,
|
|
349
398
|
attribute: "mode"
|
|
350
|
-
})], EFTimegroup.prototype, "mode",
|
|
399
|
+
})], EFTimegroup.prototype, "mode", null);
|
|
351
400
|
_decorate([property({
|
|
352
401
|
type: Number,
|
|
353
402
|
converter: durationConverter,
|
|
354
403
|
attribute: "overlap"
|
|
355
|
-
})], EFTimegroup.prototype, "overlapMs",
|
|
404
|
+
})], EFTimegroup.prototype, "overlapMs", null);
|
|
356
405
|
_decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
|
|
357
406
|
_decorate([property({
|
|
358
407
|
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 = Number.EPSILON;
|
|
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 };
|