@editframe/elements 0.20.4-beta.0 → 0.21.0-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 (92) hide show
  1. package/dist/DelayedLoadingState.js +0 -27
  2. package/dist/EF_FRAMEGEN.d.ts +5 -3
  3. package/dist/EF_FRAMEGEN.js +50 -11
  4. package/dist/_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js +7 -0
  5. package/dist/elements/ContextProxiesController.js +2 -22
  6. package/dist/elements/EFAudio.js +4 -8
  7. package/dist/elements/EFCaptions.js +59 -84
  8. package/dist/elements/EFImage.js +5 -6
  9. package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -4
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +35 -30
  11. package/dist/elements/EFMedia/BaseMediaEngine.js +57 -73
  12. package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -76
  13. package/dist/elements/EFMedia/JitMediaEngine.js +9 -19
  14. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +3 -6
  15. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -1
  16. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
  17. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +6 -5
  18. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
  19. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +1 -1
  20. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
  21. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +1 -1
  22. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +4 -16
  23. package/dist/elements/EFMedia/shared/BufferUtils.js +2 -15
  24. package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
  25. package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
  26. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
  27. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -10
  28. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
  29. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
  30. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
  31. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -7
  32. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -5
  33. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
  34. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -1
  35. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -70
  36. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +7 -11
  37. package/dist/elements/EFMedia.js +26 -24
  38. package/dist/elements/EFSourceMixin.js +5 -7
  39. package/dist/elements/EFSurface.js +6 -9
  40. package/dist/elements/EFTemporal.js +19 -37
  41. package/dist/elements/EFThumbnailStrip.js +16 -59
  42. package/dist/elements/EFTimegroup.js +95 -90
  43. package/dist/elements/EFVideo.d.ts +6 -2
  44. package/dist/elements/EFVideo.js +142 -107
  45. package/dist/elements/EFWaveform.js +18 -27
  46. package/dist/elements/SampleBuffer.js +2 -5
  47. package/dist/elements/TargetController.js +3 -3
  48. package/dist/elements/durationConverter.js +4 -4
  49. package/dist/elements/updateAnimations.js +14 -35
  50. package/dist/gui/ContextMixin.js +23 -52
  51. package/dist/gui/EFConfiguration.js +7 -7
  52. package/dist/gui/EFControls.js +5 -5
  53. package/dist/gui/EFFilmstrip.js +77 -98
  54. package/dist/gui/EFFitScale.js +5 -6
  55. package/dist/gui/EFFocusOverlay.js +4 -4
  56. package/dist/gui/EFPreview.js +4 -4
  57. package/dist/gui/EFScrubber.js +9 -9
  58. package/dist/gui/EFTimeDisplay.js +5 -5
  59. package/dist/gui/EFToggleLoop.js +4 -4
  60. package/dist/gui/EFTogglePlay.js +5 -5
  61. package/dist/gui/EFWorkbench.js +5 -5
  62. package/dist/gui/TWMixin2.js +1 -1
  63. package/dist/index.d.ts +1 -0
  64. package/dist/otel/BridgeSpanExporter.d.ts +13 -0
  65. package/dist/otel/BridgeSpanExporter.js +87 -0
  66. package/dist/otel/setupBrowserTracing.d.ts +12 -0
  67. package/dist/otel/setupBrowserTracing.js +30 -0
  68. package/dist/otel/tracingHelpers.d.ts +34 -0
  69. package/dist/otel/tracingHelpers.js +113 -0
  70. package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
  71. package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
  72. package/dist/transcoding/utils/UrlGenerator.js +2 -19
  73. package/dist/utils/LRUCache.js +6 -53
  74. package/package.json +10 -2
  75. package/src/elements/EFCaptions.browsertest.ts +2 -0
  76. package/src/elements/EFMedia/AssetMediaEngine.ts +65 -37
  77. package/src/elements/EFMedia/BaseMediaEngine.ts +110 -52
  78. package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
  79. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +7 -3
  80. package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
  81. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +16 -10
  82. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
  83. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -116
  84. package/src/elements/EFMedia.ts +16 -1
  85. package/src/elements/EFTimegroup.browsertest.ts +10 -8
  86. package/src/elements/EFTimegroup.ts +164 -76
  87. package/src/elements/EFVideo.browsertest.ts +19 -27
  88. package/src/elements/EFVideo.ts +203 -101
  89. package/src/otel/BridgeSpanExporter.ts +150 -0
  90. package/src/otel/setupBrowserTracing.ts +68 -0
  91. package/src/otel/tracingHelpers.ts +251 -0
  92. package/types.json +1 -1
