@editframe/elements 0.21.0-beta.0 → 0.23.6-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 +72 -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 +162 -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,59 @@ 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;
84
108
  }
85
109
  `;
86
110
  }
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;
111
+ attributeChangedCallback(name, old, value) {
112
+ if (name === "mode" && value) this.mode = value;
113
+ if (name === "overlap" && value) this.overlapMs = parseTimeToMs(value);
114
+ super.attributeChangedCallback(name, old, value);
101
115
  }
102
116
  #resizeObserver;
117
+ #currentTime = void 0;
103
118
  #seekInProgress = false;
104
119
  #pendingSeekTime;
105
120
  #processingPendingSeek = false;
106
- #frameTaskInProgress = false;
107
- #pendingFrameTaskRun = false;
108
- #processingPendingFrameTask = false;
109
121
  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
- }
122
+ if (this.playbackController) return this.playbackController.runThrottledFrameTask();
123
+ await this.frameTask.run();
130
124
  }
131
125
  set currentTime(time) {
132
- time = Math.max(0, time);
126
+ if (this.playbackController) {
127
+ this.playbackController.currentTime = time;
128
+ return;
129
+ }
130
+ time = Math.max(0, Math.min(this.durationMs / 1e3, time));
133
131
  if (!this.isRootTimegroup) return;
134
132
  if (Number.isNaN(time)) return;
135
133
  if (time === this.#currentTime && !this.#processingPendingSeek) return;
@@ -155,6 +153,7 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
155
153
  });
156
154
  }
157
155
  get currentTime() {
156
+ if (this.playbackController) return this.playbackController.currentTime;
158
157
  return this.#currentTime ?? 0;
159
158
  }
160
159
  set currentTimeMs(ms) {
@@ -163,10 +162,23 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
163
162
  get currentTimeMs() {
164
163
  return this.currentTime * 1e3;
165
164
  }
165
+ async seek(timeMs) {
166
+ this.currentTimeMs = timeMs;
167
+ await this.seekTask.taskComplete;
168
+ if (this.playbackController) this.saveTimeToLocalStorage(this.currentTime);
169
+ await this.frameTask.taskComplete;
170
+ const visibleElements = deepGetElementsWithFrameTasks(this).filter((element) => {
171
+ return evaluateTemporalStateForAnimation(element).isVisible;
172
+ });
173
+ await Promise.all(visibleElements.map(async (element) => {
174
+ if ("waitForFrameReady" in element && typeof element.waitForFrameReady === "function") await element.waitForFrameReady();
175
+ else await element.updateComplete;
176
+ }));
177
+ }
166
178
  get isRootTimegroup() {
167
179
  return !this.parentTimegroup;
168
180
  }
169
- #saveTimeToLocalStorage(time) {
181
+ saveTimeToLocalStorage(time) {
170
182
  try {
171
183
  if (this.id && this.isConnected && !Number.isNaN(time)) localStorage.setItem(this.storageKey, time.toString());
172
184
  } catch (error) {
@@ -182,7 +194,7 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
182
194
  flushStartTimeMsCache();
183
195
  this.requestUpdate();
184
196
  };
185
- maybeLoadTimeFromLocalStorage() {
197
+ loadTimeFromLocalStorage() {
186
198
  if (this.id) try {
187
199
  const storedValue = localStorage.getItem(this.storageKey);
188
200
  if (storedValue === null) return;
@@ -193,9 +205,9 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
193
205
  }
194
206
  connectedCallback() {
195
207
  super.connectedCallback();
196
- this.waitForMediaDurations().then(() => {
208
+ if (!this.playbackController) this.waitForMediaDurations().then(() => {
197
209
  if (this.id) {
198
- const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
210
+ const maybeLoadedTime = this.loadTimeFromLocalStorage();
199
211
  if (maybeLoadedTime !== void 0) this.currentTime = maybeLoadedTime;
200
212
  }
201
213
  if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) this.seekTask.run();
@@ -204,7 +216,9 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
204
216
  if (this.shouldWrapWithWorkbench()) this.wrapWithWorkbench();
205
217
  }
206
218
  #previousDurationMs = 0;
207
- updated(_changedProperties) {
219
+ updated(changedProperties) {
220
+ super.updated(changedProperties);
221
+ if (changedProperties.has("mode") || changedProperties.has("overlapMs")) sequenceDurationCache.delete(this);
208
222
  if (this.#previousDurationMs !== this.durationMs) {
209
223
  this.#previousDurationMs = this.durationMs;
210
224
  this.runThrottledFrameTask();
@@ -338,6 +352,8 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
338
352
  return null;
339
353
  }
340
354
  shouldWrapWithWorkbench() {
355
+ 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;
356
+ if (!globalThis.EF_DEV_WORKBENCH) return false;
341
357
  return EF_INTERACTIVE && this.closest("ef-timegroup") === this && this.closest("ef-preview") === null && this.closest("ef-workbench") === null && this.closest("test-context") === null;
342
358
  }
343
359
  wrapWithWorkbench() {
@@ -354,67 +370,11 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
354
370
  get efElements() {
355
371
  return Array.from(this.querySelectorAll("ef-audio, ef-video, ef-image, ef-captions, ef-waveform"));
356
372
  }
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
- }));
373
+ getMediaElements() {
374
+ return deepGetMediaElements(this);
393
375
  }
394
376
  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
- });
377
+ return renderTemporalAudio(this, fromMs, toMs);
418
378
  }
419
379
  async testPlayAudio(fromMs, toMs) {
420
380
  const renderedBuffer = await this.renderAudio(fromMs, toMs);
@@ -441,21 +401,13 @@ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
441
401
  }
442
402
  }
443
403
  await Promise.all(loaderTasks);
444
- efElements.map((el) => {
404
+ efElements.forEach((el) => {
445
405
  if ("productionSrc" in el && el.productionSrc instanceof Function) el.setAttribute("src", el.productionSrc());
446
406
  });
447
407
  }
448
408
  };
449
409
  __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);
410
+ __decorate([provide({ context: efContext })], EFTimegroup.prototype, "efContext", void 0);
459
411
  __decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
460
412
  __decorate([property({
461
413
  type: Number,