@editframe/elements 0.33.0-beta → 0.34.6-beta

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 (251) hide show
  1. package/dist/EF_FRAMEGEN.js +5 -3
  2. package/dist/EF_FRAMEGEN.js.map +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.94.0 → _@oxc-project_runtime@0.95.0}/helpers/decorate.js +1 -1
  4. package/dist/canvas/EFCanvas.d.ts +7 -4
  5. package/dist/canvas/EFCanvas.js +1 -1
  6. package/dist/canvas/EFCanvasItem.d.ts +4 -4
  7. package/dist/canvas/EFCanvasItem.js +1 -1
  8. package/dist/canvas/overlays/SelectionOverlay.d.ts +95 -0
  9. package/dist/canvas/overlays/SelectionOverlay.js +1 -1
  10. package/dist/canvas/selection/SelectionController.js +7 -11
  11. package/dist/canvas/selection/SelectionController.js.map +1 -1
  12. package/dist/elements/EFAudio.d.ts +25 -7
  13. package/dist/elements/EFAudio.js +31 -61
  14. package/dist/elements/EFAudio.js.map +1 -1
  15. package/dist/elements/EFCaptions.d.ts +65 -52
  16. package/dist/elements/EFCaptions.js +186 -400
  17. package/dist/elements/EFCaptions.js.map +1 -1
  18. package/dist/elements/EFImage.d.ts +34 -6
  19. package/dist/elements/EFImage.js +114 -79
  20. package/dist/elements/EFImage.js.map +1 -1
  21. package/dist/elements/EFMedia/AssetIdMediaEngine.js +17 -17
  22. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
  23. package/dist/elements/EFMedia/AssetMediaEngine.js +41 -25
  24. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  25. package/dist/elements/EFMedia/BaseMediaEngine.js +4 -4
  26. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  27. package/dist/elements/EFMedia/BufferedSeekingInput.js +1 -1
  28. package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
  29. package/dist/elements/EFMedia/JitMediaEngine.js +31 -17
  30. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  31. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
  32. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  33. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +17 -9
  34. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  35. package/dist/elements/EFMedia.d.ts +66 -20
  36. package/dist/elements/EFMedia.js +412 -30
  37. package/dist/elements/EFMedia.js.map +1 -1
  38. package/dist/elements/EFPanZoom.d.ts +4 -4
  39. package/dist/elements/EFPanZoom.js +1 -1
  40. package/dist/elements/EFSourceMixin.js +43 -15
  41. package/dist/elements/EFSourceMixin.js.map +1 -1
  42. package/dist/elements/EFSurface.d.ts +23 -10
  43. package/dist/elements/EFSurface.js +64 -22
  44. package/dist/elements/EFSurface.js.map +1 -1
  45. package/dist/elements/EFTemporal.d.ts +8 -2
  46. package/dist/elements/EFTemporal.js +42 -31
  47. package/dist/elements/EFTemporal.js.map +1 -1
  48. package/dist/elements/EFText.d.ts +5 -4
  49. package/dist/elements/EFText.js +11 -2
  50. package/dist/elements/EFText.js.map +1 -1
  51. package/dist/elements/EFTextSegment.d.ts +4 -4
  52. package/dist/elements/EFTextSegment.js +1 -1
  53. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  54. package/dist/elements/EFThumbnailStrip.js +1 -1
  55. package/dist/elements/EFTimegroup.d.ts +22 -8
  56. package/dist/elements/EFTimegroup.js +203 -115
  57. package/dist/elements/EFTimegroup.js.map +1 -1
  58. package/dist/elements/EFVideo.d.ts +57 -20
  59. package/dist/elements/EFVideo.js +324 -72
  60. package/dist/elements/EFVideo.js.map +1 -1
  61. package/dist/elements/EFWaveform.d.ts +33 -7
  62. package/dist/elements/EFWaveform.js +103 -59
  63. package/dist/elements/EFWaveform.js.map +1 -1
  64. package/dist/elements/renderTemporalAudio.js +14 -3
  65. package/dist/elements/renderTemporalAudio.js.map +1 -1
  66. package/dist/getRenderInfo.d.ts +2 -2
  67. package/dist/gui/ContextMixin.js +1 -1
  68. package/dist/gui/Controllable.d.ts +2 -0
  69. package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
  70. package/dist/gui/EFActiveRootTemporal.js +1 -1
  71. package/dist/gui/EFConfiguration.d.ts +4 -4
  72. package/dist/gui/EFConfiguration.js +1 -1
  73. package/dist/gui/EFControls.d.ts +2 -2
  74. package/dist/gui/EFControls.js +1 -1
  75. package/dist/gui/EFDial.d.ts +4 -4
  76. package/dist/gui/EFDial.js +1 -1
  77. package/dist/gui/EFFilmstrip.d.ts +3 -2
  78. package/dist/gui/EFFilmstrip.js +1 -1
  79. package/dist/gui/EFFitScale.js +1 -1
  80. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  81. package/dist/gui/EFFocusOverlay.js +1 -1
  82. package/dist/gui/EFOverlayItem.d.ts +4 -4
  83. package/dist/gui/EFOverlayItem.js +1 -1
  84. package/dist/gui/EFOverlayLayer.d.ts +4 -4
  85. package/dist/gui/EFOverlayLayer.js +1 -1
  86. package/dist/gui/EFPause.d.ts +4 -4
  87. package/dist/gui/EFPause.js +1 -1
  88. package/dist/gui/EFPlay.d.ts +4 -4
  89. package/dist/gui/EFPlay.js +1 -1
  90. package/dist/gui/EFPreview.d.ts +4 -4
  91. package/dist/gui/EFPreview.js +1 -1
  92. package/dist/gui/EFResizableBox.d.ts +4 -4
  93. package/dist/gui/EFResizableBox.js +1 -1
  94. package/dist/gui/EFScrubber.d.ts +4 -4
  95. package/dist/gui/EFScrubber.js +1 -1
  96. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  97. package/dist/gui/EFTimeDisplay.js +1 -1
  98. package/dist/gui/EFTimelineRuler.d.ts +4 -4
  99. package/dist/gui/EFTimelineRuler.js +1 -1
  100. package/dist/gui/EFToggleLoop.d.ts +4 -4
  101. package/dist/gui/EFToggleLoop.js +1 -1
  102. package/dist/gui/EFTogglePlay.d.ts +4 -4
  103. package/dist/gui/EFTogglePlay.js +1 -1
  104. package/dist/gui/EFTransformHandles.d.ts +4 -4
  105. package/dist/gui/EFTransformHandles.js +1 -1
  106. package/dist/gui/EFWorkbench.d.ts +5 -4
  107. package/dist/gui/EFWorkbench.js +1 -1
  108. package/dist/gui/PlaybackController.d.ts +10 -2
  109. package/dist/gui/PlaybackController.js +52 -30
  110. package/dist/gui/PlaybackController.js.map +1 -1
  111. package/dist/gui/TWMixin.js +1 -1
  112. package/dist/gui/TWMixin.js.map +1 -1
  113. package/dist/gui/TargetOrContextMixin.js +1 -1
  114. package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
  115. package/dist/gui/hierarchy/EFHierarchy.js +1 -1
  116. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
  117. package/dist/gui/hierarchy/EFHierarchyItem.js +1 -1
  118. package/dist/gui/timeline/EFTimeline.d.ts +6 -2
  119. package/dist/gui/timeline/EFTimeline.js +1 -1
  120. package/dist/gui/timeline/EFTimelineRow.d.ts +57 -0
  121. package/dist/gui/timeline/EFTimelineRow.js +1 -1
  122. package/dist/gui/timeline/TrimHandles.d.ts +4 -4
  123. package/dist/gui/timeline/TrimHandles.js +1 -1
  124. package/dist/gui/timeline/tracks/AudioTrack.d.ts +2 -0
  125. package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
  126. package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +58 -0
  127. package/dist/gui/timeline/tracks/CaptionsTrack.js +1 -1
  128. package/dist/gui/timeline/tracks/HTMLTrack.d.ts +13 -0
  129. package/dist/gui/timeline/tracks/HTMLTrack.js +1 -1
  130. package/dist/gui/timeline/tracks/ImageTrack.d.ts +14 -0
  131. package/dist/gui/timeline/tracks/ImageTrack.js +1 -1
  132. package/dist/gui/timeline/tracks/TextTrack.d.ts +26 -0
  133. package/dist/gui/timeline/tracks/TextTrack.js +1 -1
  134. package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +47 -0
  135. package/dist/gui/timeline/tracks/TimegroupTrack.js +4 -12
  136. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
  137. package/dist/gui/timeline/tracks/TrackItem.d.ts +81 -0
  138. package/dist/gui/timeline/tracks/TrackItem.js +1 -1
  139. package/dist/gui/timeline/tracks/VideoTrack.d.ts +25 -0
  140. package/dist/gui/timeline/tracks/VideoTrack.js +1 -1
  141. package/dist/gui/timeline/tracks/WaveformTrack.d.ts +14 -0
  142. package/dist/gui/timeline/tracks/WaveformTrack.js +1 -1
  143. package/dist/gui/timeline/tracks/ensureTrackItemInit.d.ts +1 -0
  144. package/dist/gui/timeline/tracks/preloadTracks.d.ts +9 -0
  145. package/dist/gui/tree/EFTree.d.ts +5 -4
  146. package/dist/gui/tree/EFTree.js +1 -1
  147. package/dist/gui/tree/EFTreeItem.d.ts +4 -4
  148. package/dist/gui/tree/EFTreeItem.js +1 -1
  149. package/dist/index.d.ts +4 -1
  150. package/dist/preview/AdaptiveResolutionTracker.js +6 -14
  151. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
  152. package/dist/preview/FrameController.d.ts +123 -0
  153. package/dist/preview/FrameController.js +216 -0
  154. package/dist/preview/FrameController.js.map +1 -0
  155. package/dist/preview/RenderContext.d.ts +1 -0
  156. package/dist/preview/RenderContext.js +193 -0
  157. package/dist/preview/RenderContext.js.map +1 -0
  158. package/dist/preview/encoding/canvasEncoder.js +166 -0
  159. package/dist/preview/encoding/canvasEncoder.js.map +1 -0
  160. package/dist/preview/encoding/mainThreadEncoder.js +39 -0
  161. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -0
  162. package/dist/preview/encoding/types.d.ts +1 -0
  163. package/dist/preview/encoding/workerEncoder.js +58 -0
  164. package/dist/preview/encoding/workerEncoder.js.map +1 -0
  165. package/dist/preview/logger.js +41 -0
  166. package/dist/preview/logger.js.map +1 -0
  167. package/dist/preview/previewTypes.js +11 -10
  168. package/dist/preview/previewTypes.js.map +1 -1
  169. package/dist/preview/renderTimegroupPreview.js +259 -236
  170. package/dist/preview/renderTimegroupPreview.js.map +1 -1
  171. package/dist/preview/renderTimegroupToCanvas.d.ts +5 -0
  172. package/dist/preview/renderTimegroupToCanvas.js +100 -489
  173. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  174. package/dist/preview/renderTimegroupToVideo.d.ts +1 -0
  175. package/dist/preview/renderTimegroupToVideo.js +80 -22
  176. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  177. package/dist/preview/renderers.js.map +1 -1
  178. package/dist/preview/rendering/inlineImages.js +56 -0
  179. package/dist/preview/rendering/inlineImages.js.map +1 -0
  180. package/dist/preview/rendering/renderToImage.d.ts +1 -0
  181. package/dist/preview/rendering/renderToImage.js +120 -0
  182. package/dist/preview/rendering/renderToImage.js.map +1 -0
  183. package/dist/preview/rendering/renderToImageForeignObject.js +135 -0
  184. package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -0
  185. package/dist/preview/rendering/renderToImageNative.d.ts +1 -0
  186. package/dist/preview/rendering/renderToImageNative.js +129 -0
  187. package/dist/preview/rendering/renderToImageNative.js.map +1 -0
  188. package/dist/preview/rendering/svgSerializer.js +43 -0
  189. package/dist/preview/rendering/svgSerializer.js.map +1 -0
  190. package/dist/preview/rendering/types.d.ts +2 -0
  191. package/dist/preview/statsTrackingStrategy.js +3 -1
  192. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  193. package/dist/preview/workers/WorkerPool.js +8 -57
  194. package/dist/preview/workers/WorkerPool.js.map +1 -1
  195. package/dist/render/EFRenderAPI.d.ts +35 -0
  196. package/dist/render/EFRenderAPI.js +1 -0
  197. package/dist/render/EFRenderAPI.js.map +1 -1
  198. package/dist/sandbox/PlaybackControls.d.ts +1 -0
  199. package/dist/sandbox/ScenarioRunner.d.ts +1 -0
  200. package/dist/sandbox/defineSandbox.d.ts +1 -0
  201. package/dist/sandbox/index.d.ts +3 -0
  202. package/dist/style.css +3 -0
  203. package/dist/transcoding/types/index.d.ts +6 -3
  204. package/package.json +2 -3
  205. package/test/EFVideo.framegen.browsertest.ts +8 -1
  206. package/test/profilingPlugin.ts +1 -3
  207. package/test/setup.ts +23 -1
  208. package/dist/EF_INTERACTIVE.js +0 -7
  209. package/dist/EF_INTERACTIVE.js.map +0 -1
  210. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +0 -50
  211. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +0 -12
  212. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +0 -104
  213. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +0 -1
  214. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -168
  215. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +0 -1
  216. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +0 -46
  217. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +0 -1
  218. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +0 -49
  219. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +0 -1
  220. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +0 -30
  221. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +0 -1
  222. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +0 -49
  223. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +0 -1
  224. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +0 -47
  225. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +0 -1
  226. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -140
  227. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +0 -1
  228. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +0 -13
  229. package/dist/elements/EFMedia/shared/BufferUtils.js +0 -86
  230. package/dist/elements/EFMedia/shared/BufferUtils.js.map +0 -1
  231. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +0 -17
  232. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +0 -90
  233. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +0 -1
  234. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +0 -80
  235. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +0 -1
  236. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -49
  237. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +0 -1
  238. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +0 -58
  239. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +0 -1
  240. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +0 -71
  241. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +0 -1
  242. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -52
  243. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +0 -1
  244. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +0 -50
  245. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +0 -1
  246. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +0 -109
  247. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +0 -1
  248. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +0 -12
  249. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +0 -97
  250. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +0 -1
  251. package/dist/elements/SampleBuffer.d.ts +0 -19
