@editframe/elements 0.21.0-beta.0 → 0.23.7-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.
Files changed (142) hide show
  1. package/dist/EF_FRAMEGEN.js +2 -3
  2. package/dist/attachContextRoot.d.ts +1 -0
  3. package/dist/attachContextRoot.js +9 -0
  4. package/dist/elements/ContextProxiesController.d.ts +1 -2
  5. package/dist/elements/EFAudio.js +2 -2
  6. package/dist/elements/EFCaptions.d.ts +1 -3
  7. package/dist/elements/EFCaptions.js +59 -51
  8. package/dist/elements/EFImage.js +2 -2
  9. package/dist/elements/EFMedia/AssetIdMediaEngine.js +1 -2
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +1 -3
  11. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +1 -1
  12. package/dist/elements/EFMedia/BufferedSeekingInput.js +2 -4
  13. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +4 -7
  14. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -2
  15. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +5 -9
  16. package/dist/elements/EFMedia/shared/BufferUtils.js +1 -3
  17. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +4 -7
  18. package/dist/elements/EFMedia.d.ts +19 -0
  19. package/dist/elements/EFMedia.js +19 -2
  20. package/dist/elements/EFSourceMixin.js +1 -1
  21. package/dist/elements/EFSurface.js +1 -1
  22. package/dist/elements/EFTemporal.browsertest.d.ts +11 -0
  23. package/dist/elements/EFTemporal.d.ts +10 -0
  24. package/dist/elements/EFTemporal.js +82 -5
  25. package/dist/elements/EFThumbnailStrip.js +9 -16
  26. package/dist/elements/EFTimegroup.browsertest.d.ts +3 -3
  27. package/dist/elements/EFTimegroup.d.ts +35 -14
  28. package/dist/elements/EFTimegroup.js +73 -120
  29. package/dist/elements/EFVideo.d.ts +10 -0
  30. package/dist/elements/EFVideo.js +15 -2
  31. package/dist/elements/EFWaveform.js +10 -18
  32. package/dist/elements/SampleBuffer.js +1 -2
  33. package/dist/elements/TargetController.js +2 -2
  34. package/dist/elements/renderTemporalAudio.d.ts +10 -0
  35. package/dist/elements/renderTemporalAudio.js +35 -0
  36. package/dist/elements/updateAnimations.js +7 -10
  37. package/dist/gui/ContextMixin.d.ts +5 -5
  38. package/dist/gui/ContextMixin.js +151 -117
  39. package/dist/gui/Controllable.browsertest.d.ts +0 -0
  40. package/dist/gui/Controllable.d.ts +15 -0
  41. package/dist/gui/Controllable.js +9 -0
  42. package/dist/gui/EFConfiguration.js +1 -1
  43. package/dist/gui/EFControls.browsertest.d.ts +11 -0
  44. package/dist/gui/EFControls.d.ts +18 -4
  45. package/dist/gui/EFControls.js +67 -25
  46. package/dist/gui/EFDial.browsertest.d.ts +0 -0
  47. package/dist/gui/EFDial.d.ts +18 -0
  48. package/dist/gui/EFDial.js +141 -0
  49. package/dist/gui/EFFilmstrip.browsertest.d.ts +11 -0
  50. package/dist/gui/EFFilmstrip.d.ts +12 -2
  51. package/dist/gui/EFFilmstrip.js +140 -34
  52. package/dist/gui/EFFitScale.js +2 -4
  53. package/dist/gui/EFFocusOverlay.js +1 -1
  54. package/dist/gui/EFPause.browsertest.d.ts +0 -0
  55. package/dist/gui/EFPause.d.ts +23 -0
  56. package/dist/gui/EFPause.js +59 -0
  57. package/dist/gui/EFPlay.browsertest.d.ts +0 -0
  58. package/dist/gui/EFPlay.d.ts +23 -0
  59. package/dist/gui/EFPlay.js +59 -0
  60. package/dist/gui/EFPreview.d.ts +4 -0
  61. package/dist/gui/EFPreview.js +15 -6
  62. package/dist/gui/EFResizableBox.browsertest.d.ts +0 -0
  63. package/dist/gui/EFResizableBox.d.ts +34 -0
  64. package/dist/gui/EFResizableBox.js +547 -0
  65. package/dist/gui/EFScrubber.d.ts +9 -3
  66. package/dist/gui/EFScrubber.js +7 -7
  67. package/dist/gui/EFTimeDisplay.d.ts +7 -1
  68. package/dist/gui/EFTimeDisplay.js +5 -5
  69. package/dist/gui/EFToggleLoop.d.ts +9 -3
  70. package/dist/gui/EFToggleLoop.js +6 -4
  71. package/dist/gui/EFTogglePlay.d.ts +12 -4
  72. package/dist/gui/EFTogglePlay.js +24 -19
  73. package/dist/gui/EFWorkbench.js +1 -1
  74. package/dist/gui/PlaybackController.d.ts +67 -0
  75. package/dist/gui/PlaybackController.js +310 -0
  76. package/dist/gui/TWMixin.js +1 -1
  77. package/dist/gui/TargetOrContextMixin.d.ts +10 -0
  78. package/dist/gui/TargetOrContextMixin.js +98 -0
  79. package/dist/gui/efContext.d.ts +2 -2
  80. package/dist/index.d.ts +4 -0
  81. package/dist/index.js +5 -1
  82. package/dist/otel/setupBrowserTracing.d.ts +1 -1
  83. package/dist/otel/setupBrowserTracing.js +6 -4
  84. package/dist/otel/tracingHelpers.js +1 -2
  85. package/dist/style.css +1 -1
  86. package/package.json +5 -5
  87. package/src/elements/ContextProxiesController.ts +10 -10
  88. package/src/elements/EFAudio.ts +1 -0
  89. package/src/elements/EFCaptions.browsertest.ts +128 -58
  90. package/src/elements/EFCaptions.ts +60 -34
  91. package/src/elements/EFImage.browsertest.ts +1 -2
  92. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +3 -0
  93. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +1 -1
  94. package/src/elements/EFMedia.browsertest.ts +8 -15
  95. package/src/elements/EFMedia.ts +38 -7
  96. package/src/elements/EFSurface.browsertest.ts +2 -6
  97. package/src/elements/EFSurface.ts +1 -0
  98. package/src/elements/EFTemporal.browsertest.ts +58 -1
  99. package/src/elements/EFTemporal.ts +140 -4
  100. package/src/elements/EFThumbnailStrip.browsertest.ts +2 -8
  101. package/src/elements/EFThumbnailStrip.ts +1 -0
  102. package/src/elements/EFTimegroup.browsertest.ts +6 -7
  103. package/src/elements/EFTimegroup.ts +163 -244
  104. package/src/elements/EFVideo.browsertest.ts +143 -47
  105. package/src/elements/EFVideo.ts +26 -0
  106. package/src/elements/FetchContext.browsertest.ts +7 -2
  107. package/src/elements/TargetController.browsertest.ts +1 -0
  108. package/src/elements/TargetController.ts +1 -0
  109. package/src/elements/renderTemporalAudio.ts +108 -0
  110. package/src/elements/updateAnimations.browsertest.ts +181 -6
  111. package/src/elements/updateAnimations.ts +6 -6
  112. package/src/gui/ContextMixin.browsertest.ts +274 -27
  113. package/src/gui/ContextMixin.ts +230 -175
  114. package/src/gui/Controllable.browsertest.ts +258 -0
  115. package/src/gui/Controllable.ts +41 -0
  116. package/src/gui/EFControls.browsertest.ts +294 -80
  117. package/src/gui/EFControls.ts +139 -28
  118. package/src/gui/EFDial.browsertest.ts +84 -0
  119. package/src/gui/EFDial.ts +172 -0
  120. package/src/gui/EFFilmstrip.browsertest.ts +712 -0
  121. package/src/gui/EFFilmstrip.ts +213 -23
  122. package/src/gui/EFPause.browsertest.ts +202 -0
  123. package/src/gui/EFPause.ts +73 -0
  124. package/src/gui/EFPlay.browsertest.ts +202 -0
  125. package/src/gui/EFPlay.ts +73 -0
  126. package/src/gui/EFPreview.ts +20 -5
  127. package/src/gui/EFResizableBox.browsertest.ts +79 -0
  128. package/src/gui/EFResizableBox.ts +898 -0
  129. package/src/gui/EFScrubber.ts +7 -5
  130. package/src/gui/EFTimeDisplay.browsertest.ts +19 -19
  131. package/src/gui/EFTimeDisplay.ts +3 -1
  132. package/src/gui/EFToggleLoop.ts +6 -5
  133. package/src/gui/EFTogglePlay.ts +30 -23
  134. package/src/gui/PlaybackController.ts +522 -0
  135. package/src/gui/TWMixin.css +3 -0
  136. package/src/gui/TargetOrContextMixin.ts +185 -0
  137. package/src/gui/efContext.ts +2 -2
  138. package/src/otel/setupBrowserTracing.ts +17 -12
  139. package/test/cache-integration-verification.browsertest.ts +1 -1
  140. package/types.json +1 -1
  141. package/dist/elements/ContextProxiesController.js +0 -49
  142. /package/dist/_virtual/{_@oxc-project_runtime@0.93.0 → _@oxc-project_runtime@0.94.0}/helpers/decorate.js +0 -0