@@ -1,16 +1,11 @@
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js";
1
2
  import { OrderedLRUCache } from "../utils/LRUCache.js";
2
3
  import { TargetController } from "./TargetController.js";
3
4
  import { Task } from "@lit/task";
4
5
  import { LitElement, css, html } from "lit";
5
6
  import { customElement, property, state } from "lit/decorators.js";
6
- import _decorate from "@oxc-project/runtime/helpers/decorate";
7
7
  import { createRef, ref } from "lit/directives/ref.js";
8
- /**
9
- * Global thumbnail image cache for smooth resize performance
10
- * Shared across all thumbnail strip instances
11
- * Uses OrderedLRUCache for efficient timestamp-based searching
12
- */
13
- const thumbnailImageCache = new OrderedLRUCache(200, (a, b) => {
8
+ var thumbnailImageCache = new OrderedLRUCache(200, (a, b) => {
14
9
  const partsA = a.split(":");
15
10
  const partsB = b.split(":");
16
11
  const timeA = Number.parseFloat(partsA[partsA.length - 1] || "0");
@@ -18,27 +13,16 @@ const thumbnailImageCache = new OrderedLRUCache(200, (a, b) => {
18
13
  return timeA - timeB;
19
14
  });
20
15
  globalThis.debugThumbnailCache = thumbnailImageCache;
21
- /**
22
- * Quantize timestamp to 30fps frame boundaries for consistent caching
23
- * This eliminates cache misses from floating point precision differences
24
- */
25
16
  function quantizeTimestamp(timeMs) {
26
17
  const frameIntervalMs = 1e3 / 30;
27
18
  return Math.round(timeMs / frameIntervalMs) * frameIntervalMs;
28
19
  }
29
- /**
30
- * Generate cache key for thumbnail image data (dimension-independent, quantized)
31
- */
32
20
  function getThumbnailCacheKey(videoSrc, timeMs) {
33
21
  const quantizedTimeMs = quantizeTimestamp(timeMs);
34
22
  return `${videoSrc}:${quantizedTimeMs}`;
35
23
  }
36
- const THUMBNAIL_GAP = 1;
37
- const STRIP_BORDER_PADDING = 4;
38
- /**
39
- * Calculate optimal thumbnail count and timestamps for the strip
40
- * Groups thumbnails by scrub segment ID for efficient caching
41
- */
24
+ var THUMBNAIL_GAP = 1;
25
+ var STRIP_BORDER_PADDING = 4;
42
26
  function calculateThumbnailLayout(stripWidth, thumbnailWidth, startTimeMs, endTimeMs, scrubSegmentDurationMs) {
43
27
  if (stripWidth <= 0 || thumbnailWidth <= 0 || endTimeMs <= startTimeMs) return {
44
28
  count: 0,
@@ -68,7 +52,7 @@ function calculateThumbnailLayout(stripWidth, thumbnailWidth, startTimeMs, endTi
68
52
  segments
69
53
  };
70
54
  }
71
- let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
55
+ var EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
72
56
  constructor(..._args) {
73
57
  super(..._args);
74
58
  this.canvasRef = createRef();
@@ -221,9 +205,6 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
221
205
  get stripWidth() {
222
206
  return this._stripWidth;
223
207
  }
224
- /**
225
- * Run thumbnail render task directly with provided layout (bypasses task args dependency)
226
- */
227
208
  async runThumbnailRenderTask(layout) {
228
209
  if (!layout || !this.targetElement || layout.count === 0) return [];
229
210
  return this.renderThumbnails(layout, this.targetElement, this.thumbnailWidth);
@@ -236,9 +217,6 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
236
217
  }
237
218
  if (changedProperties.has("thumbnailWidth") || changedProperties.has("startTimeMs") || changedProperties.has("endTimeMs") || changedProperties.has("useIntrinsicDuration")) this.runThumbnailUpdate();
238
219
  }
239
- /**
240
- * Run thumbnail update with responsive debouncing (based on EFTimegroup currentTime pattern)
241
- */
242
220
  runThumbnailUpdate() {
243
221
  if (this._thumbnailUpdateInProgress) {
244
222
  this._pendingThumbnailUpdate = true;
@@ -257,9 +235,6 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
257
235
  }
258
236
  });
259
237
  }
260
- /**
261
- * Calculate layout with a ready media engine
262
- */
263
238
  calculateLayoutWithMediaEngine(stripWidth, thumbnailWidth, targetElement, startTimeMs, endTimeMs, useIntrinsicDuration, mediaEngine) {
264
239
  if (useIntrinsicDuration) {
265
240
  const effectiveStartMs$1 = startTimeMs ?? 0;
@@ -272,17 +247,10 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
272
247
  const effectiveEndMs = endTimeMs !== void 0 ? sourceStart + endTimeMs : sourceStart + trimmedDuration;
273
248
  return this.generateLayoutFromTimeRange(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, mediaEngine);
274
249
  }
275
- /**
276
- * Generate layout from calculated time range
277
- */
278
250
  generateLayoutFromTimeRange(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, mediaEngine) {
279
251
  const scrubSegmentDurationMs = mediaEngine && typeof mediaEngine.getScrubVideoRendition === "function" ? mediaEngine.getScrubVideoRendition()?.segmentDurationMs : void 0;
280
- const layout = calculateThumbnailLayout(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, scrubSegmentDurationMs);
281
- return layout;
252
+ return calculateThumbnailLayout(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, scrubSegmentDurationMs);
282
253
  }
283
- /**
284
- * Render thumbnails with provided layout (main rendering logic)
285
- */
286
254
  async renderThumbnails(layout, targetElement, thumbnailWidth) {
287
255
  if (!layout || !targetElement || layout.count === 0) return [];
288
256
  const videoSrc = targetElement.src;
@@ -299,8 +267,7 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
299
267
  const timePlus = thumbnail.timeMs + 5e3;
300
268
  const rangeStartKey = `${videoSrc}:${timeMinus}`;
301
269
  const rangeEndKey = `${videoSrc}:${timePlus}`;
302
- const nearHits = thumbnailImageCache.findRange(rangeStartKey, rangeEndKey);
303
- const sameVideoHits = nearHits.filter((hit) => hit.key.startsWith(`${videoSrc}:`));
270
+ const sameVideoHits = thumbnailImageCache.findRange(rangeStartKey, rangeEndKey).filter((hit) => hit.key.startsWith(`${videoSrc}:`));
304
271
  if (sameVideoHits.length > 0) {
305
272
  const nearestHit = sameVideoHits.reduce((closest, current) => {
306
273
  const currentParts = current.key.split(":");
@@ -338,8 +305,7 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
338
305
  this.resizeObserver = new ResizeObserver((entries) => {
339
306
  for (const entry of entries) {
340
307
  const width = entry.borderBoxSize && entry.borderBoxSize.length > 0 ? entry.borderBoxSize[0]?.inlineSize : entry.contentRect.width;
341
- const height = entry.borderBoxSize && entry.borderBoxSize.length > 0 ? entry.borderBoxSize[0]?.blockSize : entry.contentRect.height;
342
- this._stripHeight = height ?? 0;
308
+ this._stripHeight = (entry.borderBoxSize && entry.borderBoxSize.length > 0 ? entry.borderBoxSize[0]?.blockSize : entry.contentRect.height) ?? 0;
343
309
  this.stripWidth = width ?? 0;
344
310
  }
345
311
  });
@@ -358,9 +324,6 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
358
324
  this._videoPropertyObserver?.disconnect();
359
325
  this._videoPropertyObserver = void 0;
360
326
  }
361
- /**
362
- * Draw thumbnails to the canvas with cache hits and placeholders
363
- */
364
327
  async drawThumbnails(thumbnails) {
365
328
  const canvas = this.canvasRef.value;
366
329
  if (!canvas) return;
@@ -414,9 +377,6 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
414
377
  ctx.setLineDash([]);
415
378
  }
416
379
  }
417
- /**
418
- * Load missing thumbnails using MediaEngine batch extraction
419
- */
420
380
  async loadMissingThumbnails(thumbnails, targetElement) {
421
381
  const mediaEngine = targetElement.mediaEngineTask?.value;
422
382
  if (!mediaEngine) return;
@@ -440,9 +400,6 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
440
400
  }
441
401
  await this.drawThumbnails(thumbnails);
442
402
  }
443
- /**
444
- * Convert Canvas to ImageData for caching
445
- */
446
403
  canvasToImageData(canvas) {
447
404
  const ctx = canvas.getContext("2d");
448
405
  if (!ctx) return null;
@@ -459,21 +416,21 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
459
416
  `;
460
417
  }
461
418
  };
462
- _decorate([state()], EFThumbnailStrip.prototype, "targetElement", null);
463
- _decorate([property({ type: String })], EFThumbnailStrip.prototype, "target", void 0);
464
- _decorate([property({
419
+ __decorate([state()], EFThumbnailStrip.prototype, "targetElement", null);
420
+ __decorate([property({ type: String })], EFThumbnailStrip.prototype, "target", void 0);
421
+ __decorate([property({
465
422
  type: Number,
466
423
  attribute: "thumbnail-width"
467
424
  })], EFThumbnailStrip.prototype, "thumbnailWidth", void 0);
468
- _decorate([property({
425
+ __decorate([property({
469
426
  type: Number,
470
427
  attribute: "start-time-ms"
471
428
  })], EFThumbnailStrip.prototype, "startTimeMs", void 0);
472
- _decorate([property({
429
+ __decorate([property({
473
430
  type: Number,
474
431
  attribute: "end-time-ms"
475
432
  })], EFThumbnailStrip.prototype, "endTimeMs", void 0);
476
- _decorate([property({
433
+ __decorate([property({
477
434
  type: Boolean,
478
435
  attribute: "use-intrinsic-duration",
479
436
  reflect: true,
@@ -485,6 +442,6 @@ _decorate([property({
485
442
  toAttribute: (value) => value ? "true" : null
486
443
  }
487
444
  })], EFThumbnailStrip.prototype, "useIntrinsicDuration", void 0);
488
- _decorate([state()], EFThumbnailStrip.prototype, "stripWidth", null);
489
- EFThumbnailStrip = _decorate([customElement("ef-thumbnail-strip")], EFThumbnailStrip);
445
+ __decorate([state()], EFThumbnailStrip.prototype, "stripWidth", null);
446
+ EFThumbnailStrip = __decorate([customElement("ef-thumbnail-strip")], EFThumbnailStrip);
490
447
  export { EFThumbnailStrip };
@@ -1,5 +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
3
  import { isContextMixin } from "../gui/ContextMixin.js";
4
+ import { isTracingEnabled, withSpan } from "../otel/tracingHelpers.js";
3
5
  import { durationConverter } from "./durationConverter.js";
4
6
  import { EFTemporal, deepGetElementsWithFrameTasks, flushStartTimeMsCache, resetTemporalCache, shallowGetTemporalElements, timegroupContext } from "./EFTemporal.js";
5
7
  import { deepGetMediaElements } from "./EFMedia.js";
@@ -10,10 +12,9 @@ import { Task, TaskStatus } from "@lit/task";
10
12
  import debug from "debug";
11
13
  import { LitElement, css, html } from "lit";
12
14
  import { customElement, property } from "lit/decorators.js";
13
- import _decorate from "@oxc-project/runtime/helpers/decorate";
14
15
  var _EFTimegroup;
15
- const log = debug("ef:elements:EFTimegroup");
16
- let sequenceDurationCache = /* @__PURE__ */ new WeakMap();
16
+ var log = debug("ef:elements:EFTimegroup");
17
+ var sequenceDurationCache = /* @__PURE__ */ new WeakMap();
17
18
  const flushSequenceDurationCache = () => {
18
19
  sequenceDurationCache = /* @__PURE__ */ new WeakMap();
19
20
  };
@@ -22,7 +23,7 @@ const shallowGetTimegroups = (element, groups = []) => {
22
23
  else shallowGetTimegroups(child, groups);
23
24
  return groups;
24
25
  };
25
- let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
26
+ var EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
26
27
  static {
27
28
  _EFTimegroup = this;
28
29
  }
@@ -36,11 +37,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
36
37
  this.frameTask = new Task(this, {
37
38
  autoRun: false,
38
39
  args: () => [this.ownCurrentTimeMs, this.currentTimeMs],
39
- task: async ([]) => {
40
- if (this.isRootTimegroup) {
40
+ task: async ([ownCurrentTimeMs, currentTimeMs]) => {
41
+ if (this.isRootTimegroup) await withSpan("timegroup.frameTask", {
42
+ timegroupId: this.id || "unknown",
43
+ ownCurrentTimeMs,
44
+ currentTimeMs
45
+ }, void 0, async () => {
41
46
  await this.waitForFrameTasks();
42
47
  updateAnimations(this);
43
- }
48
+ });
44
49
  }
45
50
  });
46
51
  this.seekTask = new Task(this, {
@@ -49,14 +54,21 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
49
54
  onComplete: () => {},
50
55
  task: async ([targetTime]) => {
51
56
  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;
57
+ return withSpan("timegroup.seekTask", {
58
+ timegroupId: this.id || "unknown",
59
+ targetTime: targetTime ?? 0,
60
+ durationMs: this.durationMs
61
+ }, void 0, async (span) => {
62
+ await this.waitForMediaDurations();
63
+ const newTime = Math.max(0, Math.min(targetTime ?? 0, this.durationMs / 1e3));
64
+ if (isTracingEnabled()) span.setAttribute("newTime", newTime);
65
+ this.#currentTime = newTime;
66
+ this.requestUpdate("currentTime");
67
+ await this.runThrottledFrameTask();
68
+ this.#saveTimeToLocalStorage(this.#currentTime);
69
+ this.#seekInProgress = false;
70
+ return newTime;
71
+ });
60
72
  }
61
73
  });
62
74
  }
@@ -94,9 +106,6 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
94
106
  #frameTaskInProgress = false;
95
107
  #pendingFrameTaskRun = false;
96
108
  #processingPendingFrameTask = false;
97
- /**
98
- * Throttles frameTask execution to ensure only one runs at a time while preserving the last request
99
- */
100
109
  async runThrottledFrameTask() {
101
110
  if (this.#frameTaskInProgress) {
102
111
  this.#pendingFrameTaskRun = true;
@@ -154,15 +163,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
154
163
  get currentTimeMs() {
155
164
  return this.currentTime * 1e3;
156
165
  }
157
- /**
158
- * Determines if this is a root timegroup (no parent timegroups)
159
- */
160
166
  get isRootTimegroup() {
161
167
  return !this.parentTimegroup;
162
168
  }
163
- /**
164
- * Saves time to localStorage (extracted for reuse)
165
- */
166
169
  #saveTimeToLocalStorage(time) {
167
170
  try {
168
171
  if (this.id && this.isConnected && !Number.isNaN(time)) localStorage.setItem(this.storageKey, time.toString());
@@ -182,7 +185,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
182
185
  maybeLoadTimeFromLocalStorage() {
183
186
  if (this.id) try {
184
187
  const storedValue = localStorage.getItem(this.storageKey);
185
- if (storedValue === null) return void 0;
188
+ if (storedValue === null) return;
186
189
  return Number.parseFloat(storedValue);
187
190
  } catch (error) {
188
191
  log("Failed to load time from localStorage", error);
@@ -217,17 +220,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
217
220
  }
218
221
  get intrinsicDurationMs() {
219
222
  if (this.hasExplicitDuration) return this.explicitDurationMs;
220
- return void 0;
221
223
  }
222
224
  get hasOwnDuration() {
223
225
  return this.mode === "contain" || this.mode === "sequence" || this.mode === "fixed" && this.hasExplicitDuration;
224
226
  }
225
227
  get durationMs() {
226
228
  switch (this.mode) {
227
- case "fit": {
229
+ case "fit":
228
230
  if (!this.parentTimegroup) return 0;
229
231
  return this.parentTimegroup.durationMs;
230
- }
231
232
  case "fixed": return super.durationMs;
232
233
  case "sequence": {
233
234
  const cachedDuration = sequenceDurationCache.get(this);
@@ -258,18 +259,15 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
258
259
  signal?.throwIfAborted();
259
260
  const temporals = deepGetElementsWithFrameTasks(this);
260
261
  const timelineTimeMs = (this.#pendingSeekTime ?? this.#currentTime ?? 0) * 1e3;
261
- const activeTemporals = temporals.filter((temporal) => {
262
+ const frameTasks = temporals.filter((temporal) => {
262
263
  if (!("startTimeMs" in temporal) || !("endTimeMs" in temporal)) return true;
263
264
  const epsilon = .001;
264
265
  const startTimeMs = temporal.startTimeMs;
265
266
  const endTimeMs = temporal.endTimeMs;
266
267
  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;
268
+ const elementEndsAfterStart = temporal.tagName.toLowerCase() === "ef-timegroup" && !temporal.parentTimegroup ? endTimeMs >= timelineTimeMs : endTimeMs > timelineTimeMs;
270
269
  return elementStartsBeforeEnd && elementEndsAfterStart;
271
- });
272
- const frameTasks = activeTemporals.map((temporal) => temporal.frameTask);
270
+ }).map((temporal) => temporal.frameTask);
273
271
  frameTasks.forEach((task) => {
274
272
  task.run();
275
273
  });
@@ -288,31 +286,45 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
288
286
  }
289
287
  }
290
288
  async waitForFrameTasks() {
291
- const temporalElements = deepGetElementsWithFrameTasks(this);
292
- const visibleElements = temporalElements.filter((element) => {
293
- const animationState = evaluateTemporalStateForAnimation(element);
294
- return animationState.isVisible;
289
+ return await withSpan("timegroup.waitForFrameTasks", {
290
+ timegroupId: this.id || "unknown",
291
+ mode: this.mode
292
+ }, void 0, async (span) => {
293
+ const innerStart = performance.now();
294
+ const temporalElements = deepGetElementsWithFrameTasks(this);
295
+ if (isTracingEnabled()) span.setAttribute("temporalElementsCount", temporalElements.length);
296
+ const visibleElements = temporalElements.filter((element) => {
297
+ return evaluateTemporalStateForAnimation(element).isVisible;
298
+ });
299
+ if (isTracingEnabled()) span.setAttribute("visibleElementsCount", visibleElements.length);
300
+ const promiseStart = performance.now();
301
+ await Promise.all(visibleElements.map((element) => element.frameTask.run()));
302
+ const promiseEnd = performance.now();
303
+ const innerEnd = performance.now();
304
+ if (isTracingEnabled()) {
305
+ span.setAttribute("actualInnerMs", innerEnd - innerStart);
306
+ span.setAttribute("promiseAwaitMs", promiseEnd - promiseStart);
307
+ }
295
308
  });
296
- await Promise.all(visibleElements.map((element) => element.frameTask.run()));
297
309
  }
298
310
  async waitForMediaDurations() {
299
311
  if (!this.mediaDurationsPromise) this.mediaDurationsPromise = this.#waitForMediaDurations();
300
312
  return this.mediaDurationsPromise;
301
313
  }
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
314
  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;
315
+ return withSpan("timegroup.waitForMediaDurations", {
316
+ timegroupId: this.id || "unknown",
317
+ mode: this.mode
318
+ }, void 0, async (span) => {
319
+ await this.updateComplete;
320
+ const mediaElements = deepGetMediaElements(this);
321
+ if (isTracingEnabled()) span.setAttribute("mediaElementsCount", mediaElements.length);
322
+ await Promise.all(mediaElements.map((m) => m.mediaEngineTask.value ? Promise.resolve() : m.mediaEngineTask.run()));
323
+ flushStartTimeMsCache();
324
+ flushSequenceDurationCache();
325
+ this.requestUpdate("currentTime");
326
+ await this.updateComplete;
327
+ });
316
328
  }
317
329
  get childTemporals() {
318
330
  return shallowGetTemporalElements(this);
@@ -325,15 +337,6 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
325
337
  }
326
338
  return null;
327
339
  }
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
340
  shouldWrapWithWorkbench() {
338
341
  return EF_INTERACTIVE && this.closest("ef-timegroup") === this && this.closest("ef-preview") === null && this.closest("ef-workbench") === null && this.closest("test-context") === null;
339
342
  }
@@ -358,8 +361,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
358
361
  if (mediaElement.mute) return;
359
362
  const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
360
363
  const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
361
- const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
362
- if (!mediaOverlaps) return;
364
+ if (!(mediaStartsBeforeEnd && mediaEndsAfterStart)) return;
363
365
  const mediaLocalFromMs = Math.max(0, fromMs - mediaElement.startTimeMs);
364
366
  const mediaLocalToMs = Math.min(mediaElement.endTimeMs - mediaElement.startTimeMs, toMs - mediaElement.startTimeMs);
365
367
  if (mediaLocalFromMs >= mediaLocalToMs) return;
@@ -390,27 +392,30 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
390
392
  }));
391
393
  }
392
394
  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
- */
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
+ });
418
+ }
414
419
  async testPlayAudio(fromMs, toMs) {
415
420
  const renderedBuffer = await this.renderAudio(fromMs, toMs);
416
421
  const playbackContext = new AudioContext();
@@ -441,20 +446,20 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
441
446
  });
442
447
  }
443
448
  };
444
- _decorate([provide({ context: timegroupContext })], EFTimegroup.prototype, "_timeGroupContext", void 0);
445
- _decorate([property({
449
+ __decorate([provide({ context: timegroupContext })], EFTimegroup.prototype, "_timeGroupContext", void 0);
450
+ __decorate([property({
446
451
  type: String,
447
452
  attribute: "mode"
448
453
  })], EFTimegroup.prototype, "mode", null);
449
- _decorate([property({
454
+ __decorate([property({
450
455
  type: Number,
451
456
  converter: durationConverter,
452
457
  attribute: "overlap"
453
458
  })], EFTimegroup.prototype, "overlapMs", null);
454
- _decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
455
- _decorate([property({
459
+ __decorate([property({ type: String })], EFTimegroup.prototype, "fit", void 0);
460
+ __decorate([property({
456
461
  type: Number,
457
462
  attribute: "currenttime"
458
463
  })], EFTimegroup.prototype, "currentTime", null);
459
- EFTimegroup = _EFTimegroup = _decorate([customElement("ef-timegroup")], EFTimegroup);
464
+ EFTimegroup = _EFTimegroup = __decorate([customElement("ef-timegroup")], EFTimegroup);
460
465
  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
  */