@editframe/elements 0.26.3-beta.0 → 0.30.0-beta.13

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.
Files changed (191) hide show
  1. package/dist/elements/EFSourceMixin.js +1 -1
  2. package/dist/elements/EFSourceMixin.js.map +1 -1
  3. package/dist/elements/EFSurface.d.ts +4 -4
  4. package/dist/elements/EFText.d.ts +52 -0
  5. package/dist/elements/EFText.js +319 -0
  6. package/dist/elements/EFText.js.map +1 -0
  7. package/dist/elements/EFTextSegment.d.ts +30 -0
  8. package/dist/elements/EFTextSegment.js +94 -0
  9. package/dist/elements/EFTextSegment.js.map +1 -0
  10. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  11. package/dist/elements/EFWaveform.d.ts +4 -4
  12. package/dist/elements/FetchMixin.js +22 -7
  13. package/dist/elements/FetchMixin.js.map +1 -1
  14. package/dist/elements/easingUtils.js +62 -0
  15. package/dist/elements/easingUtils.js.map +1 -0
  16. package/dist/elements/updateAnimations.js +57 -10
  17. package/dist/elements/updateAnimations.js.map +1 -1
  18. package/dist/gui/ContextMixin.js +11 -2
  19. package/dist/gui/ContextMixin.js.map +1 -1
  20. package/dist/gui/EFConfiguration.d.ts +4 -4
  21. package/dist/gui/EFControls.d.ts +2 -2
  22. package/dist/gui/EFDial.d.ts +4 -4
  23. package/dist/gui/EFDial.js +4 -2
  24. package/dist/gui/EFDial.js.map +1 -1
  25. package/dist/gui/EFFilmstrip.d.ts +32 -6
  26. package/dist/gui/EFFilmstrip.js +314 -50
  27. package/dist/gui/EFFilmstrip.js.map +1 -1
  28. package/dist/gui/EFFitScale.js +39 -15
  29. package/dist/gui/EFFitScale.js.map +1 -1
  30. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  31. package/dist/gui/EFPause.d.ts +4 -4
  32. package/dist/gui/EFPlay.d.ts +4 -4
  33. package/dist/gui/EFPreview.d.ts +4 -4
  34. package/dist/gui/EFPreview.js +2 -2
  35. package/dist/gui/EFPreview.js.map +1 -1
  36. package/dist/gui/EFResizableBox.d.ts +4 -4
  37. package/dist/gui/EFResizableBox.js +6 -3
  38. package/dist/gui/EFResizableBox.js.map +1 -1
  39. package/dist/gui/EFScrubber.d.ts +8 -5
  40. package/dist/gui/EFScrubber.js +64 -12
  41. package/dist/gui/EFScrubber.js.map +1 -1
  42. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  43. package/dist/gui/EFToggleLoop.d.ts +4 -4
  44. package/dist/gui/EFTogglePlay.d.ts +4 -4
  45. package/dist/gui/EFWorkbench.d.ts +4 -4
  46. package/dist/gui/EFWorkbench.js +16 -3
  47. package/dist/gui/EFWorkbench.js.map +1 -1
  48. package/dist/gui/TWMixin.js +1 -1
  49. package/dist/gui/TWMixin.js.map +1 -1
  50. package/dist/index.d.ts +3 -1
  51. package/dist/index.js +3 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/style.css +7 -120
  54. package/package.json +3 -3
  55. package/scripts/build-css.js +5 -9
  56. package/test/constants.ts +8 -0
  57. package/test/recordReplayProxyPlugin.js +76 -10
  58. package/test/setup.ts +32 -0
  59. package/test/useMSW.ts +3 -0
  60. package/test/useTranscodeMSW.ts +191 -0
  61. package/tsdown.config.ts +7 -5
  62. package/types.json +1 -1
  63. package/src/elements/ContextProxiesController.ts +0 -124
  64. package/src/elements/CrossUpdateController.ts +0 -22
  65. package/src/elements/EFAudio.browsertest.ts +0 -706
  66. package/src/elements/EFAudio.ts +0 -56
  67. package/src/elements/EFCaptions.browsertest.ts +0 -1960
  68. package/src/elements/EFCaptions.ts +0 -823
  69. package/src/elements/EFImage.browsertest.ts +0 -120
  70. package/src/elements/EFImage.ts +0 -113
  71. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +0 -224
  72. package/src/elements/EFMedia/AssetIdMediaEngine.ts +0 -110
  73. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +0 -140
  74. package/src/elements/EFMedia/AssetMediaEngine.ts +0 -385
  75. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +0 -400
  76. package/src/elements/EFMedia/BaseMediaEngine.ts +0 -505
  77. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +0 -386
  78. package/src/elements/EFMedia/BufferedSeekingInput.ts +0 -430
  79. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +0 -226
  80. package/src/elements/EFMedia/JitMediaEngine.ts +0 -256
  81. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +0 -679
  82. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +0 -117
  83. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -246
  84. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +0 -59
  85. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +0 -27
  86. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +0 -55
  87. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +0 -53
  88. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +0 -207
  89. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +0 -72
  90. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +0 -32
  91. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +0 -29
  92. package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +0 -95
  93. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +0 -184
  94. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +0 -129
  95. package/src/elements/EFMedia/shared/BufferUtils.ts +0 -342
  96. package/src/elements/EFMedia/shared/GlobalInputCache.ts +0 -77
  97. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +0 -44
  98. package/src/elements/EFMedia/shared/PrecisionUtils.ts +0 -46
  99. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +0 -246
  100. package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -56
  101. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +0 -227
  102. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +0 -167
  103. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +0 -88
  104. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +0 -76
  105. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +0 -61
  106. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +0 -114
  107. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -35
  108. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +0 -52
  109. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +0 -124
  110. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -44
  111. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -32
  112. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +0 -370
  113. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +0 -109
  114. package/src/elements/EFMedia.browsertest.ts +0 -872
  115. package/src/elements/EFMedia.ts +0 -341
  116. package/src/elements/EFSourceMixin.ts +0 -60
  117. package/src/elements/EFSurface.browsertest.ts +0 -151
  118. package/src/elements/EFSurface.ts +0 -142
  119. package/src/elements/EFTemporal.browsertest.ts +0 -215
  120. package/src/elements/EFTemporal.ts +0 -800
  121. package/src/elements/EFThumbnailStrip.browsertest.ts +0 -585
  122. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +0 -714
  123. package/src/elements/EFThumbnailStrip.ts +0 -906
  124. package/src/elements/EFTimegroup.browsertest.ts +0 -934
  125. package/src/elements/EFTimegroup.ts +0 -882
  126. package/src/elements/EFVideo.browsertest.ts +0 -1482
  127. package/src/elements/EFVideo.ts +0 -564
  128. package/src/elements/EFWaveform.ts +0 -547
  129. package/src/elements/FetchContext.browsertest.ts +0 -401
  130. package/src/elements/FetchMixin.ts +0 -38
  131. package/src/elements/SampleBuffer.ts +0 -94
  132. package/src/elements/TargetController.browsertest.ts +0 -230
  133. package/src/elements/TargetController.ts +0 -224
  134. package/src/elements/TimegroupController.ts +0 -26
  135. package/src/elements/durationConverter.ts +0 -35
  136. package/src/elements/parseTimeToMs.ts +0 -9
  137. package/src/elements/printTaskStatus.ts +0 -16
  138. package/src/elements/renderTemporalAudio.ts +0 -108
  139. package/src/elements/updateAnimations.browsertest.ts +0 -1884
  140. package/src/elements/updateAnimations.ts +0 -217
  141. package/src/elements/util.ts +0 -24
  142. package/src/gui/ContextMixin.browsertest.ts +0 -860
  143. package/src/gui/ContextMixin.ts +0 -562
  144. package/src/gui/Controllable.browsertest.ts +0 -258
  145. package/src/gui/Controllable.ts +0 -41
  146. package/src/gui/EFConfiguration.ts +0 -40
  147. package/src/gui/EFControls.browsertest.ts +0 -389
  148. package/src/gui/EFControls.ts +0 -195
  149. package/src/gui/EFDial.browsertest.ts +0 -84
  150. package/src/gui/EFDial.ts +0 -172
  151. package/src/gui/EFFilmstrip.browsertest.ts +0 -712
  152. package/src/gui/EFFilmstrip.ts +0 -1349
  153. package/src/gui/EFFitScale.ts +0 -152
  154. package/src/gui/EFFocusOverlay.ts +0 -79
  155. package/src/gui/EFPause.browsertest.ts +0 -202
  156. package/src/gui/EFPause.ts +0 -73
  157. package/src/gui/EFPlay.browsertest.ts +0 -202
  158. package/src/gui/EFPlay.ts +0 -73
  159. package/src/gui/EFPreview.ts +0 -74
  160. package/src/gui/EFResizableBox.browsertest.ts +0 -79
  161. package/src/gui/EFResizableBox.ts +0 -898
  162. package/src/gui/EFScrubber.ts +0 -151
  163. package/src/gui/EFTimeDisplay.browsertest.ts +0 -237
  164. package/src/gui/EFTimeDisplay.ts +0 -55
  165. package/src/gui/EFToggleLoop.ts +0 -35
  166. package/src/gui/EFTogglePlay.ts +0 -70
  167. package/src/gui/EFWorkbench.ts +0 -115
  168. package/src/gui/PlaybackController.ts +0 -527
  169. package/src/gui/TWMixin.css +0 -6
  170. package/src/gui/TWMixin.ts +0 -61
  171. package/src/gui/TargetOrContextMixin.ts +0 -185
  172. package/src/gui/currentTimeContext.ts +0 -5
  173. package/src/gui/durationContext.ts +0 -3
  174. package/src/gui/efContext.ts +0 -6
  175. package/src/gui/fetchContext.ts +0 -5
  176. package/src/gui/focusContext.ts +0 -7
  177. package/src/gui/focusedElementContext.ts +0 -5
  178. package/src/gui/playingContext.ts +0 -5
  179. package/src/otel/BridgeSpanExporter.ts +0 -150
  180. package/src/otel/setupBrowserTracing.ts +0 -73
  181. package/src/otel/tracingHelpers.ts +0 -251
  182. package/src/transcoding/cache/RequestDeduplicator.test.ts +0 -170
  183. package/src/transcoding/cache/RequestDeduplicator.ts +0 -65
  184. package/src/transcoding/cache/URLTokenDeduplicator.test.ts +0 -182
  185. package/src/transcoding/cache/URLTokenDeduplicator.ts +0 -101
  186. package/src/transcoding/types/index.ts +0 -312
  187. package/src/transcoding/utils/MediaUtils.ts +0 -63
  188. package/src/transcoding/utils/UrlGenerator.ts +0 -68
  189. package/src/transcoding/utils/constants.ts +0 -36
  190. package/src/utils/LRUCache.test.ts +0 -274
  191. package/src/utils/LRUCache.ts +0 -696
@@ -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
- }