@@ -1,10 +1,16 @@
1
1
  import { Task } from '@lit/task';
2
2
  import { LitElement, ReactiveController } from 'lit';
3
+ import { PlaybackController } from '../gui/PlaybackController.js';
3
4
  import { EFTimegroup } from './EFTimegroup.js';
4
5
  export declare const timegroupContext: {
5
6
  __context__: EFTimegroup;
6
7
  };
7
8
  export declare class TemporalMixinInterface {
9
+ playbackController?: PlaybackController;
10
+ playing: boolean;
11
+ loop: boolean;
12
+ play(): void;
13
+ pause(): void;
8
14
  get hasOwnDuration(): boolean;
9
15
  /**
10
16
  * Whether the element has a duration set as an attribute.
@@ -137,6 +143,7 @@ export declare class TemporalMixinInterface {
137
143
  * For other temporal elements: their ownCurrentTimeMs
138
144
  */
139
145
  get currentTimeMs(): number;
146
+ set currentTimeMs(value: number);
140
147
  /**
141
148
  * The current time of the element in milliseconds, adjusted for trimming.
142
149
  *
@@ -185,6 +192,9 @@ export declare class TemporalMixinInterface {
185
192
  */
186
193
  rootTimegroup?: EFTimegroup;
187
194
  frameTask: Task<readonly unknown[], unknown>;
195
+ didBecomeRoot(): void;
196
+ didBecomeChild(): void;
197
+ updateComplete: Promise<boolean>;
188
198
  }
