@editframe/elements 0.18.20-beta.0 → 0.18.22-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elements/EFAudio.d.ts +1 -12
- package/dist/elements/EFAudio.js +3 -18
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +3 -3
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +15 -9
- package/dist/elements/EFMedia/BufferedSeekingInput.js +76 -78
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +12 -10
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +2 -18
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +12 -10
- package/dist/elements/EFTemporal.d.ts +0 -1
- package/dist/elements/EFTemporal.js +4 -8
- package/dist/elements/EFTimegroup.d.ts +4 -4
- package/dist/elements/EFTimegroup.js +52 -60
- package/dist/elements/EFVideo.d.ts +1 -32
- package/dist/elements/EFVideo.js +13 -51
- package/dist/elements/SampleBuffer.js +1 -1
- package/dist/gui/ContextMixin.browsertest.d.ts +1 -1
- package/dist/gui/ContextMixin.js +1 -1
- package/package.json +2 -2
- package/src/elements/EFAudio.browsertest.ts +0 -3
- package/src/elements/EFAudio.ts +3 -22
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +39 -1
- package/src/elements/EFMedia/AssetMediaEngine.ts +5 -4
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +90 -185
- package/src/elements/EFMedia/BufferedSeekingInput.ts +119 -130
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +21 -21
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +10 -5
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +33 -34
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +22 -20
- package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +0 -3
- package/src/elements/EFMedia.browsertest.ts +72 -60
- package/src/elements/EFTemporal.ts +5 -15
- package/src/elements/EFTimegroup.browsertest.ts +9 -4
- package/src/elements/EFTimegroup.ts +79 -95
- package/src/elements/EFVideo.browsertest.ts +172 -160
- package/src/elements/EFVideo.ts +17 -73
- package/src/elements/SampleBuffer.ts +1 -2
- package/src/gui/ContextMixin.browsertest.ts +5 -2
- package/src/gui/ContextMixin.ts +7 -0
- package/test/EFVideo.framegen.browsertest.ts +0 -54
- package/types.json +1 -1
|
@@ -25,17 +25,28 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
25
25
|
constructor(..._args) {
|
|
26
26
|
super(..._args);
|
|
27
27
|
this._timeGroupContext = this;
|
|
28
|
-
this.isFrameUpdateInProgress = false;
|
|
29
|
-
this.queuedTimeUpdate = null;
|
|
30
28
|
this.mode = "contain";
|
|
31
29
|
this.overlapMs = 0;
|
|
32
30
|
this.fit = "none";
|
|
33
31
|
this.frameTask = new Task(this, {
|
|
34
32
|
autoRun: EF_INTERACTIVE,
|
|
35
33
|
args: () => [this.ownCurrentTimeMs, this.currentTimeMs],
|
|
36
|
-
task: async ([], { signal
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
task: async ([], { signal }) => {
|
|
35
|
+
if (this.isRootTimegroup) await this.waitForFrameTasks(signal);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
this.seekTask = new Task(this, {
|
|
39
|
+
args: () => [this.#pendingSeekTime ?? this.#currentTime],
|
|
40
|
+
task: async ([targetTime], { signal }) => {
|
|
41
|
+
const newTime = Math.max(0, Math.min(targetTime, this.durationMs / 1e3));
|
|
42
|
+
this.#currentTime = newTime;
|
|
43
|
+
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
|
+
await this.frameTask.run();
|
|
49
|
+
this.#saveTimeToLocalStorage(newTime);
|
|
39
50
|
}
|
|
40
51
|
});
|
|
41
52
|
}
|
|
@@ -53,18 +64,23 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
53
64
|
}
|
|
54
65
|
#currentTime = 0;
|
|
55
66
|
#resizeObserver;
|
|
56
|
-
#
|
|
67
|
+
#seekInProgress = false;
|
|
68
|
+
#pendingSeekTime;
|
|
57
69
|
set currentTime(time) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.queuedTimeUpdate = newTime;
|
|
70
|
+
if (this.#seekInProgress) {
|
|
71
|
+
this.#pendingSeekTime = time;
|
|
61
72
|
return;
|
|
62
73
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.#
|
|
67
|
-
|
|
74
|
+
this.#seekInProgress = true;
|
|
75
|
+
this.#pendingSeekTime = time;
|
|
76
|
+
this.seekTask.run().finally(() => {
|
|
77
|
+
this.#seekInProgress = false;
|
|
78
|
+
if (this.#pendingSeekTime !== void 0 && this.#pendingSeekTime !== time) {
|
|
79
|
+
const pendingTime = this.#pendingSeekTime;
|
|
80
|
+
this.#pendingSeekTime = void 0;
|
|
81
|
+
this.currentTime = pendingTime;
|
|
82
|
+
} else this.#pendingSeekTime = void 0;
|
|
83
|
+
});
|
|
68
84
|
}
|
|
69
85
|
get currentTime() {
|
|
70
86
|
return this.#currentTime;
|
|
@@ -82,26 +98,6 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
82
98
|
return this.closest("ef-timegroup") === this;
|
|
83
99
|
}
|
|
84
100
|
/**
|
|
85
|
-
* Executes time update with frame locking for root timegroups
|
|
86
|
-
*/
|
|
87
|
-
async #executeTimeUpdate(time) {
|
|
88
|
-
this.isFrameUpdateInProgress = true;
|
|
89
|
-
this.#currentTime = time;
|
|
90
|
-
try {
|
|
91
|
-
this.#saveTimeToLocalStorage(time);
|
|
92
|
-
await this.waitForFrameTasks();
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error("⚠️ [TIME_UPDATE_ERROR] Error during frame update:", error);
|
|
95
|
-
} finally {
|
|
96
|
-
this.isFrameUpdateInProgress = false;
|
|
97
|
-
if (this.queuedTimeUpdate !== null && this.queuedTimeUpdate !== time) {
|
|
98
|
-
const nextTime = this.queuedTimeUpdate;
|
|
99
|
-
this.queuedTimeUpdate = null;
|
|
100
|
-
setTimeout(() => this.#executeTimeUpdate(nextTime), 0);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
101
|
* Saves time to localStorage (extracted for reuse)
|
|
106
102
|
*/
|
|
107
103
|
#saveTimeToLocalStorage(time) {
|
|
@@ -129,25 +125,6 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
129
125
|
});
|
|
130
126
|
if (this.parentTimegroup) new TimegroupController(this.parentTimegroup, this);
|
|
131
127
|
if (this.shouldWrapWithWorkbench()) this.wrapWithWorkbench();
|
|
132
|
-
this.#childObserver = new MutationObserver((mutations) => {
|
|
133
|
-
let shouldUpdate = false;
|
|
134
|
-
for (const mutation of mutations) if (mutation.type === "childList") shouldUpdate = true;
|
|
135
|
-
else if (mutation.type === "attributes") {
|
|
136
|
-
if (mutation.attributeName === "duration" || mutation.attributeName === "mode") shouldUpdate = true;
|
|
137
|
-
}
|
|
138
|
-
if (shouldUpdate) {
|
|
139
|
-
import("./EFTemporal.js").then(({ clearTemporalCacheForElement }) => {
|
|
140
|
-
clearTemporalCacheForElement(this);
|
|
141
|
-
});
|
|
142
|
-
this.requestUpdate();
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
this.#childObserver.observe(this, {
|
|
146
|
-
childList: true,
|
|
147
|
-
subtree: true,
|
|
148
|
-
attributes: true,
|
|
149
|
-
attributeFilter: ["duration", "mode"]
|
|
150
|
-
});
|
|
151
128
|
requestAnimationFrame(() => {
|
|
152
129
|
this.updateAnimations();
|
|
153
130
|
});
|
|
@@ -155,7 +132,6 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
155
132
|
disconnectedCallback() {
|
|
156
133
|
super.disconnectedCallback();
|
|
157
134
|
this.#resizeObserver?.disconnect();
|
|
158
|
-
this.#childObserver?.disconnect();
|
|
159
135
|
}
|
|
160
136
|
get storageKey() {
|
|
161
137
|
if (!this.id) throw new Error("Timegroup must have an id to use localStorage.");
|
|
@@ -196,21 +172,37 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
196
172
|
default: throw new Error(`Invalid time mode: ${this.mode}`);
|
|
197
173
|
}
|
|
198
174
|
}
|
|
199
|
-
async getPendingFrameTasks() {
|
|
200
|
-
await this.
|
|
175
|
+
async getPendingFrameTasks(signal) {
|
|
176
|
+
await this.waitForNestedUpdates(signal);
|
|
177
|
+
signal?.throwIfAborted();
|
|
201
178
|
const temporals = deepGetElementsWithFrameTasks(this);
|
|
202
179
|
return temporals.map((temporal) => temporal.frameTask).filter((task) => task.status < TaskStatus.COMPLETE);
|
|
203
180
|
}
|
|
204
|
-
async
|
|
181
|
+
async waitForNestedUpdates(signal) {
|
|
182
|
+
const limit = 10;
|
|
183
|
+
let steps = 0;
|
|
184
|
+
let isComplete = true;
|
|
185
|
+
while (true) {
|
|
186
|
+
steps++;
|
|
187
|
+
if (steps > limit) throw new Error("Reached update depth limit.");
|
|
188
|
+
isComplete = await this.updateComplete;
|
|
189
|
+
signal?.throwIfAborted();
|
|
190
|
+
if (isComplete) break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async waitForFrameTasks(signal) {
|
|
205
194
|
const limit = 10;
|
|
206
195
|
let step = 0;
|
|
207
|
-
await this.
|
|
196
|
+
await this.waitForNestedUpdates(signal);
|
|
208
197
|
while (step < limit) {
|
|
209
198
|
step++;
|
|
210
|
-
let pendingTasks = await this.getPendingFrameTasks();
|
|
199
|
+
let pendingTasks = await this.getPendingFrameTasks(signal);
|
|
200
|
+
signal?.throwIfAborted();
|
|
211
201
|
await Promise.all(pendingTasks.map((task) => task.taskComplete));
|
|
202
|
+
signal?.throwIfAborted();
|
|
212
203
|
await this.updateComplete;
|
|
213
|
-
|
|
204
|
+
signal?.throwIfAborted();
|
|
205
|
+
pendingTasks = await this.getPendingFrameTasks(signal);
|
|
214
206
|
if (pendingTasks.length === 0) break;
|
|
215
207
|
}
|
|
216
208
|
}
|
|
@@ -78,42 +78,11 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
78
78
|
* Check if EF_FRAMEGEN has explicitly started frame rendering (not just initialization)
|
|
79
79
|
*/
|
|
80
80
|
private isFrameRenderingActive;
|
|
81
|
-
/**
|
|
82
|
-
* Effective mode - always returns "asset" for EFVideo
|
|
83
|
-
*/
|
|
84
|
-
get effectiveMode(): string;
|
|
85
|
-
/**
|
|
86
|
-
* Legacy getter for asset index loader (maps to mediaEngine task)
|
|
87
|
-
*/
|
|
88
|
-
get assetIndexLoader(): Task<readonly [string, string | null], import('../transcoding/types/index.ts').MediaEngine>;
|
|
89
81
|
/**
|
|
90
82
|
* Legacy getter for fragment index task (maps to videoSegmentIdTask)
|
|
83
|
+
* Still used by EFCaptions
|
|
91
84
|
*/
|
|
92
85
|
get fragmentIndexTask(): Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number], number | undefined>;
|
|
93
|
-
/**
|
|
94
|
-
* Legacy getter for seek task (maps to videoSeekTask)
|
|
95
|
-
*/
|
|
96
|
-
get seekTask(): Task<readonly [number, import('./EFMedia/BufferedSeekingInput.ts').BufferedSeekingInput | undefined], import('mediabunny').VideoSample | undefined>;
|
|
97
|
-
/**
|
|
98
|
-
* Legacy getter for media segments task (maps to videoSegmentFetchTask)
|
|
99
|
-
*/
|
|
100
|
-
get mediaSegmentsTask(): Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number | undefined], ArrayBuffer>;
|
|
101
|
-
/**
|
|
102
|
-
* Legacy getter for asset segment keys task (maps to videoSegmentIdTask)
|
|
103
|
-
*/
|
|
104
|
-
get assetSegmentKeysTask(): Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number], number | undefined>;
|
|
105
|
-
/**
|
|
106
|
-
* Legacy getter for asset init segments task (maps to videoInitSegmentFetchTask)
|
|
107
|
-
*/
|
|
108
|
-
get assetInitSegmentsTask(): Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined], ArrayBuffer>;
|
|
109
|
-
/**
|
|
110
|
-
* Legacy getter for asset segment loader (maps to videoSegmentFetchTask)
|
|
111
|
-
*/
|
|
112
|
-
get assetSegmentLoader(): Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number | undefined], ArrayBuffer>;
|
|
113
|
-
/**
|
|
114
|
-
* Legacy getter for video asset task (maps to videoBufferTask)
|
|
115
|
-
*/
|
|
116
|
-
get videoAssetTask(): Task<readonly [number], import('./EFMedia/videoTasks/makeVideoBufferTask.ts').VideoBufferState>;
|
|
117
86
|
/**
|
|
118
87
|
* Clean up resources when component is disconnected
|
|
119
88
|
*/
|
package/dist/elements/EFVideo.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
1
2
|
import { EFMedia } from "./EFMedia.js";
|
|
2
3
|
import { TWMixin } from "../gui/TWMixin2.js";
|
|
3
4
|
import { DelayedLoadingState } from "../DelayedLoadingState.js";
|
|
@@ -94,6 +95,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
94
95
|
message: ""
|
|
95
96
|
};
|
|
96
97
|
this.frameTask = new Task(this, {
|
|
98
|
+
autoRun: EF_INTERACTIVE,
|
|
97
99
|
args: () => [this.desiredSeekTimeMs],
|
|
98
100
|
onError: (error) => {
|
|
99
101
|
console.error("frameTask error", error);
|
|
@@ -117,8 +119,11 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
117
119
|
const sample = this.videoSeekTask.value;
|
|
118
120
|
if (sample) {
|
|
119
121
|
const videoFrame = sample.toVideoFrame();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
try {
|
|
123
|
+
this.displayFrame(videoFrame, _seekToMs);
|
|
124
|
+
} finally {
|
|
125
|
+
videoFrame.close();
|
|
126
|
+
}
|
|
122
127
|
}
|
|
123
128
|
if (!isProductionRendering) {
|
|
124
129
|
if (!this.rootTimegroup || this.rootTimegroup.currentTimeMs === 0 && this.desiredSeekTimeMs === 0) return;
|
|
@@ -150,7 +155,11 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
150
155
|
`;
|
|
151
156
|
}
|
|
152
157
|
get canvasElement() {
|
|
153
|
-
|
|
158
|
+
const referencedCanvas = this.canvasRef.value;
|
|
159
|
+
if (referencedCanvas) return referencedCanvas;
|
|
160
|
+
const shadowCanvas = this.shadowRoot?.querySelector("canvas");
|
|
161
|
+
if (shadowCanvas) return shadowCanvas;
|
|
162
|
+
return void 0;
|
|
154
163
|
}
|
|
155
164
|
updated(changedProperties) {
|
|
156
165
|
super.updated(changedProperties);
|
|
@@ -234,60 +243,13 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
234
243
|
return currentTime >= renderStartTime;
|
|
235
244
|
}
|
|
236
245
|
/**
|
|
237
|
-
* Effective mode - always returns "asset" for EFVideo
|
|
238
|
-
*/
|
|
239
|
-
get effectiveMode() {
|
|
240
|
-
return "asset";
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Legacy getter for asset index loader (maps to mediaEngine task)
|
|
244
|
-
*/
|
|
245
|
-
get assetIndexLoader() {
|
|
246
|
-
return this.mediaEngineTask;
|
|
247
|
-
}
|
|
248
|
-
/**
|
|
249
246
|
* Legacy getter for fragment index task (maps to videoSegmentIdTask)
|
|
247
|
+
* Still used by EFCaptions
|
|
250
248
|
*/
|
|
251
249
|
get fragmentIndexTask() {
|
|
252
250
|
return this.videoSegmentIdTask;
|
|
253
251
|
}
|
|
254
252
|
/**
|
|
255
|
-
* Legacy getter for seek task (maps to videoSeekTask)
|
|
256
|
-
*/
|
|
257
|
-
get seekTask() {
|
|
258
|
-
return this.videoSeekTask;
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Legacy getter for media segments task (maps to videoSegmentFetchTask)
|
|
262
|
-
*/
|
|
263
|
-
get mediaSegmentsTask() {
|
|
264
|
-
return this.videoSegmentFetchTask;
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Legacy getter for asset segment keys task (maps to videoSegmentIdTask)
|
|
268
|
-
*/
|
|
269
|
-
get assetSegmentKeysTask() {
|
|
270
|
-
return this.videoSegmentIdTask;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Legacy getter for asset init segments task (maps to videoInitSegmentFetchTask)
|
|
274
|
-
*/
|
|
275
|
-
get assetInitSegmentsTask() {
|
|
276
|
-
return this.videoInitSegmentFetchTask;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Legacy getter for asset segment loader (maps to videoSegmentFetchTask)
|
|
280
|
-
*/
|
|
281
|
-
get assetSegmentLoader() {
|
|
282
|
-
return this.videoSegmentFetchTask;
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Legacy getter for video asset task (maps to videoBufferTask)
|
|
286
|
-
*/
|
|
287
|
-
get videoAssetTask() {
|
|
288
|
-
return this.videoBufferTask;
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
253
|
* Clean up resources when component is disconnected
|
|
292
254
|
*/
|
|
293
255
|
disconnectedCallback() {
|
|
@@ -34,7 +34,7 @@ var SampleBuffer = class {
|
|
|
34
34
|
const sampleStartMs = roundToMilliseconds((sample.timestamp || 0) * 1e3);
|
|
35
35
|
const sampleDurationMs = roundToMilliseconds((sample.duration || 0) * 1e3);
|
|
36
36
|
const sampleEndMs = roundToMilliseconds(sampleStartMs + sampleDurationMs);
|
|
37
|
-
if (targetTimeMs >= sampleStartMs && targetTimeMs
|
|
37
|
+
if (targetTimeMs >= sampleStartMs && targetTimeMs < sampleEndMs) return sample;
|
|
38
38
|
}
|
|
39
39
|
return void 0;
|
|
40
40
|
}
|
|
@@ -4,7 +4,7 @@ declare class TestContext extends TestContext_base {
|
|
|
4
4
|
}
|
|
5
5
|
declare const TestContextElement_base: (new (...args: any[]) => import('./ContextMixin.js').ContextMixinInterface) & typeof LitElement;
|
|
6
6
|
declare class TestContextElement extends TestContextElement_base {
|
|
7
|
-
render(): import('
|
|
7
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
8
8
|
}
|
|
9
9
|
declare global {
|
|
10
10
|
interface HTMLElementTagNameMap {
|
package/dist/gui/ContextMixin.js
CHANGED
|
@@ -149,7 +149,7 @@ function ContextMixin(superClass) {
|
|
|
149
149
|
if (newTimegroup !== this.targetTimegroup) {
|
|
150
150
|
this.targetTimegroup = newTimegroup;
|
|
151
151
|
shouldUpdate = true;
|
|
152
|
-
}
|
|
152
|
+
} else if (mutation.target instanceof Element && (mutation.target.tagName === "EF-TIMEGROUP" || mutation.target.closest("ef-timegroup"))) shouldUpdate = true;
|
|
153
153
|
} else if (mutation.type === "attributes") {
|
|
154
154
|
if (mutation.attributeName === "duration" || mutation.attributeName === "mode" || mutation.target instanceof Element && (mutation.target.tagName === "EF-TIMEGROUP" || mutation.target.closest("ef-timegroup"))) shouldUpdate = true;
|
|
155
155
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.22-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"license": "UNLICENSED",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@bramus/style-observer": "^1.3.0",
|
|
30
|
-
"@editframe/assets": "0.18.
|
|
30
|
+
"@editframe/assets": "0.18.22-beta.0",
|
|
31
31
|
"@lit/context": "^1.1.2",
|
|
32
32
|
"@lit/task": "^1.0.1",
|
|
33
33
|
"d3": "^7.9.0",
|
|
@@ -237,9 +237,6 @@ describe("EFAudio", () => {
|
|
|
237
237
|
|
|
238
238
|
// Should coordinate the expected tasks
|
|
239
239
|
expect(audio.fragmentIndexTask).toBeDefined();
|
|
240
|
-
expect(audio.mediaSegmentsTask).toBeDefined();
|
|
241
|
-
expect(audio.seekTask).toBeDefined();
|
|
242
|
-
expect(audio.videoAssetTask).toBeDefined();
|
|
243
240
|
});
|
|
244
241
|
|
|
245
242
|
test("frameTask handles missing dependencies", ({ expect }) => {
|
package/src/elements/EFAudio.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Task } from "@lit/task";
|
|
|
2
2
|
import { html } from "lit";
|
|
3
3
|
import { customElement, property } from "lit/decorators.js";
|
|
4
4
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
5
6
|
import { TWMixin } from "../gui/TWMixin.js";
|
|
6
7
|
import { EFMedia } from "./EFMedia.js";
|
|
7
8
|
|
|
@@ -21,6 +22,7 @@ export class EFAudio extends TWMixin(EFMedia) {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
frameTask = new Task(this, {
|
|
25
|
+
autoRun: EF_INTERACTIVE,
|
|
24
26
|
args: () =>
|
|
25
27
|
[
|
|
26
28
|
this.audioBufferTask.status,
|
|
@@ -37,34 +39,13 @@ export class EFAudio extends TWMixin(EFMedia) {
|
|
|
37
39
|
},
|
|
38
40
|
});
|
|
39
41
|
|
|
40
|
-
// Getter properties for backward compatibility with tests
|
|
41
42
|
/**
|
|
42
43
|
* Legacy getter for fragment index task (maps to audioSegmentIdTask)
|
|
44
|
+
* Still used by EFCaptions
|
|
43
45
|
*/
|
|
44
46
|
get fragmentIndexTask() {
|
|
45
47
|
return this.audioSegmentIdTask;
|
|
46
48
|
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Legacy getter for media segments task (maps to audioSegmentFetchTask)
|
|
50
|
-
*/
|
|
51
|
-
get mediaSegmentsTask() {
|
|
52
|
-
return this.audioSegmentFetchTask;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Legacy getter for seek task (maps to audioSeekTask)
|
|
57
|
-
*/
|
|
58
|
-
get seekTask() {
|
|
59
|
-
return this.audioSeekTask;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Legacy getter for audio asset task (maps to audioBufferTask)
|
|
64
|
-
*/
|
|
65
|
-
get videoAssetTask() {
|
|
66
|
-
return this.audioBufferTask;
|
|
67
|
-
}
|
|
68
49
|
}
|
|
69
50
|
|
|
70
51
|
declare global {
|
|
@@ -40,7 +40,7 @@ describe("AssetMediaEngine", () => {
|
|
|
40
40
|
mediaEngine,
|
|
41
41
|
expect,
|
|
42
42
|
}) => {
|
|
43
|
-
expect(mediaEngine.durationMs).toBeCloseTo(
|
|
43
|
+
expect(mediaEngine.durationMs).toBeCloseTo(10031, 0); // Updated: improved mediabunny processing changed duration
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
test("provides source URL from constructor", async ({
|
|
@@ -97,4 +97,42 @@ describe("AssetMediaEngine", () => {
|
|
|
97
97
|
expect(mediaEngine.computeSegmentId(500, audio as any)).toBe(0);
|
|
98
98
|
expect(mediaEngine.computeSegmentId(1500, audio as any)).toBe(0);
|
|
99
99
|
});
|
|
100
|
+
|
|
101
|
+
describe("bars n tone segment id computation", () => {
|
|
102
|
+
test("computes 0ms is 0", ({ expect, mediaEngine }) => {
|
|
103
|
+
expect(
|
|
104
|
+
mediaEngine.computeSegmentId(0, mediaEngine.getVideoRendition()),
|
|
105
|
+
).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("computes 2000 is 1", ({ expect, mediaEngine }) => {
|
|
109
|
+
expect(
|
|
110
|
+
mediaEngine.computeSegmentId(2000, mediaEngine.getVideoRendition()),
|
|
111
|
+
).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("computes 4000 is 2", ({ expect, mediaEngine }) => {
|
|
115
|
+
expect(
|
|
116
|
+
mediaEngine.computeSegmentId(4000, mediaEngine.getVideoRendition()),
|
|
117
|
+
).toBe(2);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("computes 6000 is 3", ({ expect, mediaEngine }) => {
|
|
121
|
+
expect(
|
|
122
|
+
mediaEngine.computeSegmentId(6000, mediaEngine.getVideoRendition()),
|
|
123
|
+
).toBe(3);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("computes 8000 is 4", ({ expect, mediaEngine }) => {
|
|
127
|
+
expect(
|
|
128
|
+
mediaEngine.computeSegmentId(8000, mediaEngine.getVideoRendition()),
|
|
129
|
+
).toBe(4);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("computes 7975 is 3", ({ expect, mediaEngine }) => {
|
|
133
|
+
expect(
|
|
134
|
+
mediaEngine.computeSegmentId(7975, mediaEngine.getVideoRendition()),
|
|
135
|
+
).toBe(3);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
100
138
|
});
|
|
@@ -214,7 +214,7 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
|
214
214
|
return segmentRanges;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
computeSegmentId(
|
|
217
|
+
computeSegmentId(seekTimeMs: number, rendition: MediaRendition) {
|
|
218
218
|
if (!rendition.trackId) {
|
|
219
219
|
throw new Error("Track ID is required for asset metadata");
|
|
220
220
|
}
|
|
@@ -227,11 +227,12 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
|
227
227
|
// Apply startTimeOffsetMs to map user timeline to media timeline for segment selection
|
|
228
228
|
const startTimeOffsetMs =
|
|
229
229
|
("startTimeOffsetMs" in rendition && rendition.startTimeOffsetMs) || 0;
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
|
|
231
|
+
const offsetSeekTimeMs = roundToMilliseconds(
|
|
232
|
+
seekTimeMs + startTimeOffsetMs,
|
|
232
233
|
);
|
|
233
234
|
// Convert to timescale units using consistent precision
|
|
234
|
-
const scaledSeekTime = convertToScaledTime(
|
|
235
|
+
const scaledSeekTime = convertToScaledTime(offsetSeekTimeMs, timescale);
|
|
235
236
|
|
|
236
237
|
// Find the segment that contains the actual seek time
|
|
237
238
|
for (let i = segments.length - 1; i >= 0; i--) {
|