@@ -1,19 +1,10 @@
1
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
1
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
2
2
  import { EFTemporal } from "./EFTemporal.js";
3
3
  import { efContext } from "../gui/efContext.js";
4
4
  import { isContextMixin } from "../gui/ContextMixin.js";
5
5
  import { withSpan } from "../otel/tracingHelpers.js";
6
6
  import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
7
- import { makeMediaEngineTask } from "./EFMedia/tasks/makeMediaEngineTask.js";
8
- import { makeAudioBufferTask } from "./EFMedia/audioTasks/makeAudioBufferTask.js";
9
- import { makeAudioFrequencyAnalysisTask } from "./EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js";
10
- import { makeAudioInitSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js";
11
- import { makeAudioInputTask } from "./EFMedia/audioTasks/makeAudioInputTask.js";
12
- import { makeAudioSeekTask } from "./EFMedia/audioTasks/makeAudioSeekTask.js";
13
- import { makeAudioSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioSegmentFetchTask.js";
14
- import { makeAudioSegmentIdTask } from "./EFMedia/audioTasks/makeAudioSegmentIdTask.js";
15
- import { makeAudioTimeDomainAnalysisTask } from "./EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js";
16
- import { fetchAudioSpanningTime } from "./EFMedia/shared/AudioSpanUtils.js";
7
+ import { LRUCache } from "../utils/LRUCache.js";
17
8
  import { EFSourceMixin } from "./EFSourceMixin.js";
18
9
  import { FetchMixin } from "./FetchMixin.js";
19
10
  import { renderTemporalAudio } from "./renderTemporalAudio.js";
@@ -24,12 +15,132 @@ import { property, state } from "lit/decorators.js";
24
15
 
25
16
  //#region src/elements/EFMedia.ts
26
17
  const freqWeightsCache = /* @__PURE__ */ new Map();
27
- var IgnorableError = class extends Error {};
18
+ /**
19
+ * Gets all child elements including slotted content for shadow DOM elements.
20
+ * Duplicated here to avoid circular imports from EFTemporal.
21
+ */
22
+ const getChildrenIncludingSlotted = (element) => {
23
+ if (element.shadowRoot) {
24
+ const slots = element.shadowRoot.querySelectorAll("slot");
25
+ if (slots.length > 0) {
26
+ const assignedElements = [];
27
+ for (const slot of slots) assignedElements.push(...slot.assignedElements());
28
+ for (const child of element.shadowRoot.children) if (child.tagName !== "SLOT") assignedElements.push(child);
29
+ return assignedElements;
30
+ }
31
+ }
32
+ return Array.from(element.children);
33
+ };
28
34
  const deepGetMediaElements = (element, medias = []) => {
29
- for (const child of Array.from(element.children)) if (child instanceof EFMedia) medias.push(child);
35
+ const children = getChildrenIncludingSlotted(element);
36
+ for (const child of children) if (child instanceof EFMedia) medias.push(child);
30
37
  else deepGetMediaElements(child, medias);
31
38
  return medias;
32
39
  };
40
+ /**
41
+ * Simple async value wrapper that mimics Lit Task interface.
42
+ * Used for backwards compatibility with code expecting task-like objects.
43
+ */
44
+ var AsyncValue = class {
45
+ #value = void 0;
46
+ #error = void 0;
47
+ #status = "initial";
48
+ #promise = Promise.resolve(void 0);
49
+ #resolvePromise;
50
+ get value() {
51
+ return this.#value;
52
+ }
53
+ get error() {
54
+ return this.#error;
55
+ }
56
+ get status() {
57
+ switch (this.#status) {
58
+ case "initial": return 0;
59
+ case "pending": return 1;
60
+ case "complete": return 2;
61
+ case "error": return 3;
62
+ }
63
+ }
64
+ get taskComplete() {
65
+ return this.#promise;
66
+ }
67
+ /**
68
+ * Set the value (marks status as complete)
69
+ */
70
+ setValue(value) {
71
+ this.#value = value;
72
+ this.#error = void 0;
73
+ this.#status = "complete";
74
+ this.#resolvePromise?.(value);
75
+ }
76
+ /**
77
+ * Set an error (marks status as error)
78
+ */
79
+ setError(error) {
80
+ this.#error = error;
81
+ this.#value = void 0;
82
+ this.#status = "error";
83
+ this.#resolvePromise?.(void 0);
84
+ }
85
+ /**
86
+ * Start a new async operation
87
+ */
88
+ startPending() {
89
+ this.#status = "pending";
90
+ this.#promise = new Promise((resolve) => {
91
+ this.#resolvePromise = resolve;
92
+ });
93
+ this.#promise.catch(() => {});
94
+ }
95
+ /**
96
+ * Run an async function and update status accordingly
97
+ */
98
+ async run(fn) {
99
+ this.startPending();
100
+ try {
101
+ const result = await fn();
102
+ this.setValue(result);
103
+ return result;
104
+ } catch (error) {
105
+ if (error instanceof Error) this.setError(error);
106
+ else this.setError(new Error(String(error)));
107
+ return;
108
+ }
109
+ }
110
+ };
111
+ const DECAY_WEIGHT = .8;
112
+ function processFFTData(fftData, zeroThresholdPercent = .1) {
113
+ const totalBins = fftData.length;
114
+ const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
115
+ let zeroCount = 0;
116
+ let cutoffIndex = totalBins;
117
+ for (let i = totalBins - 1; i >= 0; i--) if (fftData[i] ?? true) zeroCount++;
118
+ else if (zeroCount >= zeroThresholdCount) {
119
+ cutoffIndex = i + 1;
120
+ break;
121
+ }
122
+ if (cutoffIndex < zeroThresholdCount) return fftData;
123
+ const resampledData = interpolateData(fftData.slice(0, cutoffIndex), fftData.length);
124
+ const attenuationStartIndex = Math.floor(totalBins * .9);
125
+ for (let i = attenuationStartIndex; i < totalBins; i++) {
126
+ const attenuationProgress = (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + .2;
127
+ const attenuationFactor = Math.max(0, 1 - attenuationProgress);
128
+ resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);
129
+ }
130
+ return resampledData;
131
+ }
132
+ function interpolateData(data, targetSize) {
133
+ const resampled = new Uint8Array(targetSize);
134
+ const dataLength = data.length;
135
+ for (let i = 0; i < targetSize; i++) {
136
+ const ratio = i / (targetSize - 1) * (dataLength - 1);
137
+ const index = Math.floor(ratio);
138
+ const fraction = ratio - index;
139
+ if (index >= dataLength - 1) resampled[i] = data[dataLength - 1] ?? 0;
140
+ else resampled[i] = Math.round((data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction);
141
+ }
142
+ return resampled;
143
+ }
33
144
  var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(LitElement)), { assetType: "isobmff_files" })) {
34
145
  constructor(..._args) {
35
146
  super(..._args);
@@ -41,15 +152,15 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
41
152
  this.fftDecay = 8;
42
153
  this.fftGain = 3;
43
154
  this.interpolateFrequencies = false;
44
- this.mediaEngineTask = makeMediaEngineTask(this);
45
- this.audioSegmentIdTask = makeAudioSegmentIdTask(this);
46
- this.audioInitSegmentFetchTask = makeAudioInitSegmentFetchTask(this);
47
- this.audioSegmentFetchTask = makeAudioSegmentFetchTask(this);
48
- this.audioInputTask = makeAudioInputTask(this);
49
- this.audioSeekTask = makeAudioSeekTask(this);
50
- this.audioBufferTask = makeAudioBufferTask(this);
51
- this.byteTimeDomainTask = makeAudioTimeDomainAnalysisTask(this);
52
- this.frequencyDataTask = makeAudioFrequencyAnalysisTask(this);
155
+ this.mediaEngineTask = new AsyncValue();
156
+ this.frequencyDataTask = new AsyncValue();
157
+ this.byteTimeDomainTask = new AsyncValue();
158
+ this.audioSegmentIdTask = new AsyncValue();
159
+ this.audioInitSegmentFetchTask = new AsyncValue();
160
+ this.audioSegmentFetchTask = new AsyncValue();
161
+ this.audioInputTask = new AsyncValue();
162
+ this.audioSeekTask = new AsyncValue();
163
+ this.audioBufferTask = new AsyncValue();
53
164
  this.assetId = null;
54
165
  this._desiredSeekTimeMs = 0;
55
166
  }
@@ -100,7 +211,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
100
211
  }