189
199
  export declare const isEFTemporal: (obj: any) => obj is TemporalMixinInterface;
190
200
  export declare const deepGetTemporalElements: (element: Element, temporals?: Array<TemporalMixinInterface & HTMLElement>) => (TemporalMixinInterface & HTMLElement)[];
@@ -1,6 +1,7 @@
1
1
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
2
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js";
2
+ import { PlaybackController } from "../gui/PlaybackController.js";
3
3
  import { durationConverter } from "./durationConverter.js";
4
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
4
5
  import { consume, createContext } from "@lit/context";
5
6
  import { Task } from "@lit/task";
6
7
  import { property, state } from "lit/decorators.js";
@@ -79,18 +80,63 @@ const EFTemporal = (superClass) => {
79
80
  }
80
81
  #parentTimegroup;
81
82
  set parentTimegroup(value) {
83
+ const oldParent = this.#parentTimegroup;
82
84
  this.#parentTimegroup = value;
83
85
  this.ownCurrentTimeController?.remove();
84
86
  this.rootTimegroup = this.getRootTimegroup();
85
87
  if (this.rootTimegroup) this.ownCurrentTimeController = new OwnCurrentTimeController(this.rootTimegroup, this);
88
+ if (oldParent !== value) if (!value) this.didBecomeRoot();
89
+ else this.didBecomeChild();
86
90
  }
87
91
  disconnectedCallback() {
88
92
  super.disconnectedCallback();
89
93
  this.ownCurrentTimeController?.remove();
94
+ if (this.playbackController) {
95
+ this.playbackController.remove();
96
+ this.playbackController = void 0;
97
+ }
98
+ }
99
+ connectedCallback() {
100
+ super.connectedCallback();
101
+ if (!this.parentTimegroup) this.didBecomeRoot();
90
102
  }
91
103
  get parentTimegroup() {
92
104
  return this.#parentTimegroup;
93
105
  }
