@editframe/elements 0.26.2-beta.0 → 0.26.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/EFTimegroup.js +7 -2
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/package.json +2 -2
- package/scripts/build-css.js +3 -3
- package/tsdown.config.ts +1 -1
- package/types.json +1 -1
- package/src/elements/ContextProxiesController.ts +0 -124
- package/src/elements/CrossUpdateController.ts +0 -22
- package/src/elements/EFAudio.browsertest.ts +0 -706
- package/src/elements/EFAudio.ts +0 -56
- package/src/elements/EFCaptions.browsertest.ts +0 -1960
- package/src/elements/EFCaptions.ts +0 -823
- package/src/elements/EFImage.browsertest.ts +0 -120
- package/src/elements/EFImage.ts +0 -113
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
- package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
- package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
- package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
- package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
- package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
- package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
- package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
- package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
- package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
- package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
- package/src/elements/EFMedia.browsertest.ts +0 -872
- package/src/elements/EFMedia.ts +0 -341
- package/src/elements/EFSourceMixin.ts +0 -60
- package/src/elements/EFSurface.browsertest.ts +0 -151
- package/src/elements/EFSurface.ts +0 -142
- package/src/elements/EFTemporal.browsertest.ts +0 -215
- package/src/elements/EFTemporal.ts +0 -800
- package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
- package/src/elements/EFThumbnailStrip.ts +0 -906
- package/src/elements/EFTimegroup.browsertest.ts +0 -870
- package/src/elements/EFTimegroup.ts +0 -878
- package/src/elements/EFVideo.browsertest.ts +0 -1482
- package/src/elements/EFVideo.ts +0 -564
- package/src/elements/EFWaveform.ts +0 -547
- package/src/elements/FetchContext.browsertest.ts +0 -401
- package/src/elements/FetchMixin.ts +0 -38
- package/src/elements/SampleBuffer.ts +0 -94
- package/src/elements/TargetController.browsertest.ts +0 -230
- package/src/elements/TargetController.ts +0 -224
- package/src/elements/TimegroupController.ts +0 -26
- package/src/elements/durationConverter.ts +0 -35
- package/src/elements/parseTimeToMs.ts +0 -9
- package/src/elements/printTaskStatus.ts +0 -16
- package/src/elements/renderTemporalAudio.ts +0 -108
- package/src/elements/updateAnimations.browsertest.ts +0 -1884
- package/src/elements/updateAnimations.ts +0 -217
- package/src/elements/util.ts +0 -24
- package/src/gui/ContextMixin.browsertest.ts +0 -860
- package/src/gui/ContextMixin.ts +0 -562
- package/src/gui/Controllable.browsertest.ts +0 -258
- package/src/gui/Controllable.ts +0 -41
- package/src/gui/EFConfiguration.ts +0 -40
- package/src/gui/EFControls.browsertest.ts +0 -389
- package/src/gui/EFControls.ts +0 -195
- package/src/gui/EFDial.browsertest.ts +0 -84
- package/src/gui/EFDial.ts +0 -172
- package/src/gui/EFFilmstrip.browsertest.ts +0 -712
- package/src/gui/EFFilmstrip.ts +0 -1349
- package/src/gui/EFFitScale.ts +0 -152
- package/src/gui/EFFocusOverlay.ts +0 -79
- package/src/gui/EFPause.browsertest.ts +0 -202
- package/src/gui/EFPause.ts +0 -73
- package/src/gui/EFPlay.browsertest.ts +0 -202
- package/src/gui/EFPlay.ts +0 -73
- package/src/gui/EFPreview.ts +0 -74
- package/src/gui/EFResizableBox.browsertest.ts +0 -79
- package/src/gui/EFResizableBox.ts +0 -898
- package/src/gui/EFScrubber.ts +0 -151
- package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
- package/src/gui/EFTimeDisplay.ts +0 -55
- package/src/gui/EFToggleLoop.ts +0 -35
- package/src/gui/EFTogglePlay.ts +0 -70
- package/src/gui/EFWorkbench.ts +0 -115
- package/src/gui/PlaybackController.ts +0 -527
- package/src/gui/TWMixin.css +0 -6
- package/src/gui/TWMixin.ts +0 -61
- package/src/gui/TargetOrContextMixin.ts +0 -185
- package/src/gui/currentTimeContext.ts +0 -5
- package/src/gui/durationContext.ts +0 -3
- package/src/gui/efContext.ts +0 -6
- package/src/gui/fetchContext.ts +0 -5
- package/src/gui/focusContext.ts +0 -7
- package/src/gui/focusedElementContext.ts +0 -5
- package/src/gui/playingContext.ts +0 -5
- package/src/otel/BridgeSpanExporter.ts +0 -150
- package/src/otel/setupBrowserTracing.ts +0 -73
- package/src/otel/tracingHelpers.ts +0 -251
- package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
- package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
- package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
- package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
- package/src/transcoding/types/index.ts +0 -312
- package/src/transcoding/utils/MediaUtils.ts +0 -63
- package/src/transcoding/utils/UrlGenerator.ts +0 -68
- package/src/transcoding/utils/constants.ts +0 -36
- package/src/utils/LRUCache.test.ts +0 -274
- package/src/utils/LRUCache.ts +0 -696
package/src/elements/EFMedia.ts
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { provide } from "@lit/context";
|
|
2
|
-
import { css, LitElement, type PropertyValueMap } from "lit";
|
|
3
|
-
import { property, state } from "lit/decorators.js";
|
|
4
|
-
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
5
|
-
import type { ControllableInterface } from "../gui/Controllable.js";
|
|
6
|
-
import { efContext } from "../gui/efContext.js";
|
|
7
|
-
import { withSpan } from "../otel/tracingHelpers.js";
|
|
8
|
-
import type { AudioSpan } from "../transcoding/types/index.ts";
|
|
9
|
-
import { UrlGenerator } from "../transcoding/utils/UrlGenerator.ts";
|
|
10
|
-
import { makeAudioBufferTask } from "./EFMedia/audioTasks/makeAudioBufferTask.ts";
|
|
11
|
-
import { makeAudioFrequencyAnalysisTask } from "./EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts";
|
|
12
|
-
import { makeAudioInitSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts";
|
|
13
|
-
import { makeAudioInputTask } from "./EFMedia/audioTasks/makeAudioInputTask.ts";
|
|
14
|
-
import { makeAudioSeekTask } from "./EFMedia/audioTasks/makeAudioSeekTask.ts";
|
|
15
|
-
import { makeAudioSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioSegmentFetchTask.ts";
|
|
16
|
-
import { makeAudioSegmentIdTask } from "./EFMedia/audioTasks/makeAudioSegmentIdTask.ts";
|
|
17
|
-
import { makeAudioTimeDomainAnalysisTask } from "./EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts";
|
|
18
|
-
import { fetchAudioSpanningTime } from "./EFMedia/shared/AudioSpanUtils.ts";
|
|
19
|
-
import { makeMediaEngineTask } from "./EFMedia/tasks/makeMediaEngineTask.ts";
|
|
20
|
-
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
21
|
-
import { EFTemporal } from "./EFTemporal.js";
|
|
22
|
-
import { FetchMixin } from "./FetchMixin.js";
|
|
23
|
-
import { renderTemporalAudio } from "./renderTemporalAudio.js";
|
|
24
|
-
import { EFTargetable } from "./TargetController.ts";
|
|
25
|
-
|
|
26
|
-
// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
|
|
27
|
-
declare global {
|
|
28
|
-
var EF_FRAMEGEN: import("../EF_FRAMEGEN.js").EFFramegen;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const freqWeightsCache = new Map<number, Float32Array>();
|
|
32
|
-
|
|
33
|
-
export class IgnorableError extends Error {}
|
|
34
|
-
|
|
35
|
-
export const deepGetMediaElements = (
|
|
36
|
-
element: Element,
|
|
37
|
-
medias: EFMedia[] = [],
|
|
38
|
-
) => {
|
|
39
|
-
for (const child of Array.from(element.children)) {
|
|
40
|
-
if (child instanceof EFMedia) {
|
|
41
|
-
medias.push(child);
|
|
42
|
-
} else {
|
|
43
|
-
deepGetMediaElements(child, medias);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return medias;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export class EFMedia extends EFTargetable(
|
|
50
|
-
EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
51
|
-
assetType: "isobmff_files",
|
|
52
|
-
}),
|
|
53
|
-
) {
|
|
54
|
-
@provide({ context: efContext })
|
|
55
|
-
get efContext(): ControllableInterface | null {
|
|
56
|
-
return this.rootTimegroup ?? this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Sample buffer size configuration
|
|
60
|
-
static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
|
|
61
|
-
static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
|
|
62
|
-
|
|
63
|
-
static get observedAttributes() {
|
|
64
|
-
// biome-ignore lint/complexity/noThisInStatic: We need to access super
|
|
65
|
-
const parentAttributes = super.observedAttributes || [];
|
|
66
|
-
return [
|
|
67
|
-
...parentAttributes,
|
|
68
|
-
"mute",
|
|
69
|
-
"fft-size",
|
|
70
|
-
"fft-decay",
|
|
71
|
-
"fft-gain",
|
|
72
|
-
"interpolate-frequencies",
|
|
73
|
-
"asset-id",
|
|
74
|
-
"audio-buffer-duration",
|
|
75
|
-
"max-audio-buffer-fetches",
|
|
76
|
-
"enable-audio-buffering",
|
|
77
|
-
"sourcein",
|
|
78
|
-
"sourceout",
|
|
79
|
-
];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
static styles = [
|
|
83
|
-
css`
|
|
84
|
-
:host {
|
|
85
|
-
display: block;
|
|
86
|
-
position: relative;
|
|
87
|
-
overflow: hidden;
|
|
88
|
-
}
|
|
89
|
-
`,
|
|
90
|
-
];
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Duration in milliseconds for audio buffering ahead of current time
|
|
94
|
-
* @domAttribute "audio-buffer-duration"
|
|
95
|
-
*/
|
|
96
|
-
@property({ type: Number, attribute: "audio-buffer-duration" })
|
|
97
|
-
audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Maximum number of concurrent audio segment fetches for buffering
|
|
101
|
-
* @domAttribute "max-audio-buffer-fetches"
|
|
102
|
-
*/
|
|
103
|
-
@property({ type: Number, attribute: "max-audio-buffer-fetches" })
|
|
104
|
-
maxAudioBufferFetches = 2;
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Enable/disable audio buffering system
|
|
108
|
-
* @domAttribute "enable-audio-buffering"
|
|
109
|
-
*/
|
|
110
|
-
@property({ type: Boolean, attribute: "enable-audio-buffering" })
|
|
111
|
-
enableAudioBuffering = true;
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Mute/unmute the media element
|
|
115
|
-
* @domAttribute "mute"
|
|
116
|
-
*/
|
|
117
|
-
@property({
|
|
118
|
-
type: Boolean,
|
|
119
|
-
attribute: "mute",
|
|
120
|
-
reflect: true,
|
|
121
|
-
})
|
|
122
|
-
mute = false;
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* FFT size for frequency analysis
|
|
126
|
-
* @domAttribute "fft-size"
|
|
127
|
-
*/
|
|
128
|
-
@property({ type: Number, attribute: "fft-size", reflect: true })
|
|
129
|
-
fftSize = 128;
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* FFT decay rate for frequency analysis
|
|
133
|
-
* @domAttribute "fft-decay"
|
|
134
|
-
*/
|
|
135
|
-
@property({ type: Number, attribute: "fft-decay", reflect: true })
|
|
136
|
-
fftDecay = 8;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* FFT gain for frequency analysis
|
|
140
|
-
* @domAttribute "fft-gain"
|
|
141
|
-
*/
|
|
142
|
-
@property({ type: Number, attribute: "fft-gain", reflect: true })
|
|
143
|
-
fftGain = 3.0;
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Enable/disable frequency interpolation
|
|
147
|
-
* @domAttribute "interpolate-frequencies"
|
|
148
|
-
*/
|
|
149
|
-
@property({
|
|
150
|
-
type: Boolean,
|
|
151
|
-
attribute: "interpolate-frequencies",
|
|
152
|
-
reflect: true,
|
|
153
|
-
})
|
|
154
|
-
interpolateFrequencies = false;
|
|
155
|
-
|
|
156
|
-
// Update FREQ_WEIGHTS to use the instance fftSize instead of a static value
|
|
157
|
-
get FREQ_WEIGHTS() {
|
|
158
|
-
if (freqWeightsCache.has(this.fftSize)) {
|
|
159
|
-
// biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above
|
|
160
|
-
return freqWeightsCache.get(this.fftSize)!;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const weights = new Float32Array(this.fftSize / 2).map((_, i) => {
|
|
164
|
-
const frequency = (i * 48000) / this.fftSize;
|
|
165
|
-
if (frequency < 60) return 0.3;
|
|
166
|
-
if (frequency < 250) return 0.4;
|
|
167
|
-
if (frequency < 500) return 0.6;
|
|
168
|
-
if (frequency < 2000) return 0.8;
|
|
169
|
-
if (frequency < 4000) return 1.2;
|
|
170
|
-
if (frequency < 8000) return 1.6;
|
|
171
|
-
return 2.0;
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
freqWeightsCache.set(this.fftSize, weights);
|
|
175
|
-
return weights;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Helper getter for backwards compatibility
|
|
179
|
-
get shouldInterpolateFrequencies() {
|
|
180
|
-
return this.interpolateFrequencies;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
get urlGenerator() {
|
|
184
|
-
return new UrlGenerator(() => this.apiHost ?? "");
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
mediaEngineTask = makeMediaEngineTask(this);
|
|
188
|
-
|
|
189
|
-
audioSegmentIdTask = makeAudioSegmentIdTask(this);
|
|
190
|
-
audioInitSegmentFetchTask = makeAudioInitSegmentFetchTask(this);
|
|
191
|
-
audioSegmentFetchTask = makeAudioSegmentFetchTask(this);
|
|
192
|
-
audioInputTask = makeAudioInputTask(this);
|
|
193
|
-
audioSeekTask = makeAudioSeekTask(this);
|
|
194
|
-
|
|
195
|
-
audioBufferTask = makeAudioBufferTask(this);
|
|
196
|
-
|
|
197
|
-
// Audio analysis tasks for frequency and time domain analysis
|
|
198
|
-
byteTimeDomainTask = makeAudioTimeDomainAnalysisTask(this);
|
|
199
|
-
frequencyDataTask = makeAudioFrequencyAnalysisTask(this);
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* The unique identifier for the media asset.
|
|
203
|
-
* This property can be set programmatically or via the "asset-id" attribute.
|
|
204
|
-
* @domAttribute "asset-id"
|
|
205
|
-
*/
|
|
206
|
-
@property({ type: String, attribute: "asset-id", reflect: true })
|
|
207
|
-
assetId: string | null = null;
|
|
208
|
-
|
|
209
|
-
get intrinsicDurationMs() {
|
|
210
|
-
return this.mediaEngineTask.value?.durationMs ?? 0;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
protected updated(
|
|
214
|
-
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
215
|
-
): void {
|
|
216
|
-
super.updated(changedProperties);
|
|
217
|
-
|
|
218
|
-
// Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property
|
|
219
|
-
const newCurrentSourceTimeMs = this.currentSourceTimeMs;
|
|
220
|
-
if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {
|
|
221
|
-
this.executeSeek(newCurrentSourceTimeMs);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
225
|
-
this.executeSeek(this.currentSourceTimeMs);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Check if trim/source properties changed that affect duration
|
|
229
|
-
const durationAffectingProps = [
|
|
230
|
-
"_trimStartMs",
|
|
231
|
-
"_trimEndMs",
|
|
232
|
-
"_sourceInMs",
|
|
233
|
-
"_sourceOutMs",
|
|
234
|
-
];
|
|
235
|
-
|
|
236
|
-
const hasDurationChange = durationAffectingProps.some((prop) =>
|
|
237
|
-
changedProperties.has(prop),
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
if (hasDurationChange) {
|
|
241
|
-
// Notify parent timegroup to recalculate its duration (same pattern as EFCaptions)
|
|
242
|
-
if (this.parentTimegroup) {
|
|
243
|
-
this.parentTimegroup.requestUpdate("durationMs");
|
|
244
|
-
this.parentTimegroup.requestUpdate("currentTime");
|
|
245
|
-
|
|
246
|
-
// Also find and directly notify any context provider (ContextMixin)
|
|
247
|
-
let parent = this.parentNode;
|
|
248
|
-
while (parent) {
|
|
249
|
-
if (isContextMixin(parent)) {
|
|
250
|
-
parent.dispatchEvent(
|
|
251
|
-
new CustomEvent("child-duration-changed", {
|
|
252
|
-
detail: { source: this },
|
|
253
|
-
}),
|
|
254
|
-
);
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
parent = parent.parentNode;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
get hasOwnDuration() {
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
@state()
|
|
268
|
-
private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading
|
|
269
|
-
|
|
270
|
-
get desiredSeekTimeMs() {
|
|
271
|
-
return this._desiredSeekTimeMs;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
set desiredSeekTimeMs(value: number) {
|
|
275
|
-
if (this._desiredSeekTimeMs !== value) {
|
|
276
|
-
this._desiredSeekTimeMs = value;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
protected async executeSeek(seekToMs: number) {
|
|
281
|
-
// The seekToMs parameter should be the timeline-relative media time
|
|
282
|
-
// calculated from currentSourceTimeMs which includes timeline positioning
|
|
283
|
-
this.desiredSeekTimeMs = seekToMs;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Main integration method for EFTimegroup audio playback
|
|
288
|
-
* Now powered by clean, testable utility functions
|
|
289
|
-
* Returns undefined if no audio rendition is available
|
|
290
|
-
*/
|
|
291
|
-
async fetchAudioSpanningTime(
|
|
292
|
-
fromMs: number,
|
|
293
|
-
toMs: number,
|
|
294
|
-
signal: AbortSignal = new AbortController().signal,
|
|
295
|
-
): Promise<AudioSpan | undefined> {
|
|
296
|
-
return withSpan(
|
|
297
|
-
"media.fetchAudioSpanningTime",
|
|
298
|
-
{
|
|
299
|
-
elementId: this.id || "unknown",
|
|
300
|
-
tagName: this.tagName.toLowerCase(),
|
|
301
|
-
fromMs,
|
|
302
|
-
toMs,
|
|
303
|
-
durationMs: toMs - fromMs,
|
|
304
|
-
src: this.src || "none",
|
|
305
|
-
},
|
|
306
|
-
undefined,
|
|
307
|
-
async () => {
|
|
308
|
-
return fetchAudioSpanningTime(this, fromMs, toMs, signal);
|
|
309
|
-
},
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Wait for media engine to load and determine duration
|
|
315
|
-
* Ensures media is ready for playback
|
|
316
|
-
*/
|
|
317
|
-
async waitForMediaDurations(): Promise<void> {
|
|
318
|
-
if (this.mediaEngineTask.value) {
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
await this.mediaEngineTask.run();
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Returns media elements for playback audio rendering
|
|
326
|
-
* For standalone media, returns [this]; for timegroups, returns all descendants
|
|
327
|
-
* Used by PlaybackController for audio-driven playback
|
|
328
|
-
*/
|
|
329
|
-
getMediaElements(): EFMedia[] {
|
|
330
|
-
return [this];
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Render audio buffer for playback
|
|
335
|
-
* Called by PlaybackController during live playback
|
|
336
|
-
* Delegates to shared renderTemporalAudio utility for consistent behavior
|
|
337
|
-
*/
|
|
338
|
-
async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {
|
|
339
|
-
return renderTemporalAudio(this, fromMs, toMs);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { Task } from "@lit/task";
|
|
2
|
-
import type { LitElement } from "lit";
|
|
3
|
-
import { property } from "lit/decorators/property.js";
|
|
4
|
-
|
|
5
|
-
export declare class EFSourceMixinInterface {
|
|
6
|
-
apiHost?: string;
|
|
7
|
-
productionSrc(): string;
|
|
8
|
-
src: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface EFSourceMixinOptions {
|
|
12
|
-
assetType: string;
|
|
13
|
-
}
|
|
14
|
-
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
15
|
-
export function EFSourceMixin<T extends Constructor<LitElement>>(
|
|
16
|
-
superClass: T,
|
|
17
|
-
options: EFSourceMixinOptions,
|
|
18
|
-
) {
|
|
19
|
-
class EFSourceElement extends superClass {
|
|
20
|
-
get apiHost() {
|
|
21
|
-
const apiHost =
|
|
22
|
-
this.closest("ef-configuration")?.apiHost ??
|
|
23
|
-
this.closest("ef-workbench")?.apiHost ??
|
|
24
|
-
this.closest("ef-preview")?.apiHost;
|
|
25
|
-
|
|
26
|
-
return apiHost || "https://editframe.dev";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@property({ type: String })
|
|
30
|
-
src = "";
|
|
31
|
-
|
|
32
|
-
productionSrc() {
|
|
33
|
-
if (!this.md5SumLoader.value) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
`MD5 sum not available for ${this}. Cannot generate production URL`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (!this.apiHost) {
|
|
40
|
-
throw new Error(
|
|
41
|
-
`apiHost not available for ${this}. Cannot generate production URL`,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return `${this.apiHost}/api/v1/${options.assetType}/${this.md5SumLoader.value}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
md5SumLoader = new Task(this, {
|
|
49
|
-
autoRun: false,
|
|
50
|
-
args: () => [this.src] as const,
|
|
51
|
-
task: async ([src], { signal }) => {
|
|
52
|
-
const md5Path = `/@ef-asset/${src}`;
|
|
53
|
-
const response = await fetch(md5Path, { method: "HEAD", signal });
|
|
54
|
-
return response.headers.get("etag") ?? undefined;
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return EFSourceElement as Constructor<EFSourceMixinInterface> & T;
|
|
60
|
-
}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { html, render } from "lit";
|
|
2
|
-
import { beforeEach, describe } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { test as baseTest } from "../../test/useMSW.js";
|
|
5
|
-
|
|
6
|
-
import "./EFVideo.js";
|
|
7
|
-
import "./EFTimegroup.js";
|
|
8
|
-
import "../gui/EFPreview.js";
|
|
9
|
-
import "../gui/EFWorkbench.js";
|
|
10
|
-
import "./EFSurface.js";
|
|
11
|
-
|
|
12
|
-
import type { EFSurface } from "./EFSurface.js";
|
|
13
|
-
import type { EFTimegroup } from "./EFTimegroup.js";
|
|
14
|
-
import type { EFVideo } from "./EFVideo.js";
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
localStorage.clear();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const surfaceTest = baseTest.extend<{
|
|
21
|
-
timegroup: EFTimegroup;
|
|
22
|
-
video: EFVideo;
|
|
23
|
-
surface: EFSurface;
|
|
24
|
-
}>({
|
|
25
|
-
timegroup: async ({}, use) => {
|
|
26
|
-
const container = document.createElement("div");
|
|
27
|
-
render(
|
|
28
|
-
html`
|
|
29
|
-
<ef-configuration api-host="http://localhost:63315" signing-url="">
|
|
30
|
-
<ef-preview>
|
|
31
|
-
<ef-timegroup id="tg" mode="sequence" class="relative h-[360px] w-[640px] overflow-hidden bg-black">
|
|
32
|
-
<ef-video id="vid" src="bars-n-tone.mp4" style="width: 100%; height: 100%;"></ef-video>
|
|
33
|
-
<ef-surface id="surf" target="vid" style="position: absolute; inset: 0;"></ef-surface>
|
|
34
|
-
</ef-timegroup>
|
|
35
|
-
</ef-preview>
|
|
36
|
-
</ef-configuration>
|
|
37
|
-
`,
|
|
38
|
-
container,
|
|
39
|
-
);
|
|
40
|
-
document.body.appendChild(container);
|
|
41
|
-
const tg = container.querySelector("#tg") as EFTimegroup;
|
|
42
|
-
await tg.updateComplete;
|
|
43
|
-
await use(tg);
|
|
44
|
-
container.remove();
|
|
45
|
-
},
|
|
46
|
-
video: async ({ timegroup }, use) => {
|
|
47
|
-
const video = timegroup.querySelector("#vid") as EFVideo;
|
|
48
|
-
await video.updateComplete;
|
|
49
|
-
await use(video);
|
|
50
|
-
},
|
|
51
|
-
surface: async ({ timegroup }, use) => {
|
|
52
|
-
const surface = timegroup.querySelector("#surf") as unknown as EFSurface;
|
|
53
|
-
await surface.updateComplete;
|
|
54
|
-
await use(surface);
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe("EFSurface", () => {
|
|
59
|
-
surfaceTest("defines and renders a canvas", async ({ expect }) => {
|
|
60
|
-
const el = document.createElement("ef-surface");
|
|
61
|
-
document.body.appendChild(el);
|
|
62
|
-
await (el as any).updateComplete;
|
|
63
|
-
const canvas = el.shadowRoot?.querySelector("canvas");
|
|
64
|
-
expect(canvas).toBeTruthy();
|
|
65
|
-
expect((canvas as HTMLCanvasElement).tagName).toBe("CANVAS");
|
|
66
|
-
el.remove();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
surfaceTest(
|
|
70
|
-
"mirrors video canvas after a seek via EFTimegroup",
|
|
71
|
-
async ({ timegroup, video, surface, expect }) => {
|
|
72
|
-
// Ensure media engine initialized
|
|
73
|
-
await video.mediaEngineTask.run();
|
|
74
|
-
|
|
75
|
-
// Seek to a known time through timegroup (triggers frame tasks)
|
|
76
|
-
timegroup.currentTimeMs = 3000;
|
|
77
|
-
await timegroup.seekTask.taskComplete;
|
|
78
|
-
|
|
79
|
-
// After scheduling, surface should have mirrored pixel dimensions
|
|
80
|
-
const videoCanvas = (video as any).canvasElement as
|
|
81
|
-
| HTMLCanvasElement
|
|
82
|
-
| undefined;
|
|
83
|
-
const surfaceCanvas =
|
|
84
|
-
(surface.shadowRoot?.querySelector("canvas") as HTMLCanvasElement) ??
|
|
85
|
-
undefined;
|
|
86
|
-
|
|
87
|
-
expect(videoCanvas).toBeTruthy();
|
|
88
|
-
expect(surfaceCanvas).toBeTruthy();
|
|
89
|
-
expect(videoCanvas!.width).toBeGreaterThan(0);
|
|
90
|
-
expect(videoCanvas!.height).toBeGreaterThan(0);
|
|
91
|
-
|
|
92
|
-
// Surface copies pixel dimensions
|
|
93
|
-
expect(surfaceCanvas!.width).toBe(videoCanvas!.width);
|
|
94
|
-
expect(surfaceCanvas!.height).toBe(videoCanvas!.height);
|
|
95
|
-
},
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
surfaceTest(
|
|
99
|
-
"supports multiple surfaces mirroring the same source",
|
|
100
|
-
async ({ expect }) => {
|
|
101
|
-
const container = document.createElement("div");
|
|
102
|
-
render(
|
|
103
|
-
html`
|
|
104
|
-
<ef-configuration api-host="http://localhost:63315" signing-url="">
|
|
105
|
-
<ef-preview>
|
|
106
|
-
<ef-timegroup mode="sequence" class="relative h-[360px] w-[640px] overflow-hidden bg-black">
|
|
107
|
-
<ef-video id="v" src="bars-n-tone.mp4" style="width: 100%; height: 100%;"></ef-video>
|
|
108
|
-
<ef-surface id="s1" target="v" style="position: absolute; inset: 0;"></ef-surface>
|
|
109
|
-
<ef-surface id="s2" target="v" style="position: absolute; inset: 0;"></ef-surface>
|
|
110
|
-
</ef-timegroup>
|
|
111
|
-
</ef-preview>
|
|
112
|
-
</ef-configuration>
|
|
113
|
-
`,
|
|
114
|
-
container,
|
|
115
|
-
);
|
|
116
|
-
document.body.appendChild(container);
|
|
117
|
-
const timegroup = container.querySelector("ef-timegroup") as EFTimegroup;
|
|
118
|
-
const video = container.querySelector("ef-video") as EFVideo;
|
|
119
|
-
const s1 = container.querySelector("#s1") as unknown as EFSurface;
|
|
120
|
-
const s2 = container.querySelector("#s2") as unknown as EFSurface;
|
|
121
|
-
await timegroup.updateComplete;
|
|
122
|
-
await video.mediaEngineTask.run();
|
|
123
|
-
|
|
124
|
-
timegroup.currentTimeMs = 1000;
|
|
125
|
-
await timegroup.seekTask.taskComplete;
|
|
126
|
-
|
|
127
|
-
const vCanvas = (video as any).canvasElement as HTMLCanvasElement;
|
|
128
|
-
const c1 = s1.shadowRoot!.querySelector("canvas") as HTMLCanvasElement;
|
|
129
|
-
const c2 = s2.shadowRoot!.querySelector("canvas") as HTMLCanvasElement;
|
|
130
|
-
|
|
131
|
-
expect(vCanvas.width).toBeGreaterThan(0);
|
|
132
|
-
expect(c1.width).toBe(vCanvas.width);
|
|
133
|
-
expect(c2.width).toBe(vCanvas.width);
|
|
134
|
-
expect(c1.height).toBe(vCanvas.height);
|
|
135
|
-
expect(c2.height).toBe(vCanvas.height);
|
|
136
|
-
|
|
137
|
-
container.remove();
|
|
138
|
-
},
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
surfaceTest(
|
|
142
|
-
"handles missing video gracefully (no throw)",
|
|
143
|
-
async ({ expect }) => {
|
|
144
|
-
const el = document.createElement("ef-surface") as any;
|
|
145
|
-
document.body.appendChild(el);
|
|
146
|
-
await el.updateComplete;
|
|
147
|
-
await expect(el.frameTask.run()).resolves.toBeUndefined();
|
|
148
|
-
el.remove();
|
|
149
|
-
},
|
|
150
|
-
);
|
|
151
|
-
});
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { Task } from "@lit/task";
|
|
2
|
-
import { css, html, LitElement } from "lit";
|
|
3
|
-
import { customElement, property, state } from "lit/decorators.js";
|
|
4
|
-
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
|
-
import type { ContextMixinInterface } from "../gui/ContextMixin.ts";
|
|
6
|
-
import { TargetController } from "./TargetController.ts";
|
|
7
|
-
|
|
8
|
-
@customElement("ef-surface")
|
|
9
|
-
export class EFSurface extends LitElement {
|
|
10
|
-
static styles = [
|
|
11
|
-
css`
|
|
12
|
-
:host {
|
|
13
|
-
display: block;
|
|
14
|
-
position: relative;
|
|
15
|
-
}
|
|
16
|
-
canvas {
|
|
17
|
-
all: inherit;
|
|
18
|
-
width: 100%;
|
|
19
|
-
height: 100%;
|
|
20
|
-
display: block;
|
|
21
|
-
}
|
|
22
|
-
`,
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
canvasRef = createRef<HTMLCanvasElement>();
|
|
26
|
-
|
|
27
|
-
// @ts-expect-error controller is intentionally not referenced directly
|
|
28
|
-
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Used for side effects
|
|
29
|
-
#targetController: TargetController = new TargetController(this);
|
|
30
|
-
|
|
31
|
-
@state()
|
|
32
|
-
targetElement: ContextMixinInterface | null = null;
|
|
33
|
-
|
|
34
|
-
@property({ type: String })
|
|
35
|
-
target = "";
|
|
36
|
-
|
|
37
|
-
render() {
|
|
38
|
-
return html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Provide minimal temporal-like properties so EFTimegroup can schedule us
|
|
42
|
-
get rootTimegroup(): any {
|
|
43
|
-
// Prefer the target element's root timegroup if available
|
|
44
|
-
const target: any = this.targetElement;
|
|
45
|
-
if (target && "rootTimegroup" in target) {
|
|
46
|
-
return target.rootTimegroup;
|
|
47
|
-
}
|
|
48
|
-
// Fallback: nearest containing timegroup if any
|
|
49
|
-
let root: any = this.closest("ef-timegroup");
|
|
50
|
-
while (root?.parentTimegroup) {
|
|
51
|
-
root = root.parentTimegroup;
|
|
52
|
-
}
|
|
53
|
-
return root;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
get currentTimeMs(): number {
|
|
57
|
-
return this.rootTimegroup?.currentTimeMs ?? 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
get durationMs(): number {
|
|
61
|
-
return this.rootTimegroup?.durationMs ?? 0;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
get startTimeMs(): number {
|
|
65
|
-
return this.rootTimegroup?.startTimeMs ?? 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
get endTimeMs(): number {
|
|
69
|
-
return this.startTimeMs + this.durationMs;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Minimal integration with EFTimegroup's frame scheduling:
|
|
74
|
-
* - Waits for the target video element's frameTask to complete (ensuring it painted)
|
|
75
|
-
* - Copies the target's canvas into this element's canvas
|
|
76
|
-
*/
|
|
77
|
-
frameTask = new Task(this, {
|
|
78
|
-
autoRun: false,
|
|
79
|
-
args: () => [this.targetElement] as const,
|
|
80
|
-
task: async ([target]) => {
|
|
81
|
-
if (!target) return;
|
|
82
|
-
|
|
83
|
-
// Ensure the target has painted its frame for this tick
|
|
84
|
-
try {
|
|
85
|
-
const maybeTask = (target as any).frameTask;
|
|
86
|
-
if (maybeTask && typeof maybeTask.run === "function") {
|
|
87
|
-
// Run (idempotent) and then wait for completion
|
|
88
|
-
maybeTask.run();
|
|
89
|
-
await maybeTask.taskComplete;
|
|
90
|
-
}
|
|
91
|
-
} catch (_err) {
|
|
92
|
-
// Best-effort; continue to attempt copy
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.copyFromTarget(target);
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
protected updated(): void {
|
|
100
|
-
if (this.targetElement) {
|
|
101
|
-
this.copyFromTarget(this.targetElement);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Target resolution is handled by TargetController. No implicit discovery.
|
|
106
|
-
|
|
107
|
-
private getSourceCanvas(from: Element): HTMLCanvasElement | null {
|
|
108
|
-
const anyEl = from as any;
|
|
109
|
-
if ("canvasElement" in anyEl) {
|
|
110
|
-
return anyEl.canvasElement ?? null;
|
|
111
|
-
}
|
|
112
|
-
const sr = (from as HTMLElement).shadowRoot;
|
|
113
|
-
if (sr) {
|
|
114
|
-
const c = sr.querySelector("canvas");
|
|
115
|
-
return (c as HTMLCanvasElement) ?? null;
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private copyFromTarget(target: Element) {
|
|
121
|
-
const dst = this.canvasRef.value;
|
|
122
|
-
const src = this.getSourceCanvas(target);
|
|
123
|
-
if (!dst || !src) return;
|
|
124
|
-
if (!src.width || !src.height) return;
|
|
125
|
-
|
|
126
|
-
// Match source pixel size for a faithful mirror; layout scaling is handled by CSS
|
|
127
|
-
if (dst.width !== src.width || dst.height !== src.height) {
|
|
128
|
-
dst.width = src.width;
|
|
129
|
-
dst.height = src.height;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const ctx = dst.getContext("2d");
|
|
133
|
-
if (!ctx) return;
|
|
134
|
-
ctx.drawImage(src, 0, 0, dst.width, dst.height);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
declare global {
|
|
139
|
-
interface HTMLElementTagNameMap {
|
|
140
|
-
"ef-surface": EFSurface;
|
|
141
|
-
}
|
|
142
|
-
}
|