101
212
  `];
102
213
  }
103
- get FREQ_WEIGHTS() {
214
+ getFreqWeights() {
104
215
  if (freqWeightsCache.has(this.fftSize)) return freqWeightsCache.get(this.fftSize);
105
216
  const weights = new Float32Array(this.fftSize / 2).map((_, i) => {
106
217
  const frequency = i * 48e3 / this.fftSize;
@@ -115,17 +226,287 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
115
226
  freqWeightsCache.set(this.fftSize, weights);
116
227
  return weights;
117
228
  }
118
- get shouldInterpolateFrequencies() {
229
+ getShouldInterpolateFrequencies() {
119
230
  return this.interpolateFrequencies;
120
231
  }
121
- get urlGenerator() {
232
+ getUrlGenerator() {
122
233
  return new UrlGenerator(() => this.apiHost ?? "");
123
234
  }
235
+ #mediaEngine = void 0;
236
+ #mediaEnginePromise = void 0;
237
+ #mediaEngineError = void 0;
238
+ #mediaEngineSrcKey = null;
239
+ /**
240
+ * Get or create the MediaEngine for this element.
241
+ * Uses caching based on src/assetId to avoid redundant fetches.
242
+ */
243
+ async getMediaEngine(signal) {
244
+ const srcKey = `${this.src}|${this.assetId}`;
245
+ if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine) return this.#mediaEngine;
246
+ if (this.#mediaEngineSrcKey === srcKey && this.#mediaEnginePromise) return this.#mediaEnginePromise;
247
+ this.#mediaEngineSrcKey = srcKey;
248
+ this.mediaEngineTask.startPending();
249
+ this.#mediaEnginePromise = this.#createMediaEngine(signal);
250
+ try {
251
+ this.#mediaEngine = await this.#mediaEnginePromise;
252
+ this.#mediaEngineError = void 0;
253
+ if (this.#mediaEngine) {
254
+ this.mediaEngineTask.setValue(this.#mediaEngine);
255
+ this.#handleMediaEngineComplete();
256
+ }
257
+ return this.#mediaEngine;
258
+ } catch (error) {
259
+ this.#mediaEngineError = error instanceof Error ? error : new Error(String(error));
260
+ this.mediaEngineTask.setError(this.#mediaEngineError);
261
+ if (!(error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.message === "No valid media source" || error.message.includes("File not found") || error.message.includes("404") || error.message.includes("Failed to fetch")))) console.error("Media engine error:", error);
262
+ return;
263
+ }
264
+ }
265
+ async #createMediaEngine(signal) {
266
+ const { src, assetId, apiHost, requiredTracks } = this;
267
+ const urlGenerator = this.getUrlGenerator();
268
+ if (assetId !== null && assetId !== void 0 && assetId.trim() !== "") {
269
+ if (!apiHost) throw new Error("API host is required for AssetID mode");
270
+ const { AssetIdMediaEngine } = await import("./EFMedia/AssetIdMediaEngine.js");
271
+ return AssetIdMediaEngine.fetchByAssetId(this, urlGenerator, assetId, apiHost, requiredTracks, signal);
272
+ }
273
+ if (!src || typeof src !== "string" || src.trim() === "") return;
274
+ const lowerSrc = src.toLowerCase();
275
+ const isRemoteUrl = lowerSrc.startsWith("http://") || lowerSrc.startsWith("https://");
276
+ const configuration = this.closest("ef-configuration");
277
+ if (configuration?.mediaEngine === "jit") {
278
+ let manifestSrc = src;
279
+ if (!isRemoteUrl && configuration.apiHost) manifestSrc = `${configuration.apiHost.replace(/\/$/, "")}${src.replace(/^\.\//, "/src/")}`;
280
+ const url$1 = urlGenerator.generateManifestUrl(manifestSrc);
281
+ const { JitMediaEngine: JitMediaEngine$1 } = await import("./EFMedia/JitMediaEngine.js");
282
+ return JitMediaEngine$1.fetch(this, urlGenerator, url$1, signal);
283
+ }
284
+ if (configuration?.mediaEngine === "local") {
285
+ const { AssetMediaEngine } = await import("./EFMedia/AssetMediaEngine.js");
286
+ return AssetMediaEngine.fetch(this, urlGenerator, src, requiredTracks, signal);
287
+ }
288
+ if (!isRemoteUrl) {
289
+ const { AssetMediaEngine } = await import("./EFMedia/AssetMediaEngine.js");
290
+ return AssetMediaEngine.fetch(this, urlGenerator, src, requiredTracks, signal);
291
+ }
292
+ const url = urlGenerator.generateManifestUrl(src);
293
+ const { JitMediaEngine } = await import("./EFMedia/JitMediaEngine.js");
294
+ return JitMediaEngine.fetch(this, urlGenerator, url, signal);
295
+ }
296
+ #handleMediaEngineComplete() {
297
+ this.requestUpdate("intrinsicDurationMs");
298
+ this.requestUpdate("ownCurrentTimeMs");
299
+ if (this.rootTimegroup) queueMicrotask(() => {
300
+ this.rootTimegroup?.requestUpdate("ownCurrentTimeMs");
301
+ this.rootTimegroup?.requestUpdate("durationMs");
302
+ });
303
+ }
304
+ #frequencyDataCache = new LRUCache(100);
305
+ #timeDomainDataCache = new LRUCache(100);
306
+ /**
307
+ * Get frequency data for audio visualization at a given time.
308
+ */
309
+ async getFrequencyData(timeMs, signal) {
310
+ if (timeMs < 0) return null;
311
+ const cacheKey = `${this.getShouldInterpolateFrequencies()}:${this.fftSize}:${this.fftDecay}:${this.fftGain}:${timeMs}`;
312
+ const cached = this.#frequencyDataCache.get(cacheKey);
313
+ if (cached) return cached;
314
+ try {
315
+ const result = await this.#analyzeFrequencies(timeMs, signal);
316
+ if (result) {
317
+ this.#frequencyDataCache.set(cacheKey, result);
318
+ this.frequencyDataTask.setValue(result);
319
+ }
320
+ return result;
321
+ } catch (error) {
322
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
323
+ return null;
324
+ }
325
+ }
326
+ /**
327
+ * Get time domain data for audio visualization at a given time.
328
+ */
329
+ async getTimeDomainData(timeMs, signal) {
330
+ if (timeMs < 0) return null;
331
+ const cacheKey = `${this.fftSize}:${timeMs}`;
332
+ const cached = this.#timeDomainDataCache.get(cacheKey);
333
+ if (cached) return cached;
334
+ try {
335
+ const result = await this.#analyzeTimeDomain(timeMs, signal);
336
+ if (result) {
337
+ this.#timeDomainDataCache.set(cacheKey, result);
338
+ this.byteTimeDomainTask.setValue(result);
339
+ }
340
+ return result;
341
+ } catch (error) {
342
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
343
+ return null;
344
+ }
345
+ }
346
+ async #analyzeFrequencies(currentTimeMs, signal) {
347
+ const mediaEngine = await this.getMediaEngine(signal);
348
+ signal?.throwIfAborted();
349
+ if (!mediaEngine?.audioRendition) return null;
350
+ const frameIntervalMs = 1e3 / 30;
351
+ const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;
352
+ const fromMs = Math.max(0, earliestFrameMs);
353
+ const maxToMs = currentTimeMs + frameIntervalMs;
354
+ const videoDurationMs = this.intrinsicDurationMs || 0;
355
+ const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;
356
+ if (fromMs >= toMs) return null;
357
+ const { fetchAudioSpanningTime: fetchAudioSpan } = await import("./EFMedia/shared/AudioSpanUtils.js");
358
+ let audioSpan;
359
+ try {
360
+ audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal);
361
+ } catch (error) {
362
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
363
+ return null;
364
+ }
365
+ if (!audioSpan?.blob || audioSpan.blob.size < 100) return null;
366
+ const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
367
+ const arrayBuffer = await audioSpan.blob.arrayBuffer();
368
+ signal?.throwIfAborted();
369
+ let audioBuffer;
370
+ try {
371
+ audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
372
+ signal?.throwIfAborted();
373
+ } catch {
374
+ return null;
375
+ }
376
+ const startOffsetMs = audioSpan.startMs;
377
+ const framesData = await Promise.all(Array.from({ length: this.fftDecay }, async (_, i) => {
378
+ const frameOffset = i * (1e3 / 30);
379
+ const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
380
+ const audioContext = new OfflineAudioContext(2, 48e3 / 30, 48e3);
381
+ const analyser = audioContext.createAnalyser();
382
+ analyser.fftSize = this.fftSize;
383
+ analyser.minDecibels = -90;
384
+ analyser.maxDecibels = -10;
385
+ const gainNode = audioContext.createGain();
386
+ gainNode.gain.value = this.fftGain;
387
+ const filter = audioContext.createBiquadFilter();
388
+ filter.type = "bandpass";
389
+ filter.frequency.value = 15e3;
390
+ filter.Q.value = .05;
391
+ const audioBufferSource = audioContext.createBufferSource();
392
+ audioBufferSource.buffer = audioBuffer;
393
+ audioBufferSource.connect(filter);
394
+ filter.connect(gainNode);
395
+ gainNode.connect(analyser);
396
+ analyser.connect(audioContext.destination);
397
+ audioBufferSource.start(0, startTime, 1 / 30);
398
+ try {
399
+ await audioContext.startRendering();
400
+ signal?.throwIfAborted();
401
+ const frameData = new Uint8Array(this.fftSize / 2);
402
+ analyser.getByteFrequencyData(frameData);
403
+ return frameData;
404
+ } finally {
405
+ audioBufferSource.disconnect();
406
+ analyser.disconnect();
407
+ }
408
+ }));
409
+ const frameLength = framesData[0]?.length ?? 0;
410
+ const smoothedData = new Uint8Array(frameLength);
411
+ for (let i = 0; i < frameLength; i++) {
412
+ let weightedSum = 0;
413
+ let weightSum = 0;
414
+ framesData.forEach((frame, frameIndex) => {
415
+ const decayWeight = DECAY_WEIGHT ** frameIndex;
416
+ weightedSum += (frame[i] ?? 0) * decayWeight;
417
+ weightSum += decayWeight;
418
+ });
419
+ smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
420
+ }
421
+ smoothedData.forEach((value, i) => {
422
+ const freqWeight = this.getFreqWeights()[i] ?? 0;
423
+ smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
424
+ });
425
+ const slicedData = smoothedData.slice(0, Math.floor(smoothedData.length / 2));
426
+ return this.getShouldInterpolateFrequencies() ? processFFTData(slicedData) : slicedData;
427
+ }
428
+ async #analyzeTimeDomain(currentTimeMs, signal) {
429
+ const mediaEngine = await this.getMediaEngine(signal);
430
+ signal?.throwIfAborted();
431
+ if (!mediaEngine?.audioRendition) return null;
432
+ const frameIntervalMs = 1e3 / 30;
433
+ const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;
434
+ const fromMs = Math.max(0, earliestFrameMs);
435
+ const maxToMs = currentTimeMs + frameIntervalMs;
436
+ const videoDurationMs = this.intrinsicDurationMs || 0;
437
+ const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;
438
+ if (fromMs >= toMs) return null;
439
+ const { fetchAudioSpanningTime: fetchAudioSpan } = await import("./EFMedia/shared/AudioSpanUtils.js");
440
+ let audioSpan;
441
+ try {
442
+ audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal);
443
+ } catch (error) {
444
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
445
+ return null;
446
+ }
447
+ if (!audioSpan?.blob || audioSpan.blob.size < 100) return null;
448
+ const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
449
+ const arrayBuffer = await audioSpan.blob.arrayBuffer();
450
+ signal?.throwIfAborted();
451
+ let audioBuffer;
452
+ try {
453
+ audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
454
+ signal?.throwIfAborted();
455
+ } catch {
456
+ return null;
457
+ }
458
+ const startOffsetMs = audioSpan.startMs;
459
+ const framesData = await Promise.all(Array.from({ length: this.fftDecay }, async (_, i) => {
460
+ const frameOffset = i * (1e3 / 30);
461
+ const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
462
+ const audioContext = new OfflineAudioContext(2, 48e3 / 30, 48e3);
463
+ const analyser = audioContext.createAnalyser();
464
+ analyser.fftSize = this.fftSize;
465
+ analyser.minDecibels = -90;
466
+ analyser.maxDecibels = -10;
467
+ const gainNode = audioContext.createGain();
468
+ gainNode.gain.value = this.fftGain;
469
+ const filter = audioContext.createBiquadFilter();
470
+ filter.type = "bandpass";
471
+ filter.frequency.value = 15e3;
472
+ filter.Q.value = .05;
473
+ const audioBufferSource = audioContext.createBufferSource();
474
+ audioBufferSource.buffer = audioBuffer;
475
+ audioBufferSource.connect(filter);
476
+ filter.connect(gainNode);
477
+ gainNode.connect(analyser);
478
+ analyser.connect(audioContext.destination);
479
+ audioBufferSource.start(0, startTime, 1 / 30);
480
+ try {
481
+ await audioContext.startRendering();
482
+ signal?.throwIfAborted();
483
+ const frameData = new Uint8Array(this.fftSize);
484
+ analyser.getByteTimeDomainData(frameData);
485
+ return frameData;
486
+ } finally {
487
+ audioBufferSource.disconnect();
488
+ analyser.disconnect();
489
+ }
490
+ }));
491
+ const frameLength = framesData[0]?.length ?? 0;
492
+ const smoothedData = new Uint8Array(frameLength);
493
+ for (let i = 0; i < frameLength; i++) {
494
+ let sumSquares = 0;
495
+ framesData.forEach((frame) => {
496
+ const value = (frame[i] ?? 128) - 128;
497
+ sumSquares += value * value;
498
+ });
499
+ const rms = Math.sqrt(sumSquares / framesData.length);
500
+ smoothedData[i] = Math.min(255, Math.max(0, Math.round(rms + 128)));
501
+ }
502
+ return smoothedData;
503
+ }
124
504
  get intrinsicDurationMs() {
125
- return this.mediaEngineTask.value?.durationMs ?? 0;
505
+ return this.#mediaEngine?.durationMs;
126
506
  }