106
+ get playing() {
107
+ if (!this.playbackController) return false;
108
+ return this.playbackController.playing;
109
+ }
110
+ set playing(value) {
111
+ if (!this.playbackController) {
112
+ console.warn("Cannot set playing on non-root temporal element", this);
113
+ return;
114
+ }
115
+ this.playbackController.setPlaying(value);
116
+ }
117
+ play() {
118
+ if (!this.playbackController) {
119
+ console.warn("play() called on non-root temporal element", this);
120
+ return;
121
+ }
122
+ this.playbackController.play();
123
+ }
124
+ pause() {
125
+ if (!this.playbackController) {
126
+ console.warn("pause() called on non-root temporal element", this);
127
+ return;
128
+ }
129
+ this.playbackController.pause();
130
+ }
131
+ get loop() {
132
+ return this.playbackController?.loop ?? this.#loop;
133
+ }
134
+ set loop(value) {
135
+ const oldValue = this.#loop;
136
+ this.#loop = value;
137
+ if (this.playbackController) this.playbackController.setLoop(value);
138
+ this.requestUpdate("loop", oldValue);
139
+ }
94
140
  set duration(value) {
95
141
  if (value !== void 0) this.setAttribute("duration", value);
96
142
  else this.removeAttribute("duration");
@@ -173,6 +219,7 @@ const EFTemporal = (superClass) => {
173
219
  if (!this.parentTemporal) return 0;
174
220
  return this.startTimeMs - this.parentTemporal.startTimeMs;
175
221
  }
222
+ #loop = false;
176
223
  get startTimeMs() {
177
224
  const cachedStartTime = startTimeMsCache.get(this);
178
225
  if (cachedStartTime !== void 0) return cachedStartTime;
@@ -212,22 +259,52 @@ const EFTemporal = (superClass) => {
212
259
  get endTimeMs() {
213
260
  return this.startTimeMs + this.durationMs;
214
261
  }
262
+ #currentTimeMs = 0;
215
263
  get ownCurrentTimeMs() {
216
- if (this.rootTimegroup) return Math.min(Math.max(0, this.rootTimegroup.currentTimeMs - this.startTimeMs), this.durationMs);
217
- return 0;
264
+ if (this.playbackController) return Math.min(Math.max(0, this.playbackController.currentTimeMs), this.durationMs);
265
+ if (this.rootTimegroup && this.rootTimegroup !== this) return Math.min(Math.max(0, this.rootTimegroup.currentTimeMs - this.startTimeMs), this.durationMs);
266
+ return Math.min(Math.max(0, this.#currentTimeMs), this.durationMs);
218
267
  }
219
268
  get currentTimeMs() {
220
269
  return this.ownCurrentTimeMs;
221
270
  }
271
+ set currentTimeMs(value) {
272
+ if (this.playbackController) {
273
+ this.playbackController.currentTime = value / 1e3;
274
+ return;
275
+ }
276
+ if (this.rootTimegroup && this.rootTimegroup !== this) this.rootTimegroup.currentTimeMs = value;
277
+ else {
278
+ this.#currentTimeMs = value;
279
+ this.requestUpdate("currentTimeMs");
280
+ }
281
+ }
222
282
  get currentSourceTimeMs() {
223
283
  const leadingTrimMs = this.sourceInMs || this.trimStartMs || 0;
224
284
  return this.ownCurrentTimeMs + leadingTrimMs;
225
285
  }
286
+ didBecomeRoot() {
287
+ if (!this.playbackController) {
288
+ this.playbackController = new PlaybackController(this);
289
+ if (this.#loop) this.playbackController.setLoop(this.#loop);
290
+ }
291
+ }
292
+ didBecomeChild() {
293
+ if (this.playbackController) {
294
+ this.playbackController.remove();
295
+ this.playbackController = void 0;
296
+ }
297
+ }
226
298
  }
227
299
  __decorate([consume({
228
300
  context: timegroupContext,
229
301
  subscribe: true
230
- }), property({ attribute: false })], TemporalMixinClass.prototype, "parentTimegroup", null);
302
+ })], TemporalMixinClass.prototype, "parentTimegroup", null);
303
+ __decorate([property({
304
+ type: Boolean,
305
+ reflect: true,
306
+ attribute: "loop"
307
+ })], TemporalMixinClass.prototype, "loop", null);
231
308
  __decorate([property({
232
309
  type: String,
233
310
  attribute: "offset",
@@ -267,4 +344,4 @@ const EFTemporal = (superClass) => {
267
344
  Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, { value: true });
268
345
  return TemporalMixinClass;
269
346
  };
270
- export { EFTemporal, deepGetElementsWithFrameTasks, deepGetTemporalElements, flushStartTimeMsCache, resetTemporalCache, shallowGetTemporalElements, timegroupContext };
347
+ export { EFTemporal, deepGetElementsWithFrameTasks, deepGetTemporalElements, flushStartTimeMsCache, isEFTemporal, resetTemporalCache, shallowGetTemporalElements, timegroupContext };
@@ -1,4 +1,4 @@
1
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js";
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
2
2
  import { OrderedLRUCache } from "../utils/LRUCache.js";
3
3
  import { TargetController } from "./TargetController.js";
4
4
  import { Task } from "@lit/task";
@@ -8,9 +8,7 @@ import { createRef, ref } from "lit/directives/ref.js";
8
8
  var thumbnailImageCache = new OrderedLRUCache(200, (a, b) => {
9
9
  const partsA = a.split(":");
10
10
  const partsB = b.split(":");
11
- const timeA = Number.parseFloat(partsA[partsA.length - 1] || "0");
12
- const timeB = Number.parseFloat(partsB[partsB.length - 1] || "0");
13
- return timeA - timeB;
11
+ return Number.parseFloat(partsA[partsA.length - 1] || "0") - Number.parseFloat(partsB[partsB.length - 1] || "0");
14
12
  });
15
13
  globalThis.debugThumbnailCache = thumbnailImageCache;
16
14
  function quantizeTimestamp(timeMs) {
@@ -18,8 +16,7 @@ function quantizeTimestamp(timeMs) {
18
16
  return Math.round(timeMs / frameIntervalMs) * frameIntervalMs;
19
17
  }
20
18
  function getThumbnailCacheKey(videoSrc, timeMs) {
21
- const quantizedTimeMs = quantizeTimestamp(timeMs);
22
- return `${videoSrc}:${quantizedTimeMs}`;
19
+ return `${videoSrc}:${quantizeTimestamp(timeMs)}`;
23
20
  }
24
21
  var THUMBNAIL_GAP = 1;
25
22
  var STRIP_BORDER_PADDING = 4;
@@ -43,13 +40,12 @@ function calculateThumbnailLayout(stripWidth, thumbnailWidth, startTimeMs, endTi
43
40
  if (!segmentMap.has(segmentId)) segmentMap.set(segmentId, []);
44
41
  segmentMap.get(segmentId).push({ timeMs });
45
42
  }
46
- const segments = Array.from(segmentMap.entries()).sort(([a], [b]) => a - b).map(([segmentId, thumbnails]) => ({
47
- segmentId,
48
- thumbnails
49
- }));
50
43
  return {
51
44
  count,
52
- segments
45
+ segments: Array.from(segmentMap.entries()).sort(([a], [b]) => a - b).map(([segmentId, thumbnails]) => ({
46
+ segmentId,
47
+ thumbnails
48
+ }))
53
49
  };
54
50
  }
55
51
  var EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
@@ -248,8 +244,7 @@ var EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
248
244
  return this.generateLayoutFromTimeRange(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, mediaEngine);
249
245
  }
250
246
  generateLayoutFromTimeRange(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, mediaEngine) {
251
- const scrubSegmentDurationMs = mediaEngine && typeof mediaEngine.getScrubVideoRendition === "function" ? mediaEngine.getScrubVideoRendition()?.segmentDurationMs : void 0;
252
- return calculateThumbnailLayout(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, scrubSegmentDurationMs);
247
+ return calculateThumbnailLayout(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, mediaEngine && typeof mediaEngine.getScrubVideoRendition === "function" ? mediaEngine.getScrubVideoRendition()?.segmentDurationMs : void 0);
253
248
  }
254
249
  async renderThumbnails(layout, targetElement, thumbnailWidth) {
255
250
  if (!layout || !targetElement || layout.count === 0) return [];
@@ -274,9 +269,7 @@ var EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
274
269
  const closestParts = closest.key.split(":");
275
270
  const currentTime = Number.parseFloat(currentParts[currentParts.length - 1] || "0");
276
271
  const closestTime = Number.parseFloat(closestParts[closestParts.length - 1] || "0");
277
- const currentDiff = Math.abs(currentTime - thumbnail.timeMs);
278
- const closestDiff = Math.abs(closestTime - thumbnail.timeMs);
279
- return currentDiff < closestDiff ? current : closest;
272
+ return Math.abs(currentTime - thumbnail.timeMs) < Math.abs(closestTime - thumbnail.timeMs) ? current : closest;
280
273
  });
281
274
  imageData = nearestHit.value;
282
275
  status = "near-hit";
@@ -12,17 +12,17 @@ declare class TimegroupTestMedia extends EFMedia {
12
12
  declare const TestFrameTaskA_base: (new (...args: any[]) => import('./EFTemporal.js').TemporalMixinInterface) & typeof LitElement;
13
13
  declare class TestFrameTaskA extends TestFrameTaskA_base {
14
14
  frameTaskCount: number;
15
- frameTask: Task<never[], void>;
15
+ frameTask: Task<readonly [], void>;
16
16
  }
17
17
  declare const TestFrameTaskB_base: (new (...args: any[]) => import('./EFTemporal.js').TemporalMixinInterface) & typeof LitElement;
18
18
  declare class TestFrameTaskB extends TestFrameTaskB_base {
19
19
  frameTaskCount: number;
20
- frameTask: Task<never[], void>;
20
+ frameTask: Task<readonly [], void>;
21
21
  }
22
22
  declare const TestFrameTaskC_base: (new (...args: any[]) => import('./EFTemporal.js').TemporalMixinInterface) & typeof LitElement;
23
23
  declare class TestFrameTaskC extends TestFrameTaskC_base {
24
24
  frameTaskCount: number;
25
- frameTask: Task<never[], void>;
25
+ frameTask: Task<readonly [], void>;
26
26
  }
27
27
  declare const TestTemporal_base: (new (...args: any[]) => import('./EFTemporal.js').TemporalMixinInterface) & typeof LitElement;
28
28
  declare class TestTemporal extends TestTemporal_base {
@@ -1,35 +1,44 @@
1
1
  import { Task } from '@lit/task';
2
2
  import { LitElement, PropertyValues } from 'lit';
3
+ import { EFMedia } from './EFMedia.js';
4
+ declare global {
5
+ var EF_DEV_WORKBENCH: boolean | undefined;
6
+ }
3
7
  export declare const flushSequenceDurationCache: () => void;
4
8
  export declare const shallowGetTimegroups: (element: Element, groups?: EFTimegroup[]) => EFTimegroup[];
5
9
  declare const EFTimegroup_base: (new (...args: any[]) => import('./EFTemporal.js').TemporalMixinInterface) & typeof LitElement;
6
10
  export declare class EFTimegroup extends EFTimegroup_base {
7
11
  #private;
12
+ static get observedAttributes(): string[];
8
13
  static styles: import('lit').CSSResult;
9
14
  _timeGroupContext: this;
10
- set mode(value: "fit" | "fixed" | "sequence" | "contain");
11
- get mode(): "fit" | "fixed" | "sequence" | "contain";
12
- private _mode;
13
- set overlapMs(value: number);
14
- get overlapMs(): number;
15
- private _overlapMs;
15
+ efContext: this;
16
+ mode: "fit" | "fixed" | "sequence" | "contain";
17
+ overlapMs: number;
18
+ attributeChangedCallback(name: string, old: string | null, value: string | null): void;
16
19
  fit: "none" | "contain" | "cover";
17
- /**
18
- * Throttles frameTask execution to ensure only one runs at a time while preserving the last request
19
- */
20
20
  private runThrottledFrameTask;
21
21
  set currentTime(time: number);
22
22
  get currentTime(): number;
23
23
  set currentTimeMs(ms: number);
24
24
  get currentTimeMs(): number;
25
+ /**
26
+ * Seek to a specific time and wait for all frames to be ready.
27
+ * This is the recommended way to seek in tests and programmatic control.
28
+ *
29
+ * @param timeMs - Time in milliseconds to seek to
30
+ * @returns Promise that resolves when the seek is complete and all visible children are ready
31
+ */
32
+ seek(timeMs: number): Promise<void>;
25
33
  /**
26
34
  * Determines if this is a root timegroup (no parent timegroups)
27
35
  */
28
36
  get isRootTimegroup(): boolean;
37
+ saveTimeToLocalStorage(time: number): void;
29
38
  render(): import('lit-html').TemplateResult<1>;
30
- maybeLoadTimeFromLocalStorage(): number | undefined;
39
+ loadTimeFromLocalStorage(): number | undefined;
31
40
  connectedCallback(): void;
32
- protected updated(_changedProperties: PropertyValues): void;
41
+ protected updated(changedProperties: PropertyValues): void;
33
42
  disconnectedCallback(): void;
34
43
  get storageKey(): string;
35
44
  get intrinsicDurationMs(): number | undefined;
@@ -45,15 +54,27 @@ export declare class EFTimegroup extends EFTimegroup_base {
45
54
  /**
46
55
  * Returns true if the timegroup should be wrapped with a workbench.
47
56
  *
48
- * A timegroup should be wrapped with a workbench if it is the root-most timegroup
49
- * and EF_INTERACTIVE is true.
57
+ * A timegroup should be wrapped with a workbench if:
58
+ * - It's being rendered (EF_RENDERING), OR
59
+ * - It's in interactive mode (EF_INTERACTIVE) with the dev workbench flag set
50
60
  *
51
- * If the timegroup is already wrappedin a context provider like ef-preview,
61
+ * If the timegroup is already wrapped in a context provider like ef-preview,
52
62
  * it should NOT be wrapped in a workbench.
53
63
  */
54
64
  shouldWrapWithWorkbench(): boolean;
55
65
  wrapWithWorkbench(): void;
56
66
  get efElements(): Element[];
67
+ /**
68
+ * Returns media elements for playback audio rendering
69
+ * For standalone media, returns [this]; for timegroups, returns all descendants
70
+ * Used by PlaybackController for audio-driven playback
71
+ */
72
+ getMediaElements(): EFMedia[];
73
+ /**
74
+ * Render audio buffer for playback
75
+ * Called by PlaybackController during live playback
76
+ * Delegates to shared renderTemporalAudio utility for consistent behavior
77
+ */
57
78
  renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer>;
58
79
  /**
59
80
  * TEMPORARY TEST METHOD: Renders audio and immediately plays it back
@@ -1,9 +1,14 @@
1
1
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
2
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js";
2
+ import { EF_RENDERING } from "../EF_RENDERING.js";
3
+ import { parseTimeToMs } from "./parseTimeToMs.js";
4
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
5
+ import { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, resetTemporalCache, shallowGetTemporalElements, timegroupContext } from "./EFTemporal.js";
6
+ import { efContext } from "../gui/efContext.js";
3
7
  import { isContextMixin } from "../gui/ContextMixin.js";
8
+ import { TWMixin } from "../gui/TWMixin2.js";
4
9
  import { isTracingEnabled, withSpan } from "../otel/tracingHelpers.js";
5
- import { durationConverter } from "./durationConverter.js";
6
- import { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, resetTemporalCache, shallowGetTemporalElements, timegroupContext } from "./EFTemporal.js";
10
+ import { renderTemporalAudio } from "./renderTemporalAudio.js";
11
+ import { EFTargetable } from "./TargetController.js";
7
12
  import { deepGetMediaElements } from "./EFMedia.js";
8
13
  import { TimegroupController } from "./TimegroupController.js";
9
14
  import { evaluateTemporalStateForAnimation, updateAnimations } from "./updateAnimations.js";
@@ -23,15 +28,16 @@ const shallowGetTimegroups = (element, groups = []) => {
23
28
  else shallowGetTimegroups(child, groups);
24
29
  return groups;
25
30
  };
26
- var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
31
+ var EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(LitElement))) {
27
32
  static {
28
33
  _EFTimegroup = this;
29
34
  }
30
35
  constructor(..._args) {
31
36
  super(..._args);
32
37
  this._timeGroupContext = this;
33
- this._mode = "contain";
34
- this._overlapMs = 0;
38
+ this.efContext = this;
39
+ this.mode = "contain";
40
+ this.overlapMs = 0;
35
41
  this.fit = "none";
36
42
  this.mediaDurationsPromise = void 0;
37
43
  this.frameTask = new Task(this, {
@@ -53,6 +59,10 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
53
59
  args: () => [this.#pendingSeekTime ?? this.#currentTime],
54
60
  onComplete: () => {},
55
61
  task: async ([targetTime]) => {
62
+ if (this.playbackController) {
63
+ await this.playbackController.seekTask.taskComplete;
64
+ return this.currentTime;
65
+ }
56
66
  if (!this.isRootTimegroup) return;
57
67
  return withSpan("timegroup.seekTask", {
58
68
  timegroupId: this.id || "unknown",
@@ -65,71 +75,60 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
65
75
  this.#currentTime = newTime;
66
76
  this.requestUpdate("currentTime");
67
77
  await this.runThrottledFrameTask();
68
- this.#saveTimeToLocalStorage(this.#currentTime);
78
+ this.saveTimeToLocalStorage(this.#currentTime);
69
79
  this.#seekInProgress = false;
70
80
  return newTime;
71
81
  });
72
82
  }
73
83
  });
74
84
  }
85
+ static get observedAttributes() {
86
+ return [
87
+ ...super.observedAttributes || [],
88
+ "mode",
89
+ "overlap",
90
+ "currenttime",
91
+ "fit"
92
+ ];
93
+ }
75
94
  static {
76
95
  this.styles = css`
77
96
  :host {
78
97
  display: block;
98
+ position: relative;
99
+ overflow: hidden;
100
+ }
101
+
102
+ ::slotted(ef-timegroup) {
103
+ position: absolute;
79
104
  width: 100%;
80
105
  height: 100%;
81
- position: absolute;
82
106
  top: 0;
83
107
  left: 0;
108
+ overflow: initial;
84
109
  }
85
110
  `;
86
111
  }
87
- #currentTime = void 0;
88
- set mode(value) {
89
- sequenceDurationCache.delete(this);
90
- this._mode = value;
91
- }
92
- get mode() {
93
- return this._mode;
94
- }
95
- set overlapMs(value) {
96
- sequenceDurationCache.delete(this);
97
- this._overlapMs = value;
98
- }
99
- get overlapMs() {
100
- return this._overlapMs;
112
+ attributeChangedCallback(name, old, value) {
113
+ if (name === "mode" && value) this.mode = value;
114
+ if (name === "overlap" && value) this.overlapMs = parseTimeToMs(value);
115
+ super.attributeChangedCallback(name, old, value);
101
116
  }
102
117
  #resizeObserver;
118
+ #currentTime = void 0;
103
119
  #seekInProgress = false;
104
120
  #pendingSeekTime;
105
121
  #processingPendingSeek = false;
106
- #frameTaskInProgress = false;
107
- #pendingFrameTaskRun = false;
108
- #processingPendingFrameTask = false;
109
122
  async runThrottledFrameTask() {
110
- if (this.#frameTaskInProgress) {
111
- this.#pendingFrameTaskRun = true;
112
- while (this.#frameTaskInProgress) await this.frameTask.taskComplete;
113
- return;
114
- }
115
- this.#frameTaskInProgress = true;
116
- try {
117
- await this.frameTask.run();
118
- } finally {
119
- this.#frameTaskInProgress = false;
120
- if (this.#pendingFrameTaskRun && !this.#processingPendingFrameTask) {
121
- this.#pendingFrameTaskRun = false;
122
- this.#processingPendingFrameTask = true;
123
- try {
124
- await this.runThrottledFrameTask();
125
- } finally {
126
- this.#processingPendingFrameTask = false;
127
- }
128
- } else this.#pendingFrameTaskRun = false;
129
- }
123
+ if (this.playbackController) return this.playbackController.runThrottledFrameTask();
124
+ await this.frameTask.run();
130
125
  }
131
126
  set currentTime(time) {
132
- time = Math.max(0, time);
127
+ if (this.playbackController) {
128
+ this.playbackController.currentTime = time;
129
+ return;
130
+ }
131
+ time = Math.max(0, Math.min(this.durationMs / 1e3, time));
133
132
  if (!this.isRootTimegroup) return;
134
133
  if (Number.isNaN(time)) return;
135
134
  if (time === this.#currentTime && !this.#processingPendingSeek) return;
@@ -155,6 +154,7 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
155
154
  });
156
155
  }
157
156
  get currentTime() {
157
+ if (this.playbackController) return this.playbackController.currentTime;
158
158
  return this.#currentTime ?? 0;
159
159
  }
160
160
  set currentTimeMs(ms) {
@@ -163,10 +163,23 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
163
163
  get currentTimeMs() {
164
164
  return this.currentTime * 1e3;
165
165
  }
166
+ async seek(timeMs) {
167
+ this.currentTimeMs = timeMs;
168
+ await this.seekTask.taskComplete;
169
+ if (this.playbackController) this.saveTimeToLocalStorage(this.currentTime);
170
+ await this.frameTask.taskComplete;
171
+ const visibleElements = deepGetElementsWithFrameTasks(this).filter((element) => {
172
+ return evaluateTemporalStateForAnimation(element).isVisible;
173
+ });
174
+ await Promise.all(visibleElements.map(async (element) => {
175
+ if ("waitForFrameReady" in element && typeof element.waitForFrameReady === "function") await element.waitForFrameReady();
176
+ else await element.updateComplete;
177
+ }));
178
+ }
166
179
  get isRootTimegroup() {
167
180
  return !this.parentTimegroup;
168
181
  }
169
- #saveTimeToLocalStorage(time) {
182
+ saveTimeToLocalStorage(time) {
170
183
  try {
171
184
  if (this.id && this.isConnected && !Number.isNaN(time)) localStorage.setItem(this.storageKey, time.toString());
172
185
  } catch (error) {
@@ -182,7 +195,7 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
182
195
  flushStartTimeMsCache();
183
196
  this.requestUpdate();
184
197
  };
185
- maybeLoadTimeFromLocalStorage() {
198
+ loadTimeFromLocalStorage() {
186
199
  if (this.id) try {
187
200
  const storedValue = localStorage.getItem(this.storageKey);
188
201
  if (storedValue === null) return;
@@ -193,9 +206,9 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
193
206
  }
194
207
  connectedCallback() {
195
208
  super.connectedCallback();
196
- this.waitForMediaDurations().then(() => {
209
+ if (!this.playbackController) this.waitForMediaDurations().then(() => {
197
210
  if (this.id) {
198
- const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
211
+ const maybeLoadedTime = this.loadTimeFromLocalStorage();
199
212
  if (maybeLoadedTime !== void 0) this.currentTime = maybeLoadedTime;
200
213
  }
201
214
  if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) this.seekTask.run();
@@ -204,7 +217,9 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
204
217
  if (this.shouldWrapWithWorkbench()) this.wrapWithWorkbench();
205
218
  }
206
219
  #previousDurationMs = 0;
207
- updated(_changedProperties) {
220
+ updated(changedProperties) {
221
+ super.updated(changedProperties);
222
+ if (changedProperties.has("mode") || changedProperties.has("overlapMs")) sequenceDurationCache.delete(this);
208
223
  if (this.#previousDurationMs !== this.durationMs) {
209
224
  this.#previousDurationMs = this.durationMs;
210
225
  this.runThrottledFrameTask();
@@ -338,6 +353,8 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
338
353
  return null;
339
354
  }
340
355
  shouldWrapWithWorkbench() {
356
+ if (EF_RENDERING?.() === true) return this.closest("ef-timegroup") === this && this.closest("ef-preview") === null && this.closest("ef-workbench") === null && this.closest("test-context") === null;
357
+ if (!globalThis.EF_DEV_WORKBENCH) return false;
341
358
  return EF_INTERACTIVE && this.closest("ef-timegroup") === this && this.closest("ef-preview") === null && this.closest("ef-workbench") === null && this.closest("test-context") === null;
342
359
  }
343
360
  wrapWithWorkbench() {
@@ -354,67 +371,11 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
354
371
  get efElements() {
355
372
  return Array.from(this.querySelectorAll("ef-audio, ef-video, ef-image, ef-captions, ef-waveform"));
356
373
  }
357
- async #addAudioToContext(audioContext, fromMs, toMs) {
358
- await this.waitForMediaDurations();
359
- const abortController = new AbortController();
360
- await Promise.all(deepGetMediaElements(this).map(async (mediaElement) => {
361
- if (mediaElement.mute) return;
362
- const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
363
- const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
364
- if (!(mediaStartsBeforeEnd && mediaEndsAfterStart)) return;
365
- const mediaLocalFromMs = Math.max(0, fromMs - mediaElement.startTimeMs);
366
- const mediaLocalToMs = Math.min(mediaElement.endTimeMs - mediaElement.startTimeMs, toMs - mediaElement.startTimeMs);
367
- if (mediaLocalFromMs >= mediaLocalToMs) return;
368
- const sourceInMs = mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
369
- const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
370
- const mediaSourceToMs = mediaLocalToMs + sourceInMs;
371
- let audio;
372
- try {
373
- audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, abortController.signal);
374
- } catch (error) {
375
- if (error instanceof Error && error.message.includes("No audio track available")) return;
376
- throw error;
377
- }
378
- if (!audio) return;
379
- const bufferSource = audioContext.createBufferSource();
380
- bufferSource.buffer = await audioContext.decodeAudioData(await audio.blob.arrayBuffer());
381
- bufferSource.connect(audioContext.destination);
382
- const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
383
- const requestedSourceFromMs = mediaSourceFromMs;
384
- const actualSourceStartMs = audio.startMs;
385
- const offsetInBufferMs = requestedSourceFromMs - actualSourceStartMs;
386
- const safeOffsetMs = Math.max(0, offsetInBufferMs);
387
- const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;
388
- const availableAudioMs = audio.endMs - audio.startMs;
389
- const actualDurationMs = Math.min(requestedDurationMs, availableAudioMs - safeOffsetMs);
390
- if (actualDurationMs <= 0) return;
391
- bufferSource.start(ctxStartMs / 1e3, safeOffsetMs / 1e3, actualDurationMs / 1e3);
392
- }));
374
+ getMediaElements() {
375
+ return deepGetMediaElements(this);
393
376
  }
394
377
  async renderAudio(fromMs, toMs) {
395
- return withSpan("timegroup.renderAudio", {
396
- timegroupId: this.id || "unknown",
397
- fromMs,
398
- toMs,
399
- durationMs: toMs - fromMs
400
- }, void 0, async (span) => {
401
- const aacFrames = 48e3 * ((toMs - fromMs) / 1e3) / 1024;
402
- const alignedFrames = Math.round(aacFrames);
403
- const contextSize = alignedFrames * 1024;
404
- if (isTracingEnabled()) {
405
- span.setAttribute("contextSize", contextSize);
406
- span.setAttribute("alignedFrames", alignedFrames);
407
- }
408
- if (contextSize <= 0) throw new Error(`Duration must be greater than 0 when rendering audio. ${contextSize}ms`);
409
- let audioContext;
410
- try {
411
- audioContext = new OfflineAudioContext(2, contextSize, 48e3);
412
- } catch (error) {
413
- throw new Error(`[EFTimegroup.renderAudio] Failed to create OfflineAudioContext(2, ${contextSize}, 48000) for renderAudio(${fromMs}, ${toMs}) with contextSize=${contextSize}: ${error instanceof Error ? error.message : String(error)}. This typically happens when audio parameters are invalid (e.g., contextSize <= 0).`);
414
- }
415
- await this.#addAudioToContext(audioContext, fromMs, toMs);
416
- return await audioContext.startRendering();
417
- });
378
+ return renderTemporalAudio(this, fromMs, toMs);
418
379
  }
419
380
  async testPlayAudio(fromMs, toMs) {
420
381
  const renderedBuffer = await this.renderAudio(fromMs, toMs);
@@ -441,21 +402,13 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
441
402
  }
442
403
  }
443
404
  await Promise.all(loaderTasks);
444
- efElements.map((el) => {
405
+ efElements.forEach((el) => {
445
406
  if ("productionSrc" in el && el.productionSrc instanceof Function) el.setAttribute("src", el.productionSrc());
446
407
  });
447
408
  }
448
409
  };
449
410
  __decorate([provide({ context: timegroupContext })], EFTimegroup.prototype, "_timeGroupContext", void 0);
450
- __decorate([property({
451
- type: String,
452
- attribute: "mode"
453
- })], EFTimegroup.prototype, "mode", null);
454
- __decorate([property({
455
- type: Number,
456
- converter: durationConverter,
457
- attribute: "overlap"
458
- })], EFTimegroup.prototype, "overlapMs", null);
411
+ __decorate([provide({ context: efContext })], EFTimegroup.prototype, "efContext", void 0);
459
412
  __decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
460
413
  __decorate([property({
461
414
  type: Number,