@editframe/elements 0.20.4-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 (183) hide show
  1. package/dist/DelayedLoadingState.js +0 -27
  2. package/dist/EF_FRAMEGEN.d.ts +5 -3
  3. package/dist/EF_FRAMEGEN.js +49 -11
  4. package/dist/_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js +7 -0
  5. package/dist/attachContextRoot.d.ts +1 -0
  6. package/dist/attachContextRoot.js +9 -0
  7. package/dist/elements/ContextProxiesController.d.ts +1 -2
  8. package/dist/elements/EFAudio.js +5 -9
  9. package/dist/elements/EFCaptions.d.ts +1 -3
  10. package/dist/elements/EFCaptions.js +112 -129
  11. package/dist/elements/EFImage.js +6 -7
  12. package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -5
  13. package/dist/elements/EFMedia/AssetMediaEngine.js +36 -33
  14. package/dist/elements/EFMedia/BaseMediaEngine.js +57 -73
  15. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +1 -1
  16. package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -78
  17. package/dist/elements/EFMedia/JitMediaEngine.js +9 -19
  18. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +7 -13
  19. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +2 -3
  20. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
  21. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +6 -5
  22. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
  23. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +1 -1
  24. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
  25. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +1 -1
  26. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +9 -25
  27. package/dist/elements/EFMedia/shared/BufferUtils.js +2 -17
  28. package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
  29. package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
  30. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
  31. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -10
  32. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
  33. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
  34. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
  35. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -7
  36. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -5
  37. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
  38. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -1
  39. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -70
  40. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -18
  41. package/dist/elements/EFMedia.d.ts +19 -0
  42. package/dist/elements/EFMedia.js +44 -25
  43. package/dist/elements/EFSourceMixin.js +5 -7
  44. package/dist/elements/EFSurface.js +6 -9
  45. package/dist/elements/EFTemporal.browsertest.d.ts +11 -0
  46. package/dist/elements/EFTemporal.d.ts +10 -0
  47. package/dist/elements/EFTemporal.js +100 -41
  48. package/dist/elements/EFThumbnailStrip.js +23 -73
  49. package/dist/elements/EFTimegroup.browsertest.d.ts +3 -3
  50. package/dist/elements/EFTimegroup.d.ts +35 -14
  51. package/dist/elements/EFTimegroup.js +138 -181
  52. package/dist/elements/EFVideo.d.ts +16 -2
  53. package/dist/elements/EFVideo.js +156 -108
  54. package/dist/elements/EFWaveform.js +23 -40
  55. package/dist/elements/SampleBuffer.js +3 -7
  56. package/dist/elements/TargetController.js +5 -5
  57. package/dist/elements/durationConverter.js +4 -4
  58. package/dist/elements/renderTemporalAudio.d.ts +10 -0
  59. package/dist/elements/renderTemporalAudio.js +35 -0
  60. package/dist/elements/updateAnimations.js +19 -43
  61. package/dist/gui/ContextMixin.d.ts +5 -5
  62. package/dist/gui/ContextMixin.js +167 -162
  63. package/dist/gui/Controllable.browsertest.d.ts +0 -0
  64. package/dist/gui/Controllable.d.ts +15 -0
  65. package/dist/gui/Controllable.js +9 -0
  66. package/dist/gui/EFConfiguration.js +7 -7
  67. package/dist/gui/EFControls.browsertest.d.ts +11 -0
  68. package/dist/gui/EFControls.d.ts +18 -4
  69. package/dist/gui/EFControls.js +70 -28
  70. package/dist/gui/EFDial.browsertest.d.ts +0 -0
  71. package/dist/gui/EFDial.d.ts +18 -0
  72. package/dist/gui/EFDial.js +141 -0
  73. package/dist/gui/EFFilmstrip.browsertest.d.ts +11 -0
  74. package/dist/gui/EFFilmstrip.d.ts +12 -2
  75. package/dist/gui/EFFilmstrip.js +214 -129
  76. package/dist/gui/EFFitScale.js +5 -8
  77. package/dist/gui/EFFocusOverlay.js +4 -4
  78. package/dist/gui/EFPause.browsertest.d.ts +0 -0
  79. package/dist/gui/EFPause.d.ts +23 -0
  80. package/dist/gui/EFPause.js +59 -0
  81. package/dist/gui/EFPlay.browsertest.d.ts +0 -0
  82. package/dist/gui/EFPlay.d.ts +23 -0
  83. package/dist/gui/EFPlay.js +59 -0
  84. package/dist/gui/EFPreview.d.ts +4 -0
  85. package/dist/gui/EFPreview.js +18 -9
  86. package/dist/gui/EFResizableBox.browsertest.d.ts +0 -0
  87. package/dist/gui/EFResizableBox.d.ts +34 -0
  88. package/dist/gui/EFResizableBox.js +547 -0
  89. package/dist/gui/EFScrubber.d.ts +9 -3
  90. package/dist/gui/EFScrubber.js +13 -13
  91. package/dist/gui/EFTimeDisplay.d.ts +7 -1
  92. package/dist/gui/EFTimeDisplay.js +8 -8
  93. package/dist/gui/EFToggleLoop.d.ts +9 -3
  94. package/dist/gui/EFToggleLoop.js +7 -5
  95. package/dist/gui/EFTogglePlay.d.ts +12 -4
  96. package/dist/gui/EFTogglePlay.js +26 -21
  97. package/dist/gui/EFWorkbench.js +5 -5
  98. package/dist/gui/PlaybackController.d.ts +67 -0
  99. package/dist/gui/PlaybackController.js +310 -0
  100. package/dist/gui/TWMixin.js +1 -1
  101. package/dist/gui/TWMixin2.js +1 -1
  102. package/dist/gui/TargetOrContextMixin.d.ts +10 -0
  103. package/dist/gui/TargetOrContextMixin.js +98 -0
  104. package/dist/gui/efContext.d.ts +2 -2
  105. package/dist/index.d.ts +5 -0
  106. package/dist/index.js +5 -1
  107. package/dist/otel/BridgeSpanExporter.d.ts +13 -0
  108. package/dist/otel/BridgeSpanExporter.js +87 -0
  109. package/dist/otel/setupBrowserTracing.d.ts +12 -0
  110. package/dist/otel/setupBrowserTracing.js +32 -0
  111. package/dist/otel/tracingHelpers.d.ts +34 -0
  112. package/dist/otel/tracingHelpers.js +112 -0
  113. package/dist/style.css +1 -1
  114. package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
  115. package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
  116. package/dist/transcoding/utils/UrlGenerator.js +2 -19
  117. package/dist/utils/LRUCache.js +6 -53
  118. package/package.json +13 -5
  119. package/src/elements/ContextProxiesController.ts +10 -10
  120. package/src/elements/EFAudio.ts +1 -0
  121. package/src/elements/EFCaptions.browsertest.ts +128 -56
  122. package/src/elements/EFCaptions.ts +60 -34
  123. package/src/elements/EFImage.browsertest.ts +1 -2
  124. package/src/elements/EFMedia/AssetMediaEngine.ts +65 -37
  125. package/src/elements/EFMedia/BaseMediaEngine.ts +110 -52
  126. package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
  127. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +3 -0
  128. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +7 -3
  129. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +1 -1
  130. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
  131. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +16 -10
  132. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
  133. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -116
  134. package/src/elements/EFMedia.browsertest.ts +8 -15
  135. package/src/elements/EFMedia.ts +54 -8
  136. package/src/elements/EFSurface.browsertest.ts +2 -6
  137. package/src/elements/EFSurface.ts +1 -0
  138. package/src/elements/EFTemporal.browsertest.ts +58 -1
  139. package/src/elements/EFTemporal.ts +140 -4
  140. package/src/elements/EFThumbnailStrip.browsertest.ts +2 -8
  141. package/src/elements/EFThumbnailStrip.ts +1 -0
  142. package/src/elements/EFTimegroup.browsertest.ts +16 -15
  143. package/src/elements/EFTimegroup.ts +281 -275
  144. package/src/elements/EFVideo.browsertest.ts +162 -74
  145. package/src/elements/EFVideo.ts +229 -101
  146. package/src/elements/FetchContext.browsertest.ts +7 -2
  147. package/src/elements/TargetController.browsertest.ts +1 -0
  148. package/src/elements/TargetController.ts +1 -0
  149. package/src/elements/renderTemporalAudio.ts +108 -0
  150. package/src/elements/updateAnimations.browsertest.ts +181 -6
  151. package/src/elements/updateAnimations.ts +6 -6
  152. package/src/gui/ContextMixin.browsertest.ts +274 -27
  153. package/src/gui/ContextMixin.ts +230 -175
  154. package/src/gui/Controllable.browsertest.ts +258 -0
  155. package/src/gui/Controllable.ts +41 -0
  156. package/src/gui/EFControls.browsertest.ts +294 -80
  157. package/src/gui/EFControls.ts +139 -28
  158. package/src/gui/EFDial.browsertest.ts +84 -0
  159. package/src/gui/EFDial.ts +172 -0
  160. package/src/gui/EFFilmstrip.browsertest.ts +712 -0
  161. package/src/gui/EFFilmstrip.ts +213 -23
  162. package/src/gui/EFPause.browsertest.ts +202 -0
  163. package/src/gui/EFPause.ts +73 -0
  164. package/src/gui/EFPlay.browsertest.ts +202 -0
  165. package/src/gui/EFPlay.ts +73 -0
  166. package/src/gui/EFPreview.ts +20 -5
  167. package/src/gui/EFResizableBox.browsertest.ts +79 -0
  168. package/src/gui/EFResizableBox.ts +898 -0
  169. package/src/gui/EFScrubber.ts +7 -5
  170. package/src/gui/EFTimeDisplay.browsertest.ts +19 -19
  171. package/src/gui/EFTimeDisplay.ts +3 -1
  172. package/src/gui/EFToggleLoop.ts +6 -5
  173. package/src/gui/EFTogglePlay.ts +30 -23
  174. package/src/gui/PlaybackController.ts +522 -0
  175. package/src/gui/TWMixin.css +3 -0
  176. package/src/gui/TargetOrContextMixin.ts +185 -0
  177. package/src/gui/efContext.ts +2 -2
  178. package/src/otel/BridgeSpanExporter.ts +150 -0
  179. package/src/otel/setupBrowserTracing.ts +73 -0
  180. package/src/otel/tracingHelpers.ts +251 -0
  181. package/test/cache-integration-verification.browsertest.ts +1 -1
  182. package/types.json +1 -1
  183. package/dist/elements/ContextProxiesController.js +0 -69
@@ -1,7 +1,14 @@
1
1
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
2
- import { isContextMixin } from "../gui/ContextMixin.js";
3
- import { durationConverter } from "./durationConverter.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";
4
5
  import { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, resetTemporalCache, shallowGetTemporalElements, timegroupContext } from "./EFTemporal.js";
6
+ import { efContext } from "../gui/efContext.js";
7
+ import { isContextMixin } from "../gui/ContextMixin.js";
8
+ import { TWMixin } from "../gui/TWMixin2.js";
9
+ import { isTracingEnabled, withSpan } from "../otel/tracingHelpers.js";
10
+ import { renderTemporalAudio } from "./renderTemporalAudio.js";
11
+ import { EFTargetable } from "./TargetController.js";
5
12
  import { deepGetMediaElements } from "./EFMedia.js";
6
13
  import { TimegroupController } from "./TimegroupController.js";
7
14
  import { evaluateTemporalStateForAnimation, updateAnimations } from "./updateAnimations.js";
@@ -10,10 +17,9 @@ import { Task, TaskStatus } from "@lit/task";
10
17
  import debug from "debug";
11
18
  import { LitElement, css, html } from "lit";
12
19
  import { customElement, property } from "lit/decorators.js";
13
- import _decorate from "@oxc-project/runtime/helpers/decorate";
14
20
  var _EFTimegroup;
15
- const log = debug("ef:elements:EFTimegroup");
16
- let sequenceDurationCache = /* @__PURE__ */ new WeakMap();
21
+ var log = debug("ef:elements:EFTimegroup");
22
+ var sequenceDurationCache = /* @__PURE__ */ new WeakMap();
17
23
  const flushSequenceDurationCache = () => {
18
24
  sequenceDurationCache = /* @__PURE__ */ new WeakMap();
19
25
  };
@@ -22,25 +28,30 @@ const shallowGetTimegroups = (element, groups = []) => {
22
28
  else shallowGetTimegroups(child, groups);
23
29
  return groups;
24
30
  };
25
- let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
31
+ var EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(LitElement))) {
26
32
  static {
27
33
  _EFTimegroup = this;
28
34
  }
29
35
  constructor(..._args) {
30
36
  super(..._args);
31
37
  this._timeGroupContext = this;
32
- this._mode = "contain";
33
- this._overlapMs = 0;
38
+ this.efContext = this;
39
+ this.mode = "contain";
40
+ this.overlapMs = 0;
34
41
  this.fit = "none";
35
42
  this.mediaDurationsPromise = void 0;
36
43
  this.frameTask = new Task(this, {
37
44
  autoRun: false,
38
45
  args: () => [this.ownCurrentTimeMs, this.currentTimeMs],
39
- task: async ([]) => {
40
- if (this.isRootTimegroup) {
46
+ task: async ([ownCurrentTimeMs, currentTimeMs]) => {
47
+ if (this.isRootTimegroup) await withSpan("timegroup.frameTask", {
48
+ timegroupId: this.id || "unknown",
49
+ ownCurrentTimeMs,
50
+ currentTimeMs
51
+ }, void 0, async () => {
41
52
  await this.waitForFrameTasks();
42
53
  updateAnimations(this);
43
- }
54
+ });
44
55
  }
45
56
  });
46
57
  this.seekTask = new Task(this, {
@@ -48,79 +59,75 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
48
59
  args: () => [this.#pendingSeekTime ?? this.#currentTime],
49
60
  onComplete: () => {},
50
61
  task: async ([targetTime]) => {
62
+ if (this.playbackController) {
63
+ await this.playbackController.seekTask.taskComplete;
64
+ return this.currentTime;
65
+ }
51
66
  if (!this.isRootTimegroup) return;
52
- await this.waitForMediaDurations();
53
- const newTime = Math.max(0, Math.min(targetTime ?? 0, this.durationMs / 1e3));
54
- this.#currentTime = newTime;
55
- this.requestUpdate("currentTime");
56
- await this.runThrottledFrameTask();
57
- this.#saveTimeToLocalStorage(this.#currentTime);
58
- this.#seekInProgress = false;
59
- return newTime;
67
+ return withSpan("timegroup.seekTask", {
68
+ timegroupId: this.id || "unknown",
69
+ targetTime: targetTime ?? 0,
70
+ durationMs: this.durationMs
71
+ }, void 0, async (span) => {
72
+ await this.waitForMediaDurations();
73
+ const newTime = Math.max(0, Math.min(targetTime ?? 0, this.durationMs / 1e3));
74
+ if (isTracingEnabled()) span.setAttribute("newTime", newTime);
75
+ this.#currentTime = newTime;
76
+ this.requestUpdate("currentTime");
77
+ await this.runThrottledFrameTask();
78
+ this.saveTimeToLocalStorage(this.#currentTime);
79
+ this.#seekInProgress = false;
80
+ return newTime;
81
+ });
60
82
  }
61
83
  });
62
84
  }
85
+ static get observedAttributes() {
86
+ return [
87
+ ...super.observedAttributes || [],
88
+ "mode",
89
+ "overlap",
90
+ "currenttime",
91
+ "fit"
92
+ ];
93
+ }
63
94
  static {
64
95
  this.styles = css`
65
96
  :host {
66
97
  display: block;
98
+ position: relative;
99
+ overflow: hidden;
100
+ }
101
+
102
+ ::slotted(ef-timegroup) {
103
+ position: absolute;
67
104
  width: 100%;
68
105
  height: 100%;
69
- position: absolute;
70
106
  top: 0;
71
107
  left: 0;
72
108
  }
73
109
  `;
74
110
  }
75
- #currentTime = void 0;
76
- set mode(value) {
77
- sequenceDurationCache.delete(this);
78
- this._mode = value;
79
- }
80
- get mode() {
81
- return this._mode;
82
- }
83
- set overlapMs(value) {
84
- sequenceDurationCache.delete(this);
85
- this._overlapMs = value;
86
- }
87
- get overlapMs() {
88
- 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);
89
115
  }
90
116
  #resizeObserver;
117
+ #currentTime = void 0;
91
118
  #seekInProgress = false;
92
119
  #pendingSeekTime;
93
120
  #processingPendingSeek = false;
94
- #frameTaskInProgress = false;
95
- #pendingFrameTaskRun = false;
96
- #processingPendingFrameTask = false;
97
- /**
98
- * Throttles frameTask execution to ensure only one runs at a time while preserving the last request
99
- */
100
121
  async runThrottledFrameTask() {
101
- if (this.#frameTaskInProgress) {
102
- this.#pendingFrameTaskRun = true;
103
- while (this.#frameTaskInProgress) await this.frameTask.taskComplete;
104
- return;
105
- }
106
- this.#frameTaskInProgress = true;
107
- try {
108
- await this.frameTask.run();
109
- } finally {
110
- this.#frameTaskInProgress = false;
111
- if (this.#pendingFrameTaskRun && !this.#processingPendingFrameTask) {
112
- this.#pendingFrameTaskRun = false;
113
- this.#processingPendingFrameTask = true;
114
- try {
115
- await this.runThrottledFrameTask();
116
- } finally {
117
- this.#processingPendingFrameTask = false;
118
- }
119
- } else this.#pendingFrameTaskRun = false;
120
- }
122
+ if (this.playbackController) return this.playbackController.runThrottledFrameTask();
123
+ await this.frameTask.run();
121
124
  }
122
125
  set currentTime(time) {
123
- 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));
124
131
  if (!this.isRootTimegroup) return;
125
132
  if (Number.isNaN(time)) return;
126
133
  if (time === this.#currentTime && !this.#processingPendingSeek) return;
@@ -146,6 +153,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
146
153
  });
147
154
  }
148
155
  get currentTime() {
156
+ if (this.playbackController) return this.playbackController.currentTime;
149
157
  return this.#currentTime ?? 0;
150
158
  }
151
159
  set currentTimeMs(ms) {
@@ -154,16 +162,23 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
154
162
  get currentTimeMs() {
155
163
  return this.currentTime * 1e3;
156
164
  }
157
- /**
158
- * Determines if this is a root timegroup (no parent timegroups)
159
- */
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
+ }
160
178
  get isRootTimegroup() {
161
179
  return !this.parentTimegroup;
162
180
  }
163
- /**
164
- * Saves time to localStorage (extracted for reuse)
165
- */
166
- #saveTimeToLocalStorage(time) {
181
+ saveTimeToLocalStorage(time) {
167
182
  try {
168
183
  if (this.id && this.isConnected && !Number.isNaN(time)) localStorage.setItem(this.storageKey, time.toString());
169
184
  } catch (error) {
@@ -179,10 +194,10 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
179
194
  flushStartTimeMsCache();
180
195
  this.requestUpdate();
181
196
  };
182
- maybeLoadTimeFromLocalStorage() {
197
+ loadTimeFromLocalStorage() {
183
198
  if (this.id) try {
184
199
  const storedValue = localStorage.getItem(this.storageKey);
185
- if (storedValue === null) return void 0;
200
+ if (storedValue === null) return;
186
201
  return Number.parseFloat(storedValue);
187
202
  } catch (error) {
188
203
  log("Failed to load time from localStorage", error);
@@ -190,9 +205,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
190
205
  }
191
206
  connectedCallback() {
192
207
  super.connectedCallback();
193
- this.waitForMediaDurations().then(() => {
208
+ if (!this.playbackController) this.waitForMediaDurations().then(() => {
194
209
  if (this.id) {
195
- const maybeLoadedTime = this.maybeLoadTimeFromLocalStorage();
210
+ const maybeLoadedTime = this.loadTimeFromLocalStorage();
196
211
  if (maybeLoadedTime !== void 0) this.currentTime = maybeLoadedTime;
197
212
  }
198
213
  if (EF_INTERACTIVE && this.seekTask.status === TaskStatus.INITIAL) this.seekTask.run();
@@ -201,7 +216,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
201
216
  if (this.shouldWrapWithWorkbench()) this.wrapWithWorkbench();
202
217
  }
203
218
  #previousDurationMs = 0;
204
- updated(_changedProperties) {
219
+ updated(changedProperties) {
220
+ super.updated(changedProperties);
221
+ if (changedProperties.has("mode") || changedProperties.has("overlapMs")) sequenceDurationCache.delete(this);
205
222
  if (this.#previousDurationMs !== this.durationMs) {
206
223
  this.#previousDurationMs = this.durationMs;
207
224
  this.runThrottledFrameTask();
@@ -217,17 +234,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
217
234
  }
218
235
  get intrinsicDurationMs() {
219
236
  if (this.hasExplicitDuration) return this.explicitDurationMs;
220
- return void 0;
221
237
  }
222
238
  get hasOwnDuration() {
223
239
  return this.mode === "contain" || this.mode === "sequence" || this.mode === "fixed" && this.hasExplicitDuration;
224
240
  }
225
241
  get durationMs() {
226
242
  switch (this.mode) {
227
- case "fit": {
243
+ case "fit":
228
244
  if (!this.parentTimegroup) return 0;
229
245
  return this.parentTimegroup.durationMs;
230
- }
231
246
  case "fixed": return super.durationMs;
232
247
  case "sequence": {
233
248
  const cachedDuration = sequenceDurationCache.get(this);
@@ -258,18 +273,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
258
273
  signal?.throwIfAborted();
259
274
  const temporals = deepGetElementsWithFrameTasks(this);
260
275
  const timelineTimeMs = (this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1e3;
261
- const activeTemporals = temporals.filter((temporal) => {
276
+ const frameTasks = temporals.filter((temporal) => {
262
277
  if (!("startTimeMs" in temporal) || !("endTimeMs" in temporal)) return true;
263
278
  const epsilon = .001;
264
279
  const startTimeMs = temporal.startTimeMs;
265
280
  const endTimeMs = temporal.endTimeMs;
266
281
  const elementStartsBeforeEnd = startTimeMs <= timelineTimeMs + epsilon;
267
- const isRootTimegroup = temporal.tagName.toLowerCase() === "ef-timegroup" && !temporal.parentTimegroup;
268
- const useInclusiveEnd = isRootTimegroup;
269
- const elementEndsAfterStart = useInclusiveEnd ? endTimeMs >= timelineTimeMs : endTimeMs > timelineTimeMs;
282
+ const elementEndsAfterStart = temporal.tagName.toLowerCase() === "ef-timegroup" && !temporal.parentTimegroup ? endTimeMs >= timelineTimeMs : endTimeMs > timelineTimeMs;
270
283
  return elementStartsBeforeEnd && elementEndsAfterStart;
271
- });
272
- const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);
284
+ }).map((temporal) => temporal.frameTask);
273
285
  frameTasks.forEach((task) => {
274
286
  task.run();
275
287
  });
@@ -288,31 +300,45 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
288
300
  }
289
301
  }
290
302
  async waitForFrameTasks() {
291
- const temporalElements = deepGetElementsWithFrameTasks(this);
292
- const visibleElements = temporalElements.filter((element) => {
293
- const animationState = evaluateTemporalStateForAnimation(element);
294
- return animationState.isVisible;
303
+ return await withSpan("timegroup.waitForFrameTasks", {
304
+ timegroupId: this.id || "unknown",
305
+ mode: this.mode
306
+ }, void 0, async (span) => {
307
+ const innerStart = performance.now();
308
+ const temporalElements = deepGetElementsWithFrameTasks(this);
309
+ if (isTracingEnabled()) span.setAttribute("temporalElementsCount", temporalElements.length);
310
+ const visibleElements = temporalElements.filter((element) => {
311
+ return evaluateTemporalStateForAnimation(element).isVisible;
312
+ });
313
+ if (isTracingEnabled()) span.setAttribute("visibleElementsCount", visibleElements.length);
314
+ const promiseStart = performance.now();
315
+ await Promise.all(visibleElements.map((element) => element.frameTask.run()));
316
+ const promiseEnd = performance.now();
317
+ const innerEnd = performance.now();
318
+ if (isTracingEnabled()) {
319
+ span.setAttribute("actualInnerMs", innerEnd - innerStart);
320
+ span.setAttribute("promiseAwaitMs", promiseEnd - promiseStart);
321
+ }
295
322
  });
296
- await Promise.all(visibleElements.map((element) => element.frameTask.run()));
297
323
  }
298
324
  async waitForMediaDurations() {
299
325
  if (!this.mediaDurationsPromise) this.mediaDurationsPromise = this.#waitForMediaDurations();
300
326
  return this.mediaDurationsPromise;
301
327
  }
302
- /**
303
- * Wait for all media elements to load their initial segments.
304
- * Ideally we would only need the extracted index json data, but
305
- * that caused issues with constructing audio data. We had negative durations
306
- * in calculations and it was not clear why.
307
- */
308
328
  async #waitForMediaDurations() {
309
- await this.updateComplete;
310
- const mediaElements = deepGetMediaElements(this);
311
- await Promise.all(mediaElements.map((m) => m.mediaEngineTask.value ? Promise.resolve() : m.mediaEngineTask.run()));
312
- flushStartTimeMsCache();
313
- flushSequenceDurationCache();
314
- this.requestUpdate("currentTime");
315
- await this.updateComplete;
329
+ return withSpan("timegroup.waitForMediaDurations", {
330
+ timegroupId: this.id || "unknown",
331
+ mode: this.mode
332
+ }, void 0, async (span) => {
333
+ await this.updateComplete;
334
+ const mediaElements = deepGetMediaElements(this);
335
+ if (isTracingEnabled()) span.setAttribute("mediaElementsCount", mediaElements.length);
336
+ await Promise.all(mediaElements.map((m) => m.mediaEngineTask.value ? Promise.resolve() : m.mediaEngineTask.run()));
337
+ flushStartTimeMsCache();
338
+ flushSequenceDurationCache();
339
+ this.requestUpdate("currentTime");
340
+ await this.updateComplete;
341
+ });
316
342
  }
317
343
  get childTemporals() {
318
344
  return shallowGetTemporalElements(this);
@@ -325,16 +351,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
325
351
  }
326
352
  return null;
327
353
  }
328
- /**
329
- * Returns true if the timegroup should be wrapped with a workbench.
330
- *
331
- * A timegroup should be wrapped with a workbench if it is the root-most timegroup
332
- * and EF_INTERACTIVE is true.
333
- *
334
- * If the timegroup is already wrappedin a context provider like ef-preview,
335
- * it should NOT be wrapped in a workbench.
336
- */
337
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;
338
357
  return EF_INTERACTIVE && this.closest("ef-timegroup") === this && this.closest("ef-preview") === null && this.closest("ef-workbench") === null && this.closest("test-context") === null;
339
358
  }
340
359
  wrapWithWorkbench() {
@@ -351,66 +370,12 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
351
370
  get efElements() {
352
371
  return Array.from(this.querySelectorAll("ef-audio, ef-video, ef-image, ef-captions, ef-waveform"));
353
372
  }
354
- async #addAudioToContext(audioContext, fromMs, toMs) {
355
- await this.waitForMediaDurations();
356
- const abortController = new AbortController();
357
- await Promise.all(deepGetMediaElements(this).map(async (mediaElement) => {
358
- if (mediaElement.mute) return;
359
- const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
360
- const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
361
- const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
362
- if (!mediaOverlaps) return;
363
- const mediaLocalFromMs = Math.max(0, fromMs - mediaElement.startTimeMs);
364
- const mediaLocalToMs = Math.min(mediaElement.endTimeMs - mediaElement.startTimeMs, toMs - mediaElement.startTimeMs);
365
- if (mediaLocalFromMs >= mediaLocalToMs) return;
366
- const sourceInMs = mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
367
- const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
368
- const mediaSourceToMs = mediaLocalToMs + sourceInMs;
369
- let audio;
370
- try {
371
- audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, abortController.signal);
372
- } catch (error) {
373
- if (error instanceof Error && error.message.includes("No audio track available")) return;
374
- throw error;
375
- }
376
- if (!audio) return;
377
- const bufferSource = audioContext.createBufferSource();
378
- bufferSource.buffer = await audioContext.decodeAudioData(await audio.blob.arrayBuffer());
379
- bufferSource.connect(audioContext.destination);
380
- const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
381
- const requestedSourceFromMs = mediaSourceFromMs;
382
- const actualSourceStartMs = audio.startMs;
383
- const offsetInBufferMs = requestedSourceFromMs - actualSourceStartMs;
384
- const safeOffsetMs = Math.max(0, offsetInBufferMs);
385
- const requestedDurationMs = mediaSourceToMs - mediaSourceFromMs;
386
- const availableAudioMs = audio.endMs - audio.startMs;
387
- const actualDurationMs = Math.min(requestedDurationMs, availableAudioMs - safeOffsetMs);
388
- if (actualDurationMs <= 0) return;
389
- bufferSource.start(ctxStartMs / 1e3, safeOffsetMs / 1e3, actualDurationMs / 1e3);
390
- }));
373
+ getMediaElements() {
374
+ return deepGetMediaElements(this);
391
375
  }
392
376
  async renderAudio(fromMs, toMs) {
393
- const durationMs = toMs - fromMs;
394
- const duration = durationMs / 1e3;
395
- const exactSamples = 48e3 * duration;
396
- const aacFrames = exactSamples / 1024;
397
- const alignedFrames = Math.round(aacFrames);
398
- const contextSize = alignedFrames * 1024;
399
- if (contextSize <= 0) throw new Error(`Duration must be greater than 0 when rendering audio. ${contextSize}ms`);
400
- let audioContext;
401
- try {
402
- audioContext = new OfflineAudioContext(2, contextSize, 48e3);
403
- } catch (error) {
404
- 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).`);
405
- }
406
- await this.#addAudioToContext(audioContext, fromMs, toMs);
407
- const renderedBuffer = await audioContext.startRendering();
408
- return renderedBuffer;
409
- }
410
- /**
411
- * TEMPORARY TEST METHOD: Renders audio and immediately plays it back
412
- * Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds
413
- */
377
+ return renderTemporalAudio(this, fromMs, toMs);
378
+ }
414
379
  async testPlayAudio(fromMs, toMs) {
415
380
  const renderedBuffer = await this.renderAudio(fromMs, toMs);
416
381
  const playbackContext = new AudioContext();
@@ -436,25 +401,17 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
436
401
  }
437
402
  }
438
403
  await Promise.all(loaderTasks);
439
- efElements.map((el) => {
404
+ efElements.forEach((el) => {
440
405
  if ("productionSrc" in el && el.productionSrc instanceof Function) el.setAttribute("src", el.productionSrc());
441
406
  });
442
407
  }
443
408
  };
444
- _decorate([provide({ context: timegroupContext })], EFTimegroup.prototype, "_timeGroupContext", void 0);
445
- _decorate([property({
446
- type: String,
447
- attribute: "mode"
448
- })], EFTimegroup.prototype, "mode", null);
449
- _decorate([property({
450
- type: Number,
451
- converter: durationConverter,
452
- attribute: "overlap"
453
- })], EFTimegroup.prototype, "overlapMs", null);
454
- _decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
455
- _decorate([property({
409
+ __decorate([provide({ context: timegroupContext })], EFTimegroup.prototype, "_timeGroupContext", void 0);
410
+ __decorate([provide({ context: efContext })], EFTimegroup.prototype, "efContext", void 0);
411
+ __decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
412
+ __decorate([property({
456
413
  type: Number,
457
414
  attribute: "currenttime"
458
415
  })], EFTimegroup.prototype, "currentTime", null);
459
- EFTimegroup = _EFTimegroup = _decorate([customElement("ef-timegroup")], EFTimegroup);
416
+ EFTimegroup = _EFTimegroup = __decorate([customElement("ef-timegroup")], EFTimegroup);
460
417
  export { EFTimegroup, flushSequenceDurationCache, shallowGetTimegroups };
@@ -67,7 +67,11 @@ export declare class EFVideo extends EFVideo_base {
67
67
  * Set loading state for user feedback
68
68
  */
69
69
  private setLoadingState;
70
- paintTask: Task<readonly [number], void>;
70
+ /**
71
+ * Paint the current video frame to canvas
72
+ * Called by frameTask after seek is complete
73
+ */
74
+ paint(seekToMs: number, parentSpan?: any): void;
71
75
  /**
72
76
  * Clear the canvas when element becomes inactive
73
77
  */
@@ -75,7 +79,7 @@ export declare class EFVideo extends EFVideo_base {
75
79
  /**
76
80
  * Display a video frame on the canvas
77
81
  */
78
- displayFrame(frame: VideoFrame, seekToMs: number): number;
82
+ displayFrame(frame: VideoFrame, seekToMs: number, parentSpan?: any): void;
79
83
  /**
80
84
  * Check if we're in production rendering mode (EF_FRAMEGEN active) vs preview mode
81
85
  */
@@ -89,10 +93,20 @@ export declare class EFVideo extends EFVideo_base {
89
93
  * Still used by EFCaptions - maps to unified video seek task
90
94
  */
91
95
  get fragmentIndexTask(): Task<readonly [number], import('mediabunny').VideoSample | undefined>;
96
+ /**
97
+ * Helper method for tests: wait for the current frame to be ready
98
+ * This encapsulates the complexity of ensuring the video has updated
99
+ * and its frameTask has completed.
100
+ *
101
+ * @returns Promise that resolves when the frame is ready
102
+ */
103
+ waitForFrameReady(): Promise<void>;
92
104
  /**
93
105
  * Clean up resources when component is disconnected
94
106
  */
95
107
  disconnectedCallback(): void;
108
+ didBecomeRoot(): void;
109
+ didBecomeChild(): void;
96
110
  }
97
111
  declare global {
98
112
  interface HTMLElementTagNameMap {