127
507
  updated(changedProperties) {
128
508
  super.updated(changedProperties);
509
+ if (changedProperties.has("src") || changedProperties.has("assetId")) this.getMediaEngine().catch(() => {});
129
510
  const newCurrentSourceTimeMs = this.currentSourceTimeMs;
130
511
  if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) this.executeSeek(newCurrentSourceTimeMs);
131
512
  if (changedProperties.has("ownCurrentTimeMs")) this.executeSeek(this.currentSourceTimeMs);
@@ -159,7 +540,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
159
540
  if (this._desiredSeekTimeMs !== value) this._desiredSeekTimeMs = value;
160
541
  }
161
542
  async executeSeek(seekToMs) {
162
- this.desiredSeekTimeMs = seekToMs;
543
+ this._desiredSeekTimeMs = seekToMs;
163
544
  }
164
545
  /**
165
546
  * Main integration method for EFTimegroup audio playback
@@ -175,6 +556,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
175
556
  durationMs: toMs - fromMs,
176
557
  src: this.src || "none"
177
558
  }, void 0, async () => {
559
+ const { fetchAudioSpanningTime } = await import("./EFMedia/shared/AudioSpanUtils.js");
178
560
  return fetchAudioSpanningTime(this, fromMs, toMs, signal);
179
561
  });
180
562
  }
@@ -183,9 +565,9 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
183
565
  * Ensures media is ready for playback
184
566
  */
185
567
  async waitForMediaDurations(signal) {
186
- if (this.mediaEngineTask.value) return;
568
+ if (this.#mediaEngine) return;
187
569
  try {
188
- await this.mediaEngineTask.taskComplete;
570
+ await this.getMediaEngine(signal);
189
571
  } catch (error) {
190
572
  const isAbortError = error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message.includes("signal is aborted") || error.message.includes("The user aborted a request"));
191
573
  if (signal?.aborted) throw error;
@@ -256,5 +638,5 @@ __decorate([property({
256
638
  __decorate([state()], EFMedia.prototype, "_desiredSeekTimeMs", void 0);
257
639
 
258
640
  //#endregion
259
- export { EFMedia, IgnorableError, deepGetMediaElements };
641
+ export { AsyncValue, EFMedia, deepGetMediaElements };
260
642
  //# sourceMappingURL=EFMedia.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EFMedia.js","names":[],"sources":["../../src/elements/EFMedia.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport type { ControllableInterface } from \"../gui/Controllable.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { withSpan } from \"../otel/tracingHelpers.js\";\nimport type { AudioSpan } from \"../transcoding/types/index.ts\";\nimport { UrlGenerator } from \"../transcoding/utils/UrlGenerator.ts\";\nimport { makeAudioBufferTask } from \"./EFMedia/audioTasks/makeAudioBufferTask.ts\";\nimport { makeAudioFrequencyAnalysisTask } from \"./EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts\";\nimport { makeAudioInitSegmentFetchTask } from \"./EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts\";\nimport { makeAudioInputTask } from \"./EFMedia/audioTasks/makeAudioInputTask.ts\";\nimport { makeAudioSeekTask } from \"./EFMedia/audioTasks/makeAudioSeekTask.ts\";\nimport { makeAudioSegmentFetchTask } from \"./EFMedia/audioTasks/makeAudioSegmentFetchTask.ts\";\nimport { makeAudioSegmentIdTask } from \"./EFMedia/audioTasks/makeAudioSegmentIdTask.ts\";\nimport { makeAudioTimeDomainAnalysisTask } from \"./EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts\";\nimport { fetchAudioSpanningTime } from \"./EFMedia/shared/AudioSpanUtils.ts\";\nimport { makeMediaEngineTask } from \"./EFMedia/tasks/makeMediaEngineTask.ts\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.ts\";\n\n// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts\ndeclare global {\n var EF_FRAMEGEN: import(\"../EF_FRAMEGEN.js\").EFFramegen;\n}\n\nconst freqWeightsCache = new Map<number, Float32Array>();\n\nexport class IgnorableError extends Error {}\n\nexport const deepGetMediaElements = (\n element: Element,\n medias: EFMedia[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFMedia) {\n medias.push(child);\n } else {\n deepGetMediaElements(child, medias);\n }\n }\n return medias;\n};\n\n// Import EFTemporal - use a function wrapper to defer evaluation until class definition\n// This breaks the circular dependency: EFTimegroup -> EFMedia -> EFTemporal\nimport { EFTemporal } from \"./EFTemporal.js\";\n\nexport class EFMedia extends EFTargetable(\n EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {\n assetType: \"isobmff_files\",\n }),\n) {\n @provide({ context: efContext })\n get efContext(): ControllableInterface | null {\n return this.rootTimegroup ?? this;\n }\n\n // Sample buffer size configuration\n static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;\n static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;\n\n /**\n * Which tracks this media element requires.\n * Subclasses can override to specify their needs:\n * - \"audio\" - Only needs audio track (e.g., EFAudio)\n * - \"video\" - Only needs video track\n * - \"both\" - Needs both tracks (default for backwards compatibility)\n * \n * This is used during media engine creation to skip validation\n * of tracks that won't be used, avoiding unnecessary network requests.\n */\n get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"both\";\n }\n\n static get observedAttributes() {\n // biome-ignore lint/complexity/noThisInStatic: We need to access super\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mute\",\n \"fft-size\",\n \"fft-decay\",\n \"fft-gain\",\n \"interpolate-frequencies\",\n \"asset-id\",\n \"audio-buffer-duration\",\n \"max-audio-buffer-fetches\",\n \"enable-audio-buffering\",\n \"sourcein\",\n \"sourceout\",\n ];\n }\n\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n `,\n ];\n\n /**\n * Duration in milliseconds for audio buffering ahead of current time\n * @domAttribute \"audio-buffer-duration\"\n */\n @property({ type: Number, attribute: \"audio-buffer-duration\" })\n audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent audio segment fetches for buffering\n * @domAttribute \"max-audio-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-audio-buffer-fetches\" })\n maxAudioBufferFetches = 2;\n\n /**\n * Enable/disable audio buffering system\n * @domAttribute \"enable-audio-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-audio-buffering\" })\n enableAudioBuffering = true;\n\n /**\n * Mute/unmute the media element\n * @domAttribute \"mute\"\n */\n @property({\n type: Boolean,\n attribute: \"mute\",\n reflect: true,\n })\n mute = false;\n\n /**\n * FFT size for frequency analysis\n * @domAttribute \"fft-size\"\n */\n @property({ type: Number, attribute: \"fft-size\", reflect: true })\n fftSize = 128;\n\n /**\n * FFT decay rate for frequency analysis\n * @domAttribute \"fft-decay\"\n */\n @property({ type: Number, attribute: \"fft-decay\", reflect: true })\n fftDecay = 8;\n\n /**\n * FFT gain for frequency analysis\n * @domAttribute \"fft-gain\"\n */\n @property({ type: Number, attribute: \"fft-gain\", reflect: true })\n fftGain = 3.0;\n\n /**\n * Enable/disable frequency interpolation\n * @domAttribute \"interpolate-frequencies\"\n */\n @property({\n type: Boolean,\n attribute: \"interpolate-frequencies\",\n reflect: true,\n })\n interpolateFrequencies = false;\n\n // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value\n get FREQ_WEIGHTS() {\n if (freqWeightsCache.has(this.fftSize)) {\n // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above\n return freqWeightsCache.get(this.fftSize)!;\n }\n\n const weights = new Float32Array(this.fftSize / 2).map((_, i) => {\n const frequency = (i * 48000) / this.fftSize;\n if (frequency < 60) return 0.3;\n if (frequency < 250) return 0.4;\n if (frequency < 500) return 0.6;\n if (frequency < 2000) return 0.8;\n if (frequency < 4000) return 1.2;\n if (frequency < 8000) return 1.6;\n return 2.0;\n });\n\n freqWeightsCache.set(this.fftSize, weights);\n return weights;\n }\n\n // Helper getter for backwards compatibility\n get shouldInterpolateFrequencies() {\n return this.interpolateFrequencies;\n }\n\n get urlGenerator() {\n return new UrlGenerator(() => this.apiHost ?? \"\");\n }\n\n mediaEngineTask = makeMediaEngineTask(this);\n\n audioSegmentIdTask = makeAudioSegmentIdTask(this);\n audioInitSegmentFetchTask = makeAudioInitSegmentFetchTask(this);\n audioSegmentFetchTask = makeAudioSegmentFetchTask(this);\n audioInputTask = makeAudioInputTask(this);\n audioSeekTask = makeAudioSeekTask(this);\n\n audioBufferTask = makeAudioBufferTask(this);\n\n // Audio analysis tasks for frequency and time domain analysis\n byteTimeDomainTask = makeAudioTimeDomainAnalysisTask(this);\n frequencyDataTask = makeAudioFrequencyAnalysisTask(this);\n\n /**\n * The unique identifier for the media asset.\n * This property can be set programmatically or via the \"asset-id\" attribute.\n * @domAttribute \"asset-id\"\n */\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n assetId: string | null = null;\n\n get intrinsicDurationMs() {\n return this.mediaEngineTask.value?.durationMs ?? 0;\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property\n const newCurrentSourceTimeMs = this.currentSourceTimeMs;\n if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {\n this.executeSeek(newCurrentSourceTimeMs);\n }\n\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.executeSeek(this.currentSourceTimeMs);\n }\n\n // Check if trim/source properties changed that affect duration\n const durationAffectingProps = [\n \"_trimStartMs\",\n \"_trimEndMs\",\n \"_sourceInMs\",\n \"_sourceOutMs\",\n ];\n\n const hasDurationChange = durationAffectingProps.some((prop) =>\n changedProperties.has(prop),\n );\n\n if (hasDurationChange) {\n // Notify parent timegroup to recalculate its duration (same pattern as EFCaptions)\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n\n // Also find and directly notify any context provider (ContextMixin)\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n parent.dispatchEvent(\n new CustomEvent(\"child-duration-changed\", {\n detail: { source: this },\n }),\n );\n break;\n }\n parent = parent.parentNode;\n }\n }\n }\n }\n\n get hasOwnDuration() {\n return true;\n }\n\n @state()\n private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading\n\n get desiredSeekTimeMs() {\n return this._desiredSeekTimeMs;\n }\n\n set desiredSeekTimeMs(value: number) {\n if (this._desiredSeekTimeMs !== value) {\n this._desiredSeekTimeMs = value;\n }\n }\n\n protected async executeSeek(seekToMs: number) {\n // The seekToMs parameter should be the timeline-relative media time\n // calculated from currentSourceTimeMs which includes timeline positioning\n this.desiredSeekTimeMs = seekToMs;\n }\n\n /**\n * Main integration method for EFTimegroup audio playback\n * Now powered by clean, testable utility functions\n * Returns undefined if no audio rendition is available\n */\n async fetchAudioSpanningTime(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioSpan | undefined> {\n return withSpan(\n \"media.fetchAudioSpanningTime\",\n {\n elementId: this.id || \"unknown\",\n tagName: this.tagName.toLowerCase(),\n fromMs,\n toMs,\n durationMs: toMs - fromMs,\n src: this.src || \"none\",\n },\n undefined,\n async () => {\n return fetchAudioSpanningTime(this, fromMs, toMs, signal);\n },\n );\n }\n\n /**\n * Wait for media engine to load and determine duration\n * Ensures media is ready for playback\n */\n async waitForMediaDurations(signal?: AbortSignal): Promise<void> {\n if (this.mediaEngineTask.value) {\n return;\n }\n \n // Use taskComplete instead of run() to avoid throwing errors\n // taskComplete resolves when the task completes successfully or rejects on error\n // This allows us to handle AbortError without it being logged as unhandled\n try {\n await this.mediaEngineTask.taskComplete;\n } catch (error) {\n // Don't throw AbortError - these are intentional cancellations when element is disconnected\n const isAbortError = \n error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message.includes(\"signal is aborted\") ||\n error.message.includes(\"The user aborted a request\")\n );\n \n // If explicitly aborted via signal, throw to propagate cancellation\n if (signal?.aborted) {\n throw error;\n }\n \n // For task abort (element disconnected), silently return\n // This is expected behavior when element is removed from DOM\n if (isAbortError) {\n return;\n }\n \n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return [this];\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EFMedia.js","names":["assignedElements: Element[]","#value","#error","#status","#promise","#resolvePromise","#mediaEngineSrcKey","#mediaEngine","#mediaEnginePromise","#createMediaEngine","#mediaEngineError","#handleMediaEngineComplete","url","JitMediaEngine","#frequencyDataCache","#analyzeFrequencies","#timeDomainDataCache","#analyzeTimeDomain"],"sources":["../../src/elements/EFMedia.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport type { ControllableInterface } from \"../gui/Controllable.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { withSpan } from \"../otel/tracingHelpers.js\";\nimport type { MediaEngine } from \"../transcoding/types/index.ts\";\nimport type { AudioSpan } from \"../transcoding/types/index.ts\";\nimport { UrlGenerator } from \"../transcoding/utils/UrlGenerator.ts\";\nimport { LRUCache } from \"../utils/LRUCache.js\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.ts\";\n\n// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts\ndeclare global {\n var EF_FRAMEGEN: import(\"../EF_FRAMEGEN.js\").EFFramegen;\n}\n\nconst freqWeightsCache = new Map<number, Float32Array>();\n\nexport class IgnorableError extends Error {}\n\n/**\n * Gets all child elements including slotted content for shadow DOM elements.\n * Duplicated here to avoid circular imports from EFTemporal.\n */\nconst getChildrenIncludingSlotted = (element: Element): Element[] => {\n if (element.shadowRoot) {\n const slots = element.shadowRoot.querySelectorAll('slot');\n if (slots.length > 0) {\n const assignedElements: Element[] = [];\n for (const slot of slots) {\n assignedElements.push(...slot.assignedElements());\n }\n for (const child of element.shadowRoot.children) {\n if (child.tagName !== 'SLOT') {\n assignedElements.push(child);\n }\n }\n return assignedElements;\n }\n }\n return Array.from(element.children);\n};\n\nexport const deepGetMediaElements = (\n element: Element,\n medias: EFMedia[] = [],\n) => {\n const children = getChildrenIncludingSlotted(element);\n for (const child of children) {\n if (child instanceof EFMedia) {\n medias.push(child);\n } else {\n deepGetMediaElements(child, medias);\n }\n }\n return medias;\n};\n\n// Import EFTemporal - use a function wrapper to defer evaluation until class definition\n// This breaks the circular dependency: EFTimegroup -> EFMedia -> EFTemporal\nimport { EFTemporal } from \"./EFTemporal.js\";\n\n/**\n * Simple async value wrapper that mimics Lit Task interface.\n * Used for backwards compatibility with code expecting task-like objects.\n */\nexport class AsyncValue<T> {\n #value: T | undefined = undefined;\n #error: Error | undefined = undefined;\n #status: \"initial\" | \"pending\" | \"complete\" | \"error\" = \"initial\";\n #promise: Promise<T | undefined> = Promise.resolve(undefined);\n #resolvePromise: ((value: T | undefined) => void) | undefined;\n\n // Use properties instead of getters to avoid TypeScript declaration generation bug\n get value(): T | undefined {\n return this.#value;\n }\n\n get error(): Error | undefined {\n return this.#error;\n }\n\n get status(): number {\n // Match TaskStatus enum: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n switch (this.#status) {\n case \"initial\": return 0;\n case \"pending\": return 1;\n case \"complete\": return 2;\n case \"error\": return 3;\n }\n }\n\n get taskComplete(): Promise<T | undefined> {\n return this.#promise;\n }\n\n /**\n * Set the value (marks status as complete)\n */\n setValue(value: T): void {\n this.#value = value;\n this.#error = undefined;\n this.#status = \"complete\";\n this.#resolvePromise?.(value);\n }\n\n /**\n * Set an error (marks status as error)\n */\n setError(error: Error): void {\n this.#error = error;\n this.#value = undefined;\n this.#status = \"error\";\n // Don't reject - just resolve with undefined to match old behavior\n this.#resolvePromise?.(undefined);\n }\n\n /**\n * Start a new async operation\n */\n startPending(): void {\n this.#status = \"pending\";\n this.#promise = new Promise((resolve) => {\n this.#resolvePromise = resolve;\n });\n // Prevent unhandled rejection warnings\n this.#promise.catch(() => {});\n }\n\n /**\n * Run an async function and update status accordingly\n */\n async run(fn: () => Promise<T>): Promise<T | undefined> {\n this.startPending();\n try {\n const result = await fn();\n this.setValue(result);\n return result;\n } catch (error) {\n if (error instanceof Error) {\n this.setError(error);\n } else {\n this.setError(new Error(String(error)));\n }\n return undefined;\n }\n }\n}\n\n// Audio analysis helper functions\nconst DECAY_WEIGHT = 0.8;\n\nfunction processFFTData(\n fftData: Uint8Array,\n zeroThresholdPercent = 0.1,\n): Uint8Array {\n const totalBins = fftData.length;\n const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);\n\n let zeroCount = 0;\n let cutoffIndex = totalBins;\n\n for (let i = totalBins - 1; i >= 0; i--) {\n if (fftData[i] ?? 0 < 10) {\n zeroCount++;\n } else {\n if (zeroCount >= zeroThresholdCount) {\n cutoffIndex = i + 1;\n break;\n }\n }\n }\n\n if (cutoffIndex < zeroThresholdCount) {\n return fftData;\n }\n\n const goodData = fftData.slice(0, cutoffIndex);\n const resampledData = interpolateData(goodData, fftData.length);\n\n const attenuationStartIndex = Math.floor(totalBins * 0.9);\n for (let i = attenuationStartIndex; i < totalBins; i++) {\n const attenuationProgress =\n (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;\n const attenuationFactor = Math.max(0, 1 - attenuationProgress);\n resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);\n }\n\n return resampledData;\n}\n\nfunction interpolateData(data: Uint8Array, targetSize: number): Uint8Array {\n const resampled = new Uint8Array(targetSize);\n const dataLength = data.length;\n\n for (let i = 0; i < targetSize; i++) {\n const ratio = (i / (targetSize - 1)) * (dataLength - 1);\n const index = Math.floor(ratio);\n const fraction = ratio - index;\n\n if (index >= dataLength - 1) {\n resampled[i] = data[dataLength - 1] ?? 0;\n } else {\n resampled[i] = Math.round(\n (data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,\n );\n }\n }\n\n return resampled;\n}\n\nexport class EFMedia extends EFTargetable(\n EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {\n assetType: \"isobmff_files\",\n }),\n) {\n @provide({ context: efContext })\n get efContext(): ControllableInterface | null {\n return this.rootTimegroup ?? this;\n }\n\n // Sample buffer size configuration\n static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;\n static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;\n\n /**\n * Which tracks this media element requires.\n * Subclasses can override to specify their needs:\n * - \"audio\" - Only needs audio track (e.g., EFAudio)\n * - \"video\" - Only needs video track\n * - \"both\" - Needs both tracks (default for backwards compatibility)\n * \n * This is used during media engine creation to skip validation\n * of tracks that won't be used, avoiding unnecessary network requests.\n */\n get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"both\";\n }\n\n static get observedAttributes() {\n // biome-ignore lint/complexity/noThisInStatic: We need to access super\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mute\",\n \"fft-size\",\n \"fft-decay\",\n \"fft-gain\",\n \"interpolate-frequencies\",\n \"asset-id\",\n \"audio-buffer-duration\",\n \"max-audio-buffer-fetches\",\n \"enable-audio-buffering\",\n \"sourcein\",\n \"sourceout\",\n ];\n }\n\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n `,\n ];\n\n /**\n * Duration in milliseconds for audio buffering ahead of current time\n * @domAttribute \"audio-buffer-duration\"\n */\n @property({ type: Number, attribute: \"audio-buffer-duration\" })\n audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent audio segment fetches for buffering\n * @domAttribute \"max-audio-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-audio-buffer-fetches\" })\n maxAudioBufferFetches = 2;\n\n /**\n * Enable/disable audio buffering system\n * @domAttribute \"enable-audio-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-audio-buffering\" })\n enableAudioBuffering = true;\n\n /**\n * Mute/unmute the media element\n * @domAttribute \"mute\"\n */\n @property({\n type: Boolean,\n attribute: \"mute\",\n reflect: true,\n })\n mute = false;\n\n /**\n * FFT size for frequency analysis\n * @domAttribute \"fft-size\"\n */\n @property({ type: Number, attribute: \"fft-size\", reflect: true })\n fftSize = 128;\n\n /**\n * FFT decay rate for frequency analysis\n * @domAttribute \"fft-decay\"\n */\n @property({ type: Number, attribute: \"fft-decay\", reflect: true })\n fftDecay = 8;\n\n /**\n * FFT gain for frequency analysis\n * @domAttribute \"fft-gain\"\n */\n @property({ type: Number, attribute: \"fft-gain\", reflect: true })\n fftGain = 3.0;\n\n /**\n * Enable/disable frequency interpolation\n * @domAttribute \"interpolate-frequencies\"\n */\n @property({\n type: Boolean,\n attribute: \"interpolate-frequencies\",\n reflect: true,\n })\n interpolateFrequencies = false;\n\n // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value\n getFreqWeights() {\n if (freqWeightsCache.has(this.fftSize)) {\n // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above\n return freqWeightsCache.get(this.fftSize)!;\n }\n\n const weights = new Float32Array(this.fftSize / 2).map((_, i) => {\n const frequency = (i * 48000) / this.fftSize;\n if (frequency < 60) return 0.3;\n if (frequency < 250) return 0.4;\n if (frequency < 500) return 0.6;\n if (frequency < 2000) return 0.8;\n if (frequency < 4000) return 1.2;\n if (frequency < 8000) return 1.6;\n return 2.0;\n });\n\n freqWeightsCache.set(this.fftSize, weights);\n return weights;\n }\n\n // Helper method for backwards compatibility\n getShouldInterpolateFrequencies() {\n return this.interpolateFrequencies;\n }\n\n getUrlGenerator() {\n return new UrlGenerator(() => this.apiHost ?? \"\");\n }\n\n // ============================================================================\n // Media Engine - replaced task with async method + cached wrapper\n // ============================================================================\n\n #mediaEngine: MediaEngine | undefined = undefined;\n #mediaEnginePromise: Promise<MediaEngine | undefined> | undefined = undefined;\n #mediaEngineError: Error | undefined = undefined;\n #mediaEngineSrcKey: string | null = null;\n\n /**\n * Async wrapper that mimics Task interface for backwards compatibility.\n * Code expecting mediaEngineTask.value, .taskComplete, .error, .status will still work.\n */\n mediaEngineTask = new AsyncValue<MediaEngine>();\n\n /**\n * Get or create the MediaEngine for this element.\n * Uses caching based on src/assetId to avoid redundant fetches.\n */\n async getMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n const srcKey = `${this.src}|${this.assetId}`;\n \n // Return cached if src hasn't changed\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEngine) {\n return this.#mediaEngine;\n }\n\n // If already loading for this src, wait for it\n if (this.#mediaEngineSrcKey === srcKey && this.#mediaEnginePromise) {\n return this.#mediaEnginePromise;\n }\n\n // Start new load\n this.#mediaEngineSrcKey = srcKey;\n this.mediaEngineTask.startPending();\n\n this.#mediaEnginePromise = this.#createMediaEngine(signal);\n \n try {\n this.#mediaEngine = await this.#mediaEnginePromise;\n this.#mediaEngineError = undefined;\n if (this.#mediaEngine) {\n this.mediaEngineTask.setValue(this.#mediaEngine);\n this.#handleMediaEngineComplete();\n }\n return this.#mediaEngine;\n } catch (error) {\n this.#mediaEngineError = error instanceof Error ? error : new Error(String(error));\n this.mediaEngineTask.setError(this.#mediaEngineError);\n \n // Don't throw for expected errors\n const isExpectedError = error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.message === \"No valid media source\" ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"404\") ||\n error.message.includes(\"Failed to fetch\")\n );\n \n if (!isExpectedError) {\n console.error(\"Media engine error:\", error);\n }\n \n return undefined;\n }\n }\n\n async #createMediaEngine(signal?: AbortSignal): Promise<MediaEngine | undefined> {\n const { src, assetId, apiHost, requiredTracks } = this;\n const urlGenerator = this.getUrlGenerator();\n\n // Check for AssetID mode first\n if (assetId !== null && assetId !== undefined && assetId.trim() !== \"\") {\n if (!apiHost) {\n throw new Error(\"API host is required for AssetID mode\");\n }\n const { AssetIdMediaEngine } = await import(\"./EFMedia/AssetIdMediaEngine.js\");\n return AssetIdMediaEngine.fetchByAssetId(\n this,\n urlGenerator,\n assetId,\n apiHost,\n requiredTracks,\n signal,\n );\n }\n\n // Check for null/undefined/empty/whitespace src\n if (!src || typeof src !== \"string\" || src.trim() === \"\") {\n return undefined;\n }\n\n const lowerSrc = src.toLowerCase();\n const isRemoteUrl = lowerSrc.startsWith(\"http://\") || lowerSrc.startsWith(\"https://\");\n \n // Check configuration for explicit engine preference\n const configuration = this.closest(\"ef-configuration\");\n \n // \"jit\" mode: Force JitMediaEngine for all sources (including local files)\n if (configuration?.mediaEngine === \"jit\") {\n let manifestSrc = src;\n if (!isRemoteUrl && configuration.apiHost) {\n const baseUrl = configuration.apiHost.replace(/\\/$/, \"\");\n const normalizedPath = src.replace(/^\\.\\//, \"/src/\");\n manifestSrc = `${baseUrl}${normalizedPath}`;\n }\n const url = urlGenerator.generateManifestUrl(manifestSrc);\n const { JitMediaEngine } = await import(\"./EFMedia/JitMediaEngine.js\");\n return JitMediaEngine.fetch(this, urlGenerator, url, signal);\n }\n \n // \"local\" mode: Force AssetMediaEngine for all sources\n if (configuration?.mediaEngine === \"local\") {\n const { AssetMediaEngine } = await import(\"./EFMedia/AssetMediaEngine.js\");\n return AssetMediaEngine.fetch(this, urlGenerator, src, requiredTracks, signal);\n }\n \n // \"cloud\" mode (default): AssetMediaEngine for local paths, JitMediaEngine for remote URLs\n if (!isRemoteUrl) {\n const { AssetMediaEngine } = await import(\"./EFMedia/AssetMediaEngine.js\");\n return AssetMediaEngine.fetch(this, urlGenerator, src, requiredTracks, signal);\n }\n\n // Default: Use JitMediaEngine for remote URLs (transcoding service)\n const url = urlGenerator.generateManifestUrl(src);\n const { JitMediaEngine } = await import(\"./EFMedia/JitMediaEngine.js\");\n return JitMediaEngine.fetch(this, urlGenerator, url, signal);\n }\n\n #handleMediaEngineComplete(): void {\n // Update self synchronously\n this.requestUpdate(\"intrinsicDurationMs\");\n this.requestUpdate(\"ownCurrentTimeMs\");\n \n // Defer updates to parent/root timegroup\n if (this.rootTimegroup) {\n queueMicrotask(() => {\n this.rootTimegroup?.requestUpdate(\"ownCurrentTimeMs\");\n this.rootTimegroup?.requestUpdate(\"durationMs\");\n });\n }\n }\n\n // ============================================================================\n // Audio Analysis - replaced tasks with async methods + cached wrappers\n // ============================================================================\n\n #frequencyDataCache = new LRUCache<string, Uint8Array>(100);\n #timeDomainDataCache = new LRUCache<string, Uint8Array>(100);\n\n /**\n * Async wrapper for frequency data - mimics Task interface for EFWaveform compatibility\n */\n frequencyDataTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Async wrapper for time domain data - mimics Task interface for EFWaveform compatibility\n */\n byteTimeDomainTask = new AsyncValue<Uint8Array | null>();\n\n /**\n * Get frequency data for audio visualization at a given time.\n */\n async getFrequencyData(timeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.getShouldInterpolateFrequencies()}:${this.fftSize}:${this.fftDecay}:${this.fftGain}:${timeMs}`;\n const cached = this.#frequencyDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeFrequencies(timeMs, signal);\n if (result) {\n this.#frequencyDataCache.set(cacheKey, result);\n this.frequencyDataTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n /**\n * Get time domain data for audio visualization at a given time.\n */\n async getTimeDomainData(timeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n if (timeMs < 0) return null;\n\n const cacheKey = `${this.fftSize}:${timeMs}`;\n const cached = this.#timeDomainDataCache.get(cacheKey);\n if (cached) return cached;\n\n try {\n const result = await this.#analyzeTimeDomain(timeMs, signal);\n if (result) {\n this.#timeDomainDataCache.set(cacheKey, result);\n this.byteTimeDomainTask.setValue(result);\n }\n return result;\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n }\n\n async #analyzeFrequencies(currentTimeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n \n if (!mediaEngine?.audioRendition) {\n return null;\n }\n\n // Calculate exact audio window needed based on fftDecay and frame timing\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } = await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n \n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n // Decode the real audio data\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1000);\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n \n const frameData = new Uint8Array(this.fftSize / 2);\n analyser.getByteFrequencyData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Combine frames with decay\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let weightedSum = 0;\n let weightSum = 0;\n\n framesData.forEach((frame: Uint8Array, frameIndex: number) => {\n const decayWeight = DECAY_WEIGHT ** frameIndex;\n weightedSum += (frame[i] ?? 0) * decayWeight;\n weightSum += decayWeight;\n });\n\n smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));\n }\n\n // Apply frequency weights\n smoothedData.forEach((value, i) => {\n const freqWeight = this.getFreqWeights()[i] ?? 0;\n smoothedData[i] = Math.min(255, Math.round(value * freqWeight));\n });\n\n // Only return the lower half of the frequency data\n const slicedData = smoothedData.slice(0, Math.floor(smoothedData.length / 2));\n return this.getShouldInterpolateFrequencies() ? processFFTData(slicedData) : slicedData;\n }\n\n async #analyzeTimeDomain(currentTimeMs: number, signal?: AbortSignal): Promise<Uint8Array | null> {\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n \n if (!mediaEngine?.audioRendition) {\n return null;\n }\n\n const frameIntervalMs = 1000 / 30;\n const earliestFrameMs = currentTimeMs - (this.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs;\n const videoDurationMs = this.intrinsicDurationMs || 0;\n const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n if (fromMs >= toMs) {\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } = await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n \n let audioSpan;\n try {\n audioSpan = await fetchAudioSpan(this, fromMs, toMs, signal);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n return null;\n }\n\n if (!audioSpan?.blob || audioSpan.blob.size < 100) {\n return null;\n }\n\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n signal?.throwIfAborted();\n\n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n signal?.throwIfAborted();\n } catch {\n return null;\n }\n\n const startOffsetMs = audioSpan.startMs;\n\n const framesData = await Promise.all(\n Array.from({ length: this.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1000);\n\n const SIZE = 48000 / 30;\n const audioContext = new OfflineAudioContext(2, SIZE, 48000);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = this.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = this.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n signal?.throwIfAborted();\n \n const frameData = new Uint8Array(this.fftSize);\n analyser.getByteTimeDomainData(frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Use RMS calculation to preserve waveform shape\n const smoothedData = new Uint8Array(frameLength);\n for (let i = 0; i < frameLength; i++) {\n let sumSquares = 0;\n framesData.forEach((frame: Uint8Array) => {\n const value = (frame[i] ?? 128) - 128;\n sumSquares += value * value;\n });\n const rms = Math.sqrt(sumSquares / framesData.length);\n smoothedData[i] = Math.min(255, Math.max(0, Math.round(rms + 128)));\n }\n\n return smoothedData;\n }\n\n // ============================================================================\n // Removed task properties - these are kept as stubs for backwards compatibility\n // ============================================================================\n\n // These tasks are no longer used but kept for API compatibility\n audioSegmentIdTask = new AsyncValue<number | undefined>();\n audioInitSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioSegmentFetchTask = new AsyncValue<ArrayBuffer | undefined>();\n audioInputTask = new AsyncValue<any>();\n audioSeekTask = new AsyncValue<any>();\n audioBufferTask = new AsyncValue<any>();\n\n /**\n * The unique identifier for the media asset.\n * This property can be set programmatically or via the \"asset-id\" attribute.\n * @domAttribute \"asset-id\"\n */\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n assetId: string | null = null;\n\n get intrinsicDurationMs(): number | undefined {\n return this.#mediaEngine?.durationMs;\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Trigger media engine load when src or assetId changes\n if (changedProperties.has(\"src\") || changedProperties.has(\"assetId\")) {\n // Start loading media engine asynchronously\n this.getMediaEngine().catch(() => {});\n }\n\n // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property\n const newCurrentSourceTimeMs = this.currentSourceTimeMs;\n if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {\n this.executeSeek(newCurrentSourceTimeMs);\n }\n\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.executeSeek(this.currentSourceTimeMs);\n }\n\n // Check if trim/source properties changed that affect duration\n const durationAffectingProps = [\n \"_trimStartMs\",\n \"_trimEndMs\",\n \"_sourceInMs\",\n \"_sourceOutMs\",\n ];\n\n const hasDurationChange = durationAffectingProps.some((prop) =>\n changedProperties.has(prop),\n );\n\n if (hasDurationChange) {\n // Notify parent timegroup to recalculate its duration (same pattern as EFCaptions)\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n\n // Also find and directly notify any context provider (ContextMixin)\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n parent.dispatchEvent(\n new CustomEvent(\"child-duration-changed\", {\n detail: { source: this },\n }),\n );\n break;\n }\n parent = parent.parentNode;\n }\n }\n }\n }\n\n get hasOwnDuration(): boolean {\n return true;\n }\n\n @state()\n private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading\n\n get desiredSeekTimeMs(): number {\n return this._desiredSeekTimeMs;\n }\n\n set desiredSeekTimeMs(value: number) {\n if (this._desiredSeekTimeMs !== value) {\n this._desiredSeekTimeMs = value;\n }\n }\n\n protected async executeSeek(seekToMs: number) {\n // The seekToMs parameter should be the timeline-relative media time\n // calculated from currentSourceTimeMs which includes timeline positioning\n this._desiredSeekTimeMs = seekToMs;\n }\n\n /**\n * Main integration method for EFTimegroup audio playback\n * Now powered by clean, testable utility functions\n * Returns undefined if no audio rendition is available\n */\n async fetchAudioSpanningTime(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioSpan | undefined> {\n return withSpan(\n \"media.fetchAudioSpanningTime\",\n {\n elementId: this.id || \"unknown\",\n tagName: this.tagName.toLowerCase(),\n fromMs,\n toMs,\n durationMs: toMs - fromMs,\n src: this.src || \"none\",\n },\n undefined,\n async () => {\n const { fetchAudioSpanningTime } = await import(\"./EFMedia/shared/AudioSpanUtils.js\");\n return fetchAudioSpanningTime(this, fromMs, toMs, signal);\n },\n );\n }\n\n /**\n * Wait for media engine to load and determine duration\n * Ensures media is ready for playback\n */\n async waitForMediaDurations(signal?: AbortSignal): Promise<void> {\n if (this.#mediaEngine) {\n return;\n }\n \n try {\n await this.getMediaEngine(signal);\n } catch (error) {\n // Don't throw AbortError - these are intentional cancellations when element is disconnected\n const isAbortError = \n error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message.includes(\"signal is aborted\") ||\n error.message.includes(\"The user aborted a request\")\n );\n \n // If explicitly aborted via signal, throw to propagate cancellation\n if (signal?.aborted) {\n throw error;\n }\n \n // For task abort (element disconnected), silently return\n if (isAbortError) {\n return;\n }\n \n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return [this];\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n}\n"],"mappings":"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"}