@editframe/elements 0.37.3-beta → 0.38.1

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 (327) hide show
  1. package/dist/EF_FRAMEGEN.js +17 -14
  2. package/dist/EF_FRAMEGEN.js.map +1 -1
  3. package/dist/EF_RENDERING.js.map +1 -1
  4. package/dist/canvas/EFCanvas.d.ts +9 -2
  5. package/dist/canvas/EFCanvas.js +14 -4
  6. package/dist/canvas/EFCanvas.js.map +1 -1
  7. package/dist/canvas/EFCanvasItem.d.ts +2 -2
  8. package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
  9. package/dist/canvas/overlays/SelectionOverlay.js +5 -12
  10. package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
  11. package/dist/canvas/overlays/overlayState.js.map +1 -1
  12. package/dist/canvas/selection/SelectionController.js.map +1 -1
  13. package/dist/elements/EFAudio.d.ts +1 -11
  14. package/dist/elements/EFAudio.js +2 -10
  15. package/dist/elements/EFAudio.js.map +1 -1
  16. package/dist/elements/EFCaptions.d.ts +5 -9
  17. package/dist/elements/EFCaptions.js +34 -11
  18. package/dist/elements/EFCaptions.js.map +1 -1
  19. package/dist/elements/EFImage.d.ts +10 -8
  20. package/dist/elements/EFImage.js +117 -32
  21. package/dist/elements/EFImage.js.map +1 -1
  22. package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
  23. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  24. package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
  25. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  26. package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
  27. package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
  28. package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
  29. package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
  30. package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
  31. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  32. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
  33. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  34. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
  35. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
  36. package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
  37. package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
  38. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
  39. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
  40. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
  41. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  42. package/dist/elements/EFMedia.d.ts +14 -8
  43. package/dist/elements/EFMedia.js +52 -19
  44. package/dist/elements/EFMedia.js.map +1 -1
  45. package/dist/elements/EFPanZoom.d.ts +2 -2
  46. package/dist/elements/EFPanZoom.js +1 -1
  47. package/dist/elements/EFPanZoom.js.map +1 -1
  48. package/dist/elements/EFSourceMixin.js +16 -8
  49. package/dist/elements/EFSourceMixin.js.map +1 -1
  50. package/dist/elements/EFSurface.d.ts +5 -8
  51. package/dist/elements/EFSurface.js +4 -43
  52. package/dist/elements/EFSurface.js.map +1 -1
  53. package/dist/elements/EFTemporal.d.ts +33 -8
  54. package/dist/elements/EFTemporal.js +92 -40
  55. package/dist/elements/EFTemporal.js.map +1 -1
  56. package/dist/elements/EFText.d.ts +3 -0
  57. package/dist/elements/EFText.js +54 -21
  58. package/dist/elements/EFText.js.map +1 -1
  59. package/dist/elements/EFTextSegment.js +8 -4
  60. package/dist/elements/EFTextSegment.js.map +1 -1
  61. package/dist/elements/EFTimegroup.d.ts +26 -43
  62. package/dist/elements/EFTimegroup.js +295 -314
  63. package/dist/elements/EFTimegroup.js.map +1 -1
  64. package/dist/elements/EFVideo.d.ts +44 -42
  65. package/dist/elements/EFVideo.js +259 -172
  66. package/dist/elements/EFVideo.js.map +1 -1
  67. package/dist/elements/EFWaveform.d.ts +3 -8
  68. package/dist/elements/EFWaveform.js +18 -13
  69. package/dist/elements/EFWaveform.js.map +1 -1
  70. package/dist/elements/ElementPositionInfo.js.map +1 -1
  71. package/dist/elements/FetchMixin.js.map +1 -1
  72. package/dist/elements/TargetController.d.ts +0 -3
  73. package/dist/elements/TargetController.js +12 -35
  74. package/dist/elements/TargetController.js.map +1 -1
  75. package/dist/elements/TimegroupController.js.map +1 -1
  76. package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
  77. package/dist/elements/cloneFactoryRegistry.js +15 -0
  78. package/dist/elements/cloneFactoryRegistry.js.map +1 -0
  79. package/dist/elements/renderTemporalAudio.js +8 -6
  80. package/dist/elements/renderTemporalAudio.js.map +1 -1
  81. package/dist/elements/setupTemporalHierarchy.js +62 -0
  82. package/dist/elements/setupTemporalHierarchy.js.map +1 -0
  83. package/dist/elements/updateAnimations.js +62 -87
  84. package/dist/elements/updateAnimations.js.map +1 -1
  85. package/dist/getRenderInfo.d.ts +3 -2
  86. package/dist/getRenderInfo.js +20 -4
  87. package/dist/getRenderInfo.js.map +1 -1
  88. package/dist/gui/ContextMixin.js +68 -12
  89. package/dist/gui/ContextMixin.js.map +1 -1
  90. package/dist/gui/Controllable.js +1 -1
  91. package/dist/gui/Controllable.js.map +1 -1
  92. package/dist/gui/EFActiveRootTemporal.d.ts +2 -2
  93. package/dist/gui/EFActiveRootTemporal.js.map +1 -1
  94. package/dist/gui/EFControls.d.ts +2 -2
  95. package/dist/gui/EFControls.js +2 -2
  96. package/dist/gui/EFControls.js.map +1 -1
  97. package/dist/gui/EFDial.d.ts +2 -2
  98. package/dist/gui/EFDial.js +12 -9
  99. package/dist/gui/EFDial.js.map +1 -1
  100. package/dist/gui/EFFilmstrip.d.ts +2 -0
  101. package/dist/gui/EFFilmstrip.js +18 -10
  102. package/dist/gui/EFFilmstrip.js.map +1 -1
  103. package/dist/gui/EFFitScale.d.ts +28 -4
  104. package/dist/gui/EFFitScale.js +88 -26
  105. package/dist/gui/EFFitScale.js.map +1 -1
  106. package/dist/gui/EFFocusOverlay.d.ts +2 -2
  107. package/dist/gui/EFFocusOverlay.js +3 -3
  108. package/dist/gui/EFFocusOverlay.js.map +1 -1
  109. package/dist/gui/EFOverlayItem.d.ts +2 -2
  110. package/dist/gui/EFOverlayLayer.d.ts +2 -2
  111. package/dist/gui/EFPause.d.ts +2 -2
  112. package/dist/gui/EFPause.js +1 -1
  113. package/dist/gui/EFPlay.d.ts +2 -2
  114. package/dist/gui/EFPlay.js +1 -1
  115. package/dist/gui/EFPreview.js +1 -1
  116. package/dist/gui/EFResizableBox.d.ts +2 -2
  117. package/dist/gui/EFResizableBox.js +5 -5
  118. package/dist/gui/EFResizableBox.js.map +1 -1
  119. package/dist/gui/EFScrubber.d.ts +2 -2
  120. package/dist/gui/EFScrubber.js +8 -13
  121. package/dist/gui/EFScrubber.js.map +1 -1
  122. package/dist/gui/EFTimeDisplay.d.ts +6 -2
  123. package/dist/gui/EFTimeDisplay.js +25 -7
  124. package/dist/gui/EFTimeDisplay.js.map +1 -1
  125. package/dist/gui/EFTimelineRuler.d.ts +2 -2
  126. package/dist/gui/EFTimelineRuler.js +3 -3
  127. package/dist/gui/EFTimelineRuler.js.map +1 -1
  128. package/dist/gui/EFToggleLoop.d.ts +2 -2
  129. package/dist/gui/EFToggleLoop.js +1 -1
  130. package/dist/gui/EFTogglePlay.d.ts +2 -2
  131. package/dist/gui/EFTogglePlay.js +1 -1
  132. package/dist/gui/EFTransformHandles.d.ts +2 -2
  133. package/dist/gui/EFTransformHandles.js +6 -6
  134. package/dist/gui/EFTransformHandles.js.map +1 -1
  135. package/dist/gui/EFWorkbench.d.ts +40 -36
  136. package/dist/gui/EFWorkbench.js +436 -822
  137. package/dist/gui/EFWorkbench.js.map +1 -1
  138. package/dist/gui/FitScaleHelpers.js.map +1 -1
  139. package/dist/gui/PlaybackController.d.ts +3 -8
  140. package/dist/gui/PlaybackController.js +59 -56
  141. package/dist/gui/PlaybackController.js.map +1 -1
  142. package/dist/gui/TWMixin.js +1 -1
  143. package/dist/gui/TWMixin.js.map +1 -1
  144. package/dist/gui/TargetOrContextMixin.js +43 -6
  145. package/dist/gui/TargetOrContextMixin.js.map +1 -1
  146. package/dist/gui/ef-theme.css +136 -0
  147. package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
  148. package/dist/gui/hierarchy/EFHierarchy.js +14 -24
  149. package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
  150. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
  151. package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
  152. package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
  153. package/dist/gui/icons.js.map +1 -1
  154. package/dist/gui/previewSettingsContext.d.ts +18 -0
  155. package/dist/gui/previewSettingsContext.js.map +1 -1
  156. package/dist/gui/theme.js +34 -0
  157. package/dist/gui/theme.js.map +1 -0
  158. package/dist/gui/timeline/EFTimeline.d.ts +2 -2
  159. package/dist/gui/timeline/EFTimeline.js +70 -52
  160. package/dist/gui/timeline/EFTimeline.js.map +1 -1
  161. package/dist/gui/timeline/EFTimelineRow.d.ts +5 -3
  162. package/dist/gui/timeline/EFTimelineRow.js +55 -32
  163. package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
  164. package/dist/gui/timeline/TrimHandles.d.ts +23 -9
  165. package/dist/gui/timeline/TrimHandles.js +224 -51
  166. package/dist/gui/timeline/TrimHandles.js.map +1 -1
  167. package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
  168. package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
  169. package/dist/gui/timeline/timelineEditingContext.js +24 -0
  170. package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
  171. package/dist/gui/timeline/timelineStateContext.js.map +1 -1
  172. package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
  173. package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
  174. package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
  175. package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
  176. package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
  177. package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
  178. package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
  179. package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
  180. package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
  181. package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
  182. package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
  183. package/dist/gui/timeline/tracks/TextTrack.js +17 -43
  184. package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
  185. package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
  186. package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
  187. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
  188. package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
  189. package/dist/gui/timeline/tracks/TrackItem.js +18 -17
  190. package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
  191. package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
  192. package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
  193. package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
  194. package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
  195. package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
  196. package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
  197. package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
  198. package/dist/gui/tree/EFTree.d.ts +2 -2
  199. package/dist/gui/tree/EFTree.js +8 -14
  200. package/dist/gui/tree/EFTree.js.map +1 -1
  201. package/dist/gui/tree/EFTreeItem.d.ts +2 -2
  202. package/dist/gui/tree/EFTreeItem.js +3 -3
  203. package/dist/gui/tree/EFTreeItem.js.map +1 -1
  204. package/dist/gui/tree/treeContext.js.map +1 -1
  205. package/dist/index.d.ts +10 -8
  206. package/dist/index.js +6 -5
  207. package/dist/index.js.map +1 -1
  208. package/dist/node.d.ts +2 -2
  209. package/dist/node.js +2 -2
  210. package/dist/preview/AdaptiveResolutionTracker.js +3 -3
  211. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
  212. package/dist/preview/FrameController.d.ts +2 -17
  213. package/dist/preview/FrameController.js +40 -63
  214. package/dist/preview/FrameController.js.map +1 -1
  215. package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
  216. package/dist/preview/QualityUpgradeScheduler.js +158 -0
  217. package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
  218. package/dist/preview/RenderContext.d.ts +119 -1
  219. package/dist/preview/RenderContext.js +21 -3
  220. package/dist/preview/RenderContext.js.map +1 -1
  221. package/dist/preview/RenderProfiler.js.map +1 -1
  222. package/dist/preview/RenderStats.js +85 -0
  223. package/dist/preview/RenderStats.js.map +1 -0
  224. package/dist/preview/encoding/canvasEncoder.js +2 -52
  225. package/dist/preview/encoding/canvasEncoder.js.map +1 -1
  226. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
  227. package/dist/preview/encoding/workerEncoder.js.map +1 -1
  228. package/dist/preview/logger.js.map +1 -1
  229. package/dist/preview/previewSettings.d.ts +34 -0
  230. package/dist/preview/previewSettings.js +29 -17
  231. package/dist/preview/previewSettings.js.map +1 -1
  232. package/dist/preview/previewTypes.js +4 -4
  233. package/dist/preview/previewTypes.js.map +1 -1
  234. package/dist/preview/renderElementToCanvas.d.ts +44 -0
  235. package/dist/preview/renderElementToCanvas.js +72 -0
  236. package/dist/preview/renderElementToCanvas.js.map +1 -0
  237. package/dist/preview/renderTimegroupToCanvas.d.ts +134 -32
  238. package/dist/preview/renderTimegroupToCanvas.js +321 -146
  239. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  240. package/dist/preview/renderTimegroupToCanvas.types.d.ts +51 -0
  241. package/dist/preview/renderTimegroupToVideo.d.ts +20 -35
  242. package/dist/preview/renderTimegroupToVideo.js +94 -106
  243. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  244. package/dist/preview/renderTimegroupToVideo.types.d.ts +42 -0
  245. package/dist/preview/renderVideoToVideo.js +286 -0
  246. package/dist/preview/renderVideoToVideo.js.map +1 -0
  247. package/dist/preview/renderers.d.ts +56 -0
  248. package/dist/preview/renderers.js +13 -1
  249. package/dist/preview/renderers.js.map +1 -1
  250. package/dist/preview/rendering/ScaleConfig.js +74 -0
  251. package/dist/preview/rendering/ScaleConfig.js.map +1 -0
  252. package/dist/preview/rendering/inlineImages.d.ts +13 -0
  253. package/dist/preview/rendering/inlineImages.js +7 -44
  254. package/dist/preview/rendering/inlineImages.js.map +1 -1
  255. package/dist/preview/rendering/loadImage.d.ts +8 -0
  256. package/dist/preview/rendering/loadImage.js +22 -0
  257. package/dist/preview/rendering/loadImage.js.map +1 -0
  258. package/dist/preview/rendering/renderToImageNative.js +3 -3
  259. package/dist/preview/rendering/renderToImageNative.js.map +1 -1
  260. package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
  261. package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
  262. package/dist/preview/statsTrackingStrategy.js +1 -101
  263. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  264. package/dist/preview/workers/WorkerPool.js +0 -1
  265. package/dist/preview/workers/WorkerPool.js.map +1 -1
  266. package/dist/preview/workers/encoderWorkerInline.js +21 -54
  267. package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
  268. package/dist/render/EFRenderAPI.d.ts +2 -1
  269. package/dist/render/EFRenderAPI.js +12 -36
  270. package/dist/render/EFRenderAPI.js.map +1 -1
  271. package/dist/render/getRenderData.js +4 -4
  272. package/dist/render/getRenderData.js.map +1 -1
  273. package/dist/style.css +114 -163
  274. package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
  275. package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
  276. package/dist/transcoding/types/index.d.ts +1 -1
  277. package/dist/transcoding/utils/UrlGenerator.js +10 -3
  278. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  279. package/dist/utils/LRUCache.js +1 -0
  280. package/dist/utils/LRUCache.js.map +1 -1
  281. package/dist/utils/frameTime.js +23 -1
  282. package/dist/utils/frameTime.js.map +1 -1
  283. package/package.json +45 -8
  284. package/scripts/build-css.js +8 -1
  285. package/test/setup.ts +0 -1
  286. package/test/useAssetMSW.ts +50 -0
  287. package/test/visualRegressionUtils.ts +23 -9
  288. package/tsdown.config.ts +6 -1
  289. package/dist/_virtual/rolldown_runtime.js +0 -27
  290. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
  291. package/dist/elements/EFThumbnailStrip.d.ts +0 -167
  292. package/dist/elements/EFThumbnailStrip.js +0 -731
  293. package/dist/elements/EFThumbnailStrip.js.map +0 -1
  294. package/dist/elements/SessionThumbnailCache.js +0 -154
  295. package/dist/elements/SessionThumbnailCache.js.map +0 -1
  296. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
  297. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
  298. package/dist/node_modules/react/cjs/react.development.js +0 -1521
  299. package/dist/node_modules/react/cjs/react.development.js.map +0 -1
  300. package/dist/node_modules/react/index.js +0 -13
  301. package/dist/node_modules/react/index.js.map +0 -1
  302. package/dist/node_modules/react/jsx-runtime.js +0 -13
  303. package/dist/node_modules/react/jsx-runtime.js.map +0 -1
  304. package/dist/preview/encoding/types.d.ts +0 -1
  305. package/dist/preview/renderTimegroupPreview.js +0 -686
  306. package/dist/preview/renderTimegroupPreview.js.map +0 -1
  307. package/dist/preview/rendering/renderToImage.d.ts +0 -2
  308. package/dist/preview/rendering/renderToImage.js +0 -95
  309. package/dist/preview/rendering/renderToImage.js.map +0 -1
  310. package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
  311. package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
  312. package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
  313. package/dist/preview/rendering/svgSerializer.js +0 -43
  314. package/dist/preview/rendering/svgSerializer.js.map +0 -1
  315. package/dist/preview/rendering/types.d.ts +0 -2
  316. package/dist/preview/thumbnailCacheSettings.js +0 -52
  317. package/dist/preview/thumbnailCacheSettings.js.map +0 -1
  318. package/dist/sandbox/PlaybackControls.d.ts +0 -1
  319. package/dist/sandbox/PlaybackControls.js +0 -10
  320. package/dist/sandbox/PlaybackControls.js.map +0 -1
  321. package/dist/sandbox/ScenarioRunner.d.ts +0 -1
  322. package/dist/sandbox/ScenarioRunner.js +0 -1
  323. package/dist/sandbox/defineSandbox.d.ts +0 -1
  324. package/dist/sandbox/index.d.ts +0 -3
  325. package/dist/sandbox/index.js +0 -2
  326. package/test/EFVideo.framegen.browsertest.ts +0 -80
  327. package/test/thumbnail-performance-test.html +0 -116
@@ -1 +1 @@
1
- {"version":3,"file":"EFVideo.js","names":["EFVideo","#cachedVideoSample","#cachedVideoSampleTimeMs","#fetchVideoSampleForFrame","#getMainVideoSampleForFrame","#getScrubVideoSampleForFrame","initSegment: ArrayBuffer | undefined","mediaSegment: ArrayBuffer | undefined","#delayedLoadingState","#pendingFrameReadyTime","#pendingFrameReadyPromise","#doWaitForFrameReady","videoSample: any","uncachedSegmentIds: number[]"],"sources":["../../src/elements/EFVideo.ts"],"sourcesContent":["import { context, trace } from \"@opentelemetry/api\";\nimport debug from \"debug\";\nimport { css, html, type PropertyValueMap } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport type { VideoSample } from \"mediabunny\";\nimport { DelayedLoadingState } from \"../DelayedLoadingState.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { withSpanSync } from \"../otel/tracingHelpers.js\";\nimport {\n type FrameRenderable,\n type FrameState,\n createFrameTaskWrapper,\n PRIORITY_VIDEO,\n} from \"../preview/FrameController.js\";\nimport { MainVideoInputCache } from \"./EFMedia/videoTasks/MainVideoInputCache.ts\";\nimport { ScrubInputCache } from \"./EFMedia/videoTasks/ScrubInputCache.ts\";\nimport { EFMedia } from \"./EFMedia.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\n\n// Shared caches for video seeking\nconst mainVideoInputCache = new MainVideoInputCache();\nconst scrubInputCache = new ScrubInputCache();\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 log = debug(\"ef:elements:EFVideo\");\n\ninterface LoadingState {\n isLoading: boolean;\n operation: \"scrub-segment\" | \"video-segment\" | \"seeking\" | \"decoding\" | null;\n message: string;\n}\n\n/**\n * Event detail for scrub segment loading progress.\n * Dispatched during prefetchScrubSegments to indicate network activity.\n */\nexport interface ScrubSegmentLoadingDetail {\n /** The segment ID being loaded (0-indexed) */\n segmentId: number;\n /** Time range covered by this segment [startMs, endMs] */\n timeRangeMs: [number, number];\n /** Number of segments loaded so far */\n loaded: number;\n /** Total number of segments to load */\n total: number;\n /** Current status: \"loading\" or \"loaded\" */\n status: \"loading\" | \"loaded\";\n}\n\n@customElement(\"ef-video\")\nexport class EFVideo extends TWMixin(EFMedia) implements FrameRenderable {\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n }\n canvas {\n overflow: hidden;\n position: static;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n overflow: hidden;\n border: none;\n outline: none;\n box-shadow: none;\n }\n .loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10;\n backdrop-filter: blur(2px);\n }\n .loading-content {\n background: rgba(0, 0, 0, 0.8);\n border-radius: 8px;\n padding: 16px 24px;\n display: flex;\n align-items: center;\n gap: 12px;\n color: white;\n font-size: 14px;\n font-weight: 500;\n }\n .loading-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid rgba(255, 255, 255, 0.2);\n border-left: 2px solid #fff;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n }\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n .loading-message {\n font-size: 12px;\n opacity: 0.8;\n }\n `,\n ];\n canvasRef = createRef<HTMLCanvasElement>();\n\n /**\n * Duration in milliseconds for video buffering ahead of current time\n * @domAttribute \"video-buffer-duration\"\n */\n @property({ type: Number, attribute: \"video-buffer-duration\" })\n videoBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent video segment fetches for buffering\n * @domAttribute \"max-video-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-video-buffer-fetches\" })\n maxVideoBufferFetches = 2;\n\n /**\n * Enable/disable video buffering system\n * @domAttribute \"enable-video-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-video-buffering\" })\n enableVideoBuffering = true;\n\n // ============================================================================\n // FrameRenderable Implementation\n // Centralized frame control - no more Lit Tasks\n // ============================================================================\n\n /**\n * Cached video sample for the current frame.\n * Set by prepareFrame(), consumed by renderFrame().\n */\n #cachedVideoSample: VideoSample | undefined = undefined;\n #cachedVideoSampleTimeMs: number | undefined = undefined;\n\n /**\n * Query readiness state for a given time.\n * @implements FrameRenderable\n * \n * Note: The timeMs parameter is the root timegroup's time. We check against\n * this.currentSourceTimeMs since that's what we cache in prepareFrame.\n */\n getFrameState(_timeMs: number): FrameState {\n // Use element's source time to match what prepareFrame caches\n const sourceTimeMs = this.currentSourceTimeMs;\n \n // Check if we have a cached sample for this exact source time\n const hasCache = \n this.#cachedVideoSample !== undefined && \n this.#cachedVideoSampleTimeMs === sourceTimeMs;\n\n return {\n needsPreparation: !hasCache,\n isReady: hasCache,\n priority: PRIORITY_VIDEO,\n };\n }\n\n /**\n * Async preparation - seeks video and caches the sample.\n * @implements FrameRenderable\n * \n * Note: The timeMs parameter is the root timegroup's time. We ignore it and\n * use this.currentSourceTimeMs instead, which accounts for:\n * - Our position within the parent timegroup (ownCurrentTimeMs)\n * - Source trimming (sourceIn/sourceOut or trimStart/trimEnd)\n */\n async prepareFrame(_timeMs: number, signal: AbortSignal): Promise<void> {\n signal.throwIfAborted();\n \n // Use element's source time, not the passed root timegroup time.\n // currentSourceTimeMs = ownCurrentTimeMs + (sourceIn || trimStart || 0)\n // This correctly maps timeline position to actual media time.\n const sourceTimeMs = this.currentSourceTimeMs;\n \n const mediaEngine = await this.getMediaEngine(signal);\n if (!mediaEngine) {\n this.#cachedVideoSample = undefined;\n this.#cachedVideoSampleTimeMs = sourceTimeMs;\n return;\n }\n \n signal.throwIfAborted();\n \n // Fetch video sample at the correct source time\n // Handle errors gracefully so one failed seek doesn't break subsequent frames\n try {\n const videoSample = await this.#fetchVideoSampleForFrame(mediaEngine, sourceTimeMs, signal);\n \n signal.throwIfAborted();\n \n // Cache the result\n this.#cachedVideoSample = videoSample;\n this.#cachedVideoSampleTimeMs = sourceTimeMs;\n } catch (error) {\n // Re-throw abort errors to properly handle cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n \n // For seek errors (NoSample, out of bounds, etc.), just clear cache\n // This allows subsequent frames to retry instead of being stuck\n console.warn(`Video seek error at ${sourceTimeMs}ms:`, error);\n this.#cachedVideoSample = undefined;\n this.#cachedVideoSampleTimeMs = sourceTimeMs;\n }\n }\n\n /**\n * Synchronous render - paints cached video sample to canvas.\n * @implements FrameRenderable\n * \n * Note: The timeMs parameter is the root timegroup's time. We use \n * this.currentSourceTimeMs to match what prepareFrame cached.\n */\n renderFrame(_timeMs: number): void {\n // Use element's source time to match what was cached in prepareFrame\n const sourceTimeMs = this.currentSourceTimeMs;\n \n // Use cached sample if available for this source time\n if (this.#cachedVideoSampleTimeMs === sourceTimeMs && this.#cachedVideoSample) {\n const videoFrame = this.#cachedVideoSample.toVideoFrame();\n try {\n this.displayFrame(videoFrame, sourceTimeMs);\n } finally {\n videoFrame.close();\n }\n }\n \n // Update animations if not in parent timegroup (same as frameTask behavior)\n if (!this.parentTimegroup) {\n updateAnimations(this);\n }\n }\n\n /**\n * Fetch video sample for a given time.\n * \n * Uses a quality routing strategy:\n * - In production rendering: always use main (full quality) track\n * - In preview mode: try scrub track first for faster scrubbing, fall back to main\n * - If main track segment is already cached: use it (avoid redundant lower-quality fetch)\n */\n async #fetchVideoSampleForFrame(\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal\n ): Promise<VideoSample | undefined> {\n // FIRST: Check if main quality content is already cached - use it if so\n // This avoids redundant lower-quality fetches when main is already loaded\n const mainRendition = mediaEngine.videoRendition;\n if (mainRendition) {\n const mainSegmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n mainRendition,\n );\n if (\n mainSegmentId !== undefined &&\n mediaEngine.isSegmentCached(mainSegmentId, mainRendition)\n ) {\n return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);\n }\n }\n\n // SECOND: In production rendering mode, always use main (full quality) track\n if (this.isInProductionRenderingMode()) {\n return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);\n }\n\n // THIRD: In preview mode, try scrub track first for faster scrubbing\n const scrubRendition = mediaEngine.getScrubVideoRendition?.();\n if (scrubRendition) {\n const scrubSample = await this.#getScrubVideoSampleForFrame(\n mediaEngine,\n desiredSeekTimeMs,\n signal\n );\n if (scrubSample) {\n return scrubSample;\n }\n // Scrub track failed, fall through to main track\n }\n\n // FOURTH: Fall back to main video path\n return this.#getMainVideoSampleForFrame(mediaEngine, desiredSeekTimeMs, signal);\n }\n\n /**\n * Get scrub (low-resolution) video sample for fast preview scrubbing.\n * Used in preview mode for faster response during timeline scrubbing.\n */\n async #getScrubVideoSampleForFrame(\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal\n ): Promise<VideoSample | undefined> {\n const scrubRendition = mediaEngine.getScrubVideoRendition?.();\n if (!scrubRendition) {\n return undefined;\n }\n\n const scrubRenditionWithSrc = { ...scrubRendition, src: mediaEngine.src };\n const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, scrubRenditionWithSrc);\n if (segmentId === undefined) {\n return undefined;\n }\n\n // Get cached scrub video input or create new one\n const scrubInput = await scrubInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n async () => {\n // Fetch scrub video segment\n let initSegment: ArrayBuffer | undefined;\n let mediaSegment: ArrayBuffer | undefined;\n \n try {\n [initSegment, mediaSegment] = await Promise.all([\n mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal),\n mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, signal),\n ]);\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Return undefined for expected errors - will fall back to main track\n return undefined;\n }\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n signal.throwIfAborted();\n\n // Create combined blob and BufferedSeekingInput\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n signal.throwIfAborted();\n \n const arrayBuffer = await combinedBlob.arrayBuffer();\n signal.throwIfAborted();\n\n const { BufferedSeekingInput } = await import(\"./EFMedia/BufferedSeekingInput.js\");\n \n return new BufferedSeekingInput(\n arrayBuffer,\n {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n },\n );\n }\n );\n\n if (!scrubInput) {\n return undefined;\n }\n\n signal.throwIfAborted();\n\n const videoTrack = await scrubInput.getFirstVideoTrack();\n if (!videoTrack) {\n return undefined;\n }\n\n signal.throwIfAborted();\n\n // Cast MediaSample to VideoSample (it's a video track, so it's a VideoSample)\n return scrubInput.seek(videoTrack.id, desiredSeekTimeMs) as Promise<VideoSample | undefined>;\n }\n\n /**\n * Get main video sample for a given time.\n */\n async #getMainVideoSampleForFrame(\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal\n ): Promise<VideoSample | undefined> {\n const videoRendition = mediaEngine.getVideoRendition?.() ?? mediaEngine.videoRendition;\n if (!videoRendition) {\n return undefined;\n }\n\n const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, videoRendition);\n if (segmentId === undefined) {\n return undefined;\n }\n\n // Get cached main video input or create new one\n const mainInput = await mainVideoInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n videoRendition.id,\n async () => {\n // Fetch main video segment\n let initSegment: ArrayBuffer | undefined;\n let mediaSegment: ArrayBuffer | undefined;\n \n try {\n [initSegment, mediaSegment] = await Promise.all([\n mediaEngine.fetchInitSegment(videoRendition, signal),\n mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal),\n ]);\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Return undefined for expected errors\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n error.message.includes(\"Failed to fetch\") ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"Media segment not found\") ||\n error.message.includes(\"Init segment not found\") ||\n error.message.includes(\"Track not found\"))\n ) {\n return undefined;\n }\n throw error;\n }\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n signal.throwIfAborted();\n\n // Create combined blob and BufferedSeekingInput\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n signal.throwIfAborted();\n \n const arrayBuffer = await combinedBlob.arrayBuffer();\n signal.throwIfAborted();\n\n const { BufferedSeekingInput } = await import(\"./EFMedia/BufferedSeekingInput.js\");\n \n return new BufferedSeekingInput(\n arrayBuffer,\n {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: videoRendition.startTimeOffsetMs,\n },\n );\n }\n );\n\n if (!mainInput) {\n return undefined;\n }\n\n signal.throwIfAborted();\n\n const videoTrack = await mainInput.getFirstVideoTrack();\n if (!videoTrack) {\n return undefined;\n }\n\n signal.throwIfAborted();\n\n // Cast MediaSample to VideoSample (it's a video track, so it's a VideoSample)\n return mainInput.seek(videoTrack.id, desiredSeekTimeMs) as Promise<VideoSample | undefined>;\n }\n\n // ============================================================================\n // End FrameRenderable Implementation\n // ============================================================================\n\n /**\n * Delayed loading state manager for user feedback\n */\n #delayedLoadingState: DelayedLoadingState;\n\n /**\n * Loading state for user feedback\n */\n @state()\n loadingState = {\n isLoading: false,\n operation: null as LoadingState[\"operation\"],\n message: \"\",\n };\n\n constructor() {\n super();\n\n // Initialize delayed loading state with callback to update UI\n this.#delayedLoadingState = new DelayedLoadingState(\n 250,\n (isLoading, message) => {\n this.setLoadingState(isLoading, null, message);\n },\n );\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // No need to clear canvas - displayFrame() overwrites it completely\n // and clearing creates blank frame gaps during transitions\n }\n\n render() {\n return html`\n <canvas ${ref(this.canvasRef)}></canvas>\n ${\n this.loadingState.isLoading\n ? html`\n <div class=\"loading-overlay\">\n <div class=\"loading-content\">\n <div class=\"loading-spinner\"></div>\n <div>\n <div>Loading Video...</div>\n <div class=\"loading-message\">${this.loadingState.message}</div>\n </div>\n </div>\n </div>\n `\n : \"\"\n }\n `;\n }\n\n get canvasElement() {\n const referencedCanvas = this.canvasRef.value;\n if (referencedCanvas) {\n return referencedCanvas;\n }\n const shadowCanvas = this.shadowRoot?.querySelector(\"canvas\");\n if (shadowCanvas) {\n return shadowCanvas;\n }\n return undefined;\n }\n\n /**\n * @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.\n * This is a compatibility wrapper that delegates to the new system.\n */\n frameTask = createFrameTaskWrapper(this, {\n getTimeMs: () => this.desiredSeekTimeMs,\n });\n\n /**\n * Start a delayed loading operation for testing\n */\n startDelayedLoading(\n operationId: string,\n message: string,\n options: { background?: boolean } = {},\n ): void {\n this.#delayedLoadingState.startLoading(operationId, message, options);\n }\n\n /**\n * Clear a delayed loading operation for testing\n */\n clearDelayedLoading(operationId: string): void {\n this.#delayedLoadingState.clearLoading(operationId);\n }\n\n /**\n * Set loading state for user feedback\n */\n private setLoadingState(\n isLoading: boolean,\n operation: LoadingState[\"operation\"] = null,\n message = \"\",\n ): void {\n this.loadingState = {\n isLoading,\n operation,\n message,\n };\n }\n\n /**\n * Paint the current video frame to canvas\n * Called by frameTask after seek is complete\n */\n paint(seekToMs: number, parentSpan?: any): void {\n const parentContext = parentSpan\n ? trace.setSpan(context.active(), parentSpan)\n : undefined;\n\n withSpanSync(\n \"video.paint\",\n {\n elementId: this.id || \"unknown\",\n seekToMs,\n src: this.src || \"none\",\n },\n parentContext,\n (span) => {\n const t0 = performance.now();\n\n // Check if we're in production rendering mode vs preview mode\n const isProductionRendering = this.isInProductionRenderingMode();\n const t1 = performance.now();\n span.setAttribute(\"isProductionRendering\", isProductionRendering);\n span.setAttribute(\"modeCheckMs\", t1 - t0);\n\n // Use cached video sample from prepareFrame\n try {\n const t2 = performance.now();\n const videoSample = this.#cachedVideoSample;\n span.setAttribute(\"hasVideoSample\", !!videoSample);\n span.setAttribute(\"valueAccessMs\", t2 - t1);\n\n if (videoSample) {\n const t3 = performance.now();\n const videoFrame = videoSample.toVideoFrame();\n const t4 = performance.now();\n span.setAttribute(\"toVideoFrameMs\", t4 - t3);\n\n try {\n const t5 = performance.now();\n this.displayFrame(videoFrame, seekToMs, span);\n const t6 = performance.now();\n span.setAttribute(\"displayFrameMs\", t6 - t5);\n } finally {\n videoFrame.close();\n }\n }\n } catch (error) {\n console.warn(\"Video pipeline error:\", error);\n }\n\n // EF_FRAMEGEN-aware rendering mode detection\n if (!isProductionRendering) {\n // Check if we're in a render clone (used for thumbnails, video export, etc.)\n // Render clones should ALWAYS render, even at time 0\n const isInRenderClone = !!this.closest('.ef-render-clone-container');\n \n if (isInRenderClone) {\n span.setAttribute(\"renderClone\", true);\n }\n \n // Preview mode: skip rendering during initialization to prevent artifacts\n // BUT: Always render if we're in a render clone (for thumbnails/export)\n if (\n !isInRenderClone &&\n (!this.rootTimegroup ||\n (this.rootTimegroup.currentTimeMs === 0 &&\n this.desiredSeekTimeMs === 0))\n ) {\n span.setAttribute(\"skipped\", \"preview-initialization\");\n return; // Skip initialization frame in preview mode\n }\n // Preview mode: proceed with rendering\n } else {\n // Production rendering mode: only render when EF_FRAMEGEN has explicitly started frame rendering\n // This prevents initialization frames before the actual render sequence begins\n if (!this.rootTimegroup) {\n span.setAttribute(\"skipped\", \"no-root-timegroup\");\n return;\n }\n\n if (!this.isFrameRenderingActive()) {\n span.setAttribute(\"skipped\", \"frame-rendering-not-active\");\n return; // Wait for EF_FRAMEGEN to start frame sequence\n }\n\n // Production mode: EF_FRAMEGEN has started frame sequence, proceed with rendering\n }\n\n const tEnd = performance.now();\n span.setAttribute(\"totalPaintMs\", tEnd - t0);\n },\n );\n }\n\n /**\n * Clear the canvas when element becomes inactive\n */\n clearCanvas(): void {\n if (!this.canvasElement) return;\n\n const ctx = this.canvasElement.getContext(\"2d\");\n if (ctx) {\n ctx.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height);\n }\n }\n\n /**\n * Display a video frame on the canvas\n */\n displayFrame(frame: VideoFrame, seekToMs: number, parentSpan?: any): void {\n const parentContext = parentSpan\n ? trace.setSpan(context.active(), parentSpan)\n : undefined;\n\n withSpanSync(\n \"video.displayFrame\",\n {\n elementId: this.id || \"unknown\",\n seekToMs,\n format: frame.format || \"unknown\",\n width: frame.codedWidth,\n height: frame.codedHeight,\n },\n parentContext,\n (span) => {\n const t0 = performance.now();\n\n log(\"trace: displayFrame start\", {\n seekToMs,\n frameFormat: frame.format,\n });\n\n if (!this.canvasElement) {\n log(\"trace: displayFrame aborted - no canvas element\");\n throw new Error(\n `Frame display failed: Canvas element is not available at time ${seekToMs}ms. The video component may not be properly initialized.`,\n );\n }\n const t1 = performance.now();\n span.setAttribute(\"getCanvasMs\", Math.round((t1 - t0) * 100) / 100);\n\n const ctx = this.canvasElement.getContext(\"2d\");\n const t2 = performance.now();\n span.setAttribute(\"getCtxMs\", Math.round((t2 - t1) * 100) / 100);\n\n if (!ctx) {\n log(\"trace: displayFrame aborted - no canvas context\");\n throw new Error(\n `Frame display failed: Unable to get 2D canvas context at time ${seekToMs}ms. This may indicate a browser compatibility issue or canvas corruption.`,\n );\n }\n\n let resized = false;\n if (frame?.codedWidth && frame?.codedHeight) {\n if (\n this.canvasElement.width !== frame.codedWidth ||\n this.canvasElement.height !== frame.codedHeight\n ) {\n log(\"trace: updating canvas dimensions\", {\n width: frame.codedWidth,\n height: frame.codedHeight,\n });\n this.canvasElement.width = frame.codedWidth;\n this.canvasElement.height = frame.codedHeight;\n resized = true;\n const t3 = performance.now();\n span.setAttribute(\"resizeMs\", Math.round((t3 - t2) * 100) / 100);\n }\n }\n span.setAttribute(\"canvasResized\", resized);\n\n if (frame.format === null) {\n log(\"trace: displayFrame aborted - null frame format\");\n throw new Error(\n `Frame display failed: Video frame has null format at time ${seekToMs}ms. This indicates corrupted or incompatible video data.`,\n );\n }\n\n const tDrawStart = performance.now();\n ctx.drawImage(\n frame,\n 0,\n 0,\n this.canvasElement.width,\n this.canvasElement.height,\n );\n const tDrawEnd = performance.now();\n span.setAttribute(\n \"drawImageMs\",\n Math.round((tDrawEnd - tDrawStart) * 100) / 100,\n );\n span.setAttribute(\n \"totalDisplayMs\",\n Math.round((tDrawEnd - t0) * 100) / 100,\n );\n span.setAttribute(\"canvasWidth\", this.canvasElement.width);\n span.setAttribute(\"canvasHeight\", this.canvasElement.height);\n\n log(\"trace: frame drawn to canvas\", { seekToMs });\n },\n );\n }\n\n /**\n * Check if we're in production rendering mode (EF_FRAMEGEN active) vs preview mode\n */\n private isInProductionRenderingMode(): boolean {\n // Check if EF_RENDERING function exists and returns true (production rendering)\n if (typeof window.EF_RENDERING === \"function\") {\n return window.EF_RENDERING();\n }\n\n // Check if workbench is in rendering mode\n const workbench = document.querySelector(\"ef-workbench\") as any;\n if (workbench?.rendering) {\n return true;\n }\n\n // Check if EF_FRAMEGEN exists and has render options (indicates active rendering)\n if (window.EF_FRAMEGEN?.renderOptions) {\n return true;\n }\n\n // Default to preview mode\n return false;\n }\n\n /**\n * Check if EF_FRAMEGEN has explicitly started frame rendering (not just initialization)\n */\n private isFrameRenderingActive(): boolean {\n if (!window.EF_FRAMEGEN?.renderOptions) {\n return false;\n }\n\n // In production mode, only render when EF_FRAMEGEN has actually begun frame sequence\n // Check if we're past the initialization phase by looking for explicit frame control\n const renderOptions = window.EF_FRAMEGEN.renderOptions;\n const renderStartTime = renderOptions.encoderOptions.fromMs;\n const currentTime = this.rootTimegroup?.currentTimeMs || 0;\n\n // We're in active frame rendering if:\n // 1. currentTime >= renderStartTime (includes the starting frame)\n return currentTime >= renderStartTime;\n }\n\n /**\n * Legacy getter for fragment index task\n * Still used by EFCaptions - maps to frameTask\n */\n get fragmentIndexTask() {\n return this.frameTask;\n }\n\n // Track in-flight waitForFrameReady to prevent duplicate calls from aborting each other\n #pendingFrameReadyTime: number | null = null;\n #pendingFrameReadyPromise: Promise<void> | null = null;\n\n /**\n * Helper method for tests: wait for the current frame to be ready\n * This encapsulates the complexity of ensuring the video has updated\n * and its frameTask has completed.\n *\n * @returns Promise that resolves when the frame is ready\n */\n async waitForFrameReady(): Promise<void> {\n // CRITICAL: Sync desiredSeekTimeMs immediately from currentSourceTimeMs\n // The update cycle may not have processed yet, but currentSourceTimeMs\n // is a getter that already reflects the correct time from the parent.\n const currentTime = this.currentSourceTimeMs;\n if (this.desiredSeekTimeMs !== currentTime) {\n this.desiredSeekTimeMs = currentTime;\n }\n \n // IDEMPOTENT: If we're already waiting for this exact time, return the existing promise\n // This prevents multiple concurrent calls from aborting each other's frameTask\n if (this.#pendingFrameReadyTime === currentTime && this.#pendingFrameReadyPromise) {\n return this.#pendingFrameReadyPromise;\n }\n \n // Start a new wait for this time\n this.#pendingFrameReadyTime = currentTime;\n this.#pendingFrameReadyPromise = this.#doWaitForFrameReady(currentTime);\n \n try {\n await this.#pendingFrameReadyPromise;\n } finally {\n // Clear the pending state when done (success or error)\n if (this.#pendingFrameReadyTime === currentTime) {\n this.#pendingFrameReadyTime = null;\n this.#pendingFrameReadyPromise = null;\n }\n }\n }\n\n async #doWaitForFrameReady(_targetTimeMs: number): Promise<void> {\n await this.updateComplete;\n \n try {\n await this.frameTask.run();\n } catch (error) {\n // AbortErrors are expected when element is disconnected or task is cancelled\n // Return gracefully instead of propagating the error\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 (isAbortError) {\n return;\n }\n throw error;\n }\n }\n\n /**\n * Capture a video frame directly at a source media timestamp.\n * Bypasses the frameTask system - designed for export/rendering.\n * Does NOT paint to the element's internal canvas.\n * \n * Uses the same routing logic as unified video system:\n * - \"auto\": main track for production rendering, follows normal routing otherwise\n * - \"scrub\": force low-res scrub track (for thumbnails)\n * - \"main\": force full-quality main track\n * \n * @param sourceTimeMs - Timestamp in source media coordinates (not timeline)\n * @param options - Capture options including quality and abort signal\n * @returns Frame data for serialization\n * @public\n */\n async captureFrameAtSourceTime(\n sourceTimeMs: number,\n options: {\n quality?: \"auto\" | \"scrub\" | \"main\";\n signal?: AbortSignal;\n } = {}\n ): Promise<{\n dataUrl: string;\n width: number;\n height: number;\n }> {\n const { quality = \"auto\", signal } = options;\n \n // Check abort before starting\n signal?.throwIfAborted();\n\n // 1. Get media engine\n const mediaEngine = await this.getMediaEngine(signal);\n signal?.throwIfAborted();\n \n if (!mediaEngine) {\n throw new Error(\"No media engine available for frame capture\");\n }\n\n // 2. Determine which track to use\n const useMainTrack = quality === \"main\" || \n (quality === \"auto\" && this.isInProductionRenderingMode());\n\n // 3. Get video sample using the same logic as frame preparation\n // NOTE: BufferedSeekingInput is created per-call. This is intentional for now\n // as caching would require careful lifecycle management.\n let videoSample: any;\n \n // Import BufferedSeekingInput upfront for use in cache factory functions\n const { BufferedSeekingInput } = await import(\"./EFMedia/BufferedSeekingInput.js\");\n signal?.throwIfAborted();\n \n if (useMainTrack) {\n // Use main video track with caching\n const videoRendition = mediaEngine.getVideoRendition?.() || mediaEngine.videoRendition;\n if (!videoRendition) {\n throw new Error(\"No video rendition available\");\n }\n\n const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, videoRendition);\n if (segmentId === undefined) {\n throw new Error(`Cannot compute segment ID for time ${sourceTimeMs}ms`);\n }\n\n // Use shared cache for BufferedSeekingInput - avoids recreating for same segment\n const seekingInput = await mainVideoInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n videoRendition.id,\n async () => {\n const fetchSignal = signal ?? new AbortController().signal;\n const [initSegment, mediaSegment] = await Promise.all([\n mediaEngine.fetchInitSegment(videoRendition, fetchSignal),\n mediaEngine.fetchMediaSegment(segmentId, videoRendition, fetchSignal),\n ]);\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n const arrayBuffer = await combinedBlob.arrayBuffer();\n\n return new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: videoRendition.startTimeOffsetMs,\n });\n }\n );\n signal?.throwIfAborted();\n\n if (!seekingInput) {\n throw new Error(`Failed to fetch video segments for time ${sourceTimeMs}ms`);\n }\n\n const videoTrack = await seekingInput.getFirstVideoTrack();\n signal?.throwIfAborted();\n \n if (!videoTrack) {\n throw new Error(\"No video track found in segment\");\n }\n\n videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);\n signal?.throwIfAborted();\n } else {\n // Use scrub track for thumbnails/preview with caching\n const scrubRendition = mediaEngine.getScrubVideoRendition?.();\n if (!scrubRendition) {\n // Fall back to main track if no scrub available\n return this.captureFrameAtSourceTime(sourceTimeMs, { quality: \"main\", signal });\n }\n\n const scrubRenditionWithSrc = { ...scrubRendition, src: mediaEngine.src };\n const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, scrubRenditionWithSrc);\n \n if (segmentId === undefined) {\n throw new Error(`Cannot compute scrub segment ID for time ${sourceTimeMs}ms`);\n }\n\n // Use shared cache for BufferedSeekingInput\n // Include mediaEngine.src in cache key to prevent collisions between different videos\n const seekingInput = await scrubInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n async () => {\n const scrubFetchSignal = signal ?? new AbortController().signal;\n const [initSegment, mediaSegment] = await Promise.all([\n mediaEngine.fetchInitSegment(scrubRenditionWithSrc, scrubFetchSignal),\n mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, scrubFetchSignal),\n ]);\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n const arrayBuffer = await combinedBlob.arrayBuffer();\n\n return new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n });\n }\n );\n signal?.throwIfAborted();\n\n if (!seekingInput) {\n // Fall back to main track\n return this.captureFrameAtSourceTime(sourceTimeMs, { quality: \"main\", signal });\n }\n\n const videoTrack = await seekingInput.getFirstVideoTrack();\n signal?.throwIfAborted();\n \n if (!videoTrack) {\n // Fall back to main track\n return this.captureFrameAtSourceTime(sourceTimeMs, { quality: \"main\", signal });\n }\n\n videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);\n signal?.throwIfAborted();\n }\n\n if (!videoSample) {\n throw new Error(`No video sample found at ${sourceTimeMs}ms`);\n }\n\n // 4. Convert to VideoFrame\n const videoFrame = videoSample.toVideoFrame();\n\n try {\n signal?.throwIfAborted();\n \n // 5. Draw to OffscreenCanvas and encode to dataURL\n const canvas = new OffscreenCanvas(videoFrame.codedWidth, videoFrame.codedHeight);\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n throw new Error(\"Failed to get 2d context from OffscreenCanvas\");\n }\n ctx.drawImage(videoFrame, 0, 0);\n\n signal?.throwIfAborted();\n\n // Encode to JPEG blob\n const blob = await canvas.convertToBlob({ type: \"image/jpeg\", quality: 0.92 });\n signal?.throwIfAborted();\n \n // Convert blob to dataURL\n const dataUrl = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n signal?.throwIfAborted();\n\n return {\n dataUrl,\n width: videoFrame.codedWidth,\n height: videoFrame.codedHeight,\n };\n } finally {\n videoFrame.close();\n }\n }\n\n /**\n * Pre-fetch scrub segments for given timestamps.\n * Loads 30-second segments sequentially, emitting progress events.\n * This ensures scrub track is cached for fast thumbnail generation.\n *\n * @param timestamps - Array of timestamps (in ms) that will be captured\n * @param onProgress - Optional callback for loading progress\n * @returns Promise that resolves when all segments are cached\n * @public\n */\n async prefetchScrubSegments(\n timestamps: number[],\n onProgress?: (loaded: number, total: number, segmentTimeRange: [number, number]) => void,\n ): Promise<void> {\n // Wait for media engine to be ready\n const mediaEngine = await this.getMediaEngine();\n if (!mediaEngine) {\n log(\"prefetchScrubSegments: no media engine available\");\n return;\n }\n\n // Get scrub rendition\n const scrubRendition = mediaEngine.getScrubVideoRendition();\n if (!scrubRendition) {\n log(\"prefetchScrubSegments: no scrub rendition available\");\n return;\n }\n\n const scrubRenditionWithSrc = {\n ...scrubRendition,\n src: mediaEngine.src,\n };\n\n // Compute unique segment IDs needed for all timestamps\n const segmentIds = new Set<number>();\n for (const ts of timestamps) {\n const segmentId = mediaEngine.computeSegmentId(ts, scrubRenditionWithSrc);\n if (segmentId !== undefined) {\n segmentIds.add(segmentId);\n }\n }\n\n if (segmentIds.size === 0) {\n log(\"prefetchScrubSegments: no segments to prefetch\");\n return;\n }\n\n // For AssetMediaEngine, the scrub track is a single file (not segmented).\n // We just need to fetch it once, and all segments become cached.\n // Check if ANY segment is already cached (meaning the file is loaded).\n const firstSegmentId = Array.from(segmentIds)[0]!;\n if (mediaEngine.isSegmentCached(firstSegmentId, scrubRenditionWithSrc)) {\n log(\"prefetchScrubSegments: scrub track already cached\");\n return;\n }\n\n log(`prefetchScrubSegments: fetching scrub track for ${segmentIds.size} segments...`);\n\n // Emit loading event for the entire duration\n const durationMs = mediaEngine.durationMs || 0;\n this.dispatchEvent(\n new CustomEvent(\"scrub-segment-loading\", {\n detail: {\n segmentId: 0,\n timeRangeMs: [0, durationMs] as [number, number],\n loaded: 0,\n total: 1,\n status: \"loading\",\n },\n bubbles: true,\n composed: true,\n }),\n );\n\n // Fetch the scrub track (single file for all segments)\n try {\n await mediaEngine.fetchMediaSegment(firstSegmentId, scrubRenditionWithSrc);\n log(`prefetchScrubSegments: scrub track loaded`);\n } catch (error) {\n log(`prefetchScrubSegments: failed to load scrub track`, error);\n }\n\n // Emit loaded event\n this.dispatchEvent(\n new CustomEvent(\"scrub-segment-loading\", {\n detail: {\n segmentId: 0,\n timeRangeMs: [0, durationMs] as [number, number],\n loaded: 1,\n total: 1,\n status: \"loaded\",\n },\n bubbles: true,\n composed: true,\n }),\n );\n\n // Report progress\n onProgress?.(1, 1, [0, durationMs]);\n log(`prefetchScrubSegments: complete`);\n }\n\n /**\n * Pre-fetch main video segments for given timestamps.\n * This ensures segments are cached for fast seeking during video export.\n *\n * @param timestamps - Array of timestamps (in ms) that will be captured\n * @param onProgress - Optional callback for loading progress\n * @returns Promise that resolves when all segments are cached\n * @public\n */\n async prefetchMainVideoSegments(\n timestamps: number[],\n onProgress?: (loaded: number, total: number) => void,\n ): Promise<void> {\n // Wait for media engine to be ready\n const mediaEngine = await this.getMediaEngine();\n if (!mediaEngine) {\n log(\"prefetchMainVideoSegments: no media engine available\");\n return;\n }\n\n // Get main video rendition\n const videoRendition = mediaEngine.getVideoRendition?.() || mediaEngine.videoRendition;\n if (!videoRendition) {\n log(\"prefetchMainVideoSegments: no video rendition available\");\n return;\n }\n\n // Compute unique segment IDs needed for all timestamps\n const segmentIds = new Set<number>();\n for (const ts of timestamps) {\n const segmentId = mediaEngine.computeSegmentId(ts, videoRendition);\n if (segmentId !== undefined) {\n segmentIds.add(segmentId);\n }\n }\n\n if (segmentIds.size === 0) {\n log(\"prefetchMainVideoSegments: no segments to prefetch\");\n return;\n }\n\n // Filter to segments not already cached\n const uncachedSegmentIds: number[] = [];\n for (const segmentId of segmentIds) {\n if (!mediaEngine.isSegmentCached(segmentId, videoRendition)) {\n uncachedSegmentIds.push(segmentId);\n }\n }\n\n if (uncachedSegmentIds.length === 0) {\n log(\"prefetchMainVideoSegments: all segments already cached\");\n onProgress?.(segmentIds.size, segmentIds.size);\n return;\n }\n\n log(`prefetchMainVideoSegments: fetching ${uncachedSegmentIds.length} segments...`);\n\n // Fetch init segment first (needed for all media segments)\n try {\n await (mediaEngine as any).fetchInitSegment(videoRendition);\n } catch (error) {\n log(\"prefetchMainVideoSegments: failed to fetch init segment\", error);\n return;\n }\n\n // Fetch media segments sequentially to avoid overwhelming the network\n let loaded = segmentIds.size - uncachedSegmentIds.length;\n for (const segmentId of uncachedSegmentIds) {\n try {\n await mediaEngine.fetchMediaSegment(segmentId, videoRendition);\n loaded++;\n onProgress?.(loaded, segmentIds.size);\n } catch (error) {\n log(`prefetchMainVideoSegments: failed to fetch segment ${segmentId}`, error);\n // Continue with other segments\n }\n }\n\n log(`prefetchMainVideoSegments: complete (${loaded}/${segmentIds.size} segments)`);\n }\n\n /**\n * Clean up resources when component is disconnected\n */\n disconnectedCallback(): void {\n super.disconnectedCallback();\n\n // Clean up delayed loading state\n this.#delayedLoadingState.clearAllLoading();\n }\n\n didBecomeRoot() {\n super.didBecomeRoot();\n }\n didBecomeChild() {\n super.didBecomeChild();\n }\n\n /**\n * Get the natural dimensions of the video (coded width and height).\n * Returns null if the video hasn't loaded yet or canvas isn't available.\n *\n * @public\n */\n getNaturalDimensions(): { width: number; height: number } | null {\n const canvas = this.canvasElement;\n if (!canvas || canvas.width === 0 || canvas.height === 0) {\n return null;\n }\n return {\n width: canvas.width,\n height: canvas.height,\n };\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-video\": EFVideo;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,MAAM,sBAAsB,IAAI,qBAAqB;AACrD,MAAM,kBAAkB,IAAI,iBAAiB;AAO7C,MAAM,MAAM,MAAM,sBAAsB;AA0BjC,oBAAMA,kBAAgB,QAAQ,QAAQ,CAA4B;;gBACvD,CACd,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA0DJ;;;;;;CAiCD,qBAA8C;CAC9C,2BAA+C;;;;;;;;CAS/C,cAAc,SAA6B;EAEzC,MAAM,eAAe,KAAK;EAG1B,MAAM,WACJ,MAAKC,sBAAuB,UAC5B,MAAKC,4BAA6B;AAEpC,SAAO;GACL,kBAAkB,CAAC;GACnB,SAAS;GACT,UAAU;GACX;;;;;;;;;;;CAYH,MAAM,aAAa,SAAiB,QAAoC;AACtE,SAAO,gBAAgB;EAKvB,MAAM,eAAe,KAAK;EAE1B,MAAM,cAAc,MAAM,KAAK,eAAe,OAAO;AACrD,MAAI,CAAC,aAAa;AAChB,SAAKD,oBAAqB;AAC1B,SAAKC,0BAA2B;AAChC;;AAGF,SAAO,gBAAgB;AAIvB,MAAI;GACF,MAAM,cAAc,MAAM,MAAKC,yBAA0B,aAAa,cAAc,OAAO;AAE3F,UAAO,gBAAgB;AAGvB,SAAKF,oBAAqB;AAC1B,SAAKC,0BAA2B;WACzB,OAAO;AAEd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAKR,WAAQ,KAAK,uBAAuB,aAAa,MAAM,MAAM;AAC7D,SAAKD,oBAAqB;AAC1B,SAAKC,0BAA2B;;;;;;;;;;CAWpC,YAAY,SAAuB;EAEjC,MAAM,eAAe,KAAK;AAG1B,MAAI,MAAKA,4BAA6B,gBAAgB,MAAKD,mBAAoB;GAC7E,MAAM,aAAa,MAAKA,kBAAmB,cAAc;AACzD,OAAI;AACF,SAAK,aAAa,YAAY,aAAa;aACnC;AACR,eAAW,OAAO;;;AAKtB,MAAI,CAAC,KAAK,gBACR,kBAAiB,KAAK;;;;;;;;;;CAY1B,OAAME,yBACJ,aACA,mBACA,QACkC;EAGlC,MAAM,gBAAgB,YAAY;AAClC,MAAI,eAAe;GACjB,MAAM,gBAAgB,YAAY,iBAChC,mBACA,cACD;AACD,OACE,kBAAkB,UAClB,YAAY,gBAAgB,eAAe,cAAc,CAEzD,QAAO,MAAKC,2BAA4B,aAAa,mBAAmB,OAAO;;AAKnF,MAAI,KAAK,6BAA6B,CACpC,QAAO,MAAKA,2BAA4B,aAAa,mBAAmB,OAAO;AAKjF,MADuB,YAAY,0BAA0B,EACzC;GAClB,MAAM,cAAc,MAAM,MAAKC,4BAC7B,aACA,mBACA,OACD;AACD,OAAI,YACF,QAAO;;AAMX,SAAO,MAAKD,2BAA4B,aAAa,mBAAmB,OAAO;;;;;;CAOjF,OAAMC,4BACJ,aACA,mBACA,QACkC;EAClC,MAAM,iBAAiB,YAAY,0BAA0B;AAC7D,MAAI,CAAC,eACH;EAGF,MAAM,wBAAwB;GAAE,GAAG;GAAgB,KAAK,YAAY;GAAK;EACzE,MAAM,YAAY,YAAY,iBAAiB,mBAAmB,sBAAsB;AACxF,MAAI,cAAc,OAChB;EAIF,MAAM,aAAa,MAAM,gBAAgB,iBACvC,YAAY,KACZ,WACA,YAAY;GAEV,IAAIC;GACJ,IAAIC;AAEJ,OAAI;AACF,KAAC,aAAa,gBAAgB,MAAM,QAAQ,IAAI,CAC9C,YAAY,iBAAiB,uBAAuB,OAAO,EAC3D,YAAY,kBAAkB,WAAW,uBAAuB,OAAO,CACxE,CAAC;YACK,OAAO;AAEd,QAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAGR;;AAGF,OAAI,CAAC,eAAe,CAAC,aACnB;AAEF,UAAO,gBAAgB;GAGvB,MAAM,eAAe,IAAI,KAAK,CAAC,aAAa,aAAa,CAAC;AAC1D,UAAO,gBAAgB;GAEvB,MAAM,cAAc,MAAM,aAAa,aAAa;AACpD,UAAO,gBAAgB;GAEvB,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAE9C,UAAO,IAAI,qBACT,aACA;IACE,iBAAiB,QAAQ;IACzB,iBAAiB,QAAQ;IACzB,mBAAmB,eAAe;IACnC,CACF;IAEJ;AAED,MAAI,CAAC,WACH;AAGF,SAAO,gBAAgB;EAEvB,MAAM,aAAa,MAAM,WAAW,oBAAoB;AACxD,MAAI,CAAC,WACH;AAGF,SAAO,gBAAgB;AAGvB,SAAO,WAAW,KAAK,WAAW,IAAI,kBAAkB;;;;;CAM1D,OAAMH,2BACJ,aACA,mBACA,QACkC;EAClC,MAAM,iBAAiB,YAAY,qBAAqB,IAAI,YAAY;AACxE,MAAI,CAAC,eACH;EAGF,MAAM,YAAY,YAAY,iBAAiB,mBAAmB,eAAe;AACjF,MAAI,cAAc,OAChB;EAIF,MAAM,YAAY,MAAM,oBAAoB,iBAC1C,YAAY,KACZ,WACA,eAAe,IACf,YAAY;GAEV,IAAIE;GACJ,IAAIC;AAEJ,OAAI;AACF,KAAC,aAAa,gBAAgB,MAAM,QAAQ,IAAI,CAC9C,YAAY,iBAAiB,gBAAgB,OAAO,EACpD,YAAY,kBAAkB,WAAW,gBAAgB,OAAO,CACjE,CAAC;YACK,OAAO;AAEd,QAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAGR,QACE,iBAAiB,UAChB,MAAM,QAAQ,SAAS,MAAM,IAC5B,MAAM,QAAQ,SAAS,eAAe,IACtC,MAAM,QAAQ,SAAS,kBAAkB,IACzC,MAAM,QAAQ,SAAS,iBAAiB,IACxC,MAAM,QAAQ,SAAS,0BAA0B,IACjD,MAAM,QAAQ,SAAS,yBAAyB,IAChD,MAAM,QAAQ,SAAS,kBAAkB,EAE3C;AAEF,UAAM;;AAGR,OAAI,CAAC,eAAe,CAAC,aACnB;AAEF,UAAO,gBAAgB;GAGvB,MAAM,eAAe,IAAI,KAAK,CAAC,aAAa,aAAa,CAAC;AAC1D,UAAO,gBAAgB;GAEvB,MAAM,cAAc,MAAM,aAAa,aAAa;AACpD,UAAO,gBAAgB;GAEvB,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAE9C,UAAO,IAAI,qBACT,aACA;IACE,iBAAiB,QAAQ;IACzB,iBAAiB,QAAQ;IACzB,mBAAmB,eAAe;IACnC,CACF;IAEJ;AAED,MAAI,CAAC,UACH;AAGF,SAAO,gBAAgB;EAEvB,MAAM,aAAa,MAAM,UAAU,oBAAoB;AACvD,MAAI,CAAC,WACH;AAGF,SAAO,gBAAgB;AAGvB,SAAO,UAAU,KAAK,WAAW,IAAI,kBAAkB;;;;;CAUzD;CAYA,cAAc;AACZ,SAAO;mBApYG,WAA8B;+BAOlB;+BAOA;8BAOD;sBAwWR;GACb,WAAW;GACX,WAAW;GACX,SAAS;GACV;mBA4DW,uBAAuB,MAAM,EACvC,iBAAiB,KAAK,mBACvB,CAAC;AAxDA,QAAKC,sBAAuB,IAAI,oBAC9B,MACC,WAAW,YAAY;AACtB,QAAK,gBAAgB,WAAW,MAAM,QAAQ;IAEjD;;CAGH,AAAU,QACR,mBACM;AACN,QAAM,QAAQ,kBAAkB;;CAMlC,SAAS;AACP,SAAO,IAAI;gBACC,IAAI,KAAK,UAAU,CAAC;QAE5B,KAAK,aAAa,YACd,IAAI;;;;;;6CAM6B,KAAK,aAAa,QAAQ;;;;UAK3D,GACL;;;CAIL,IAAI,gBAAgB;EAClB,MAAM,mBAAmB,KAAK,UAAU;AACxC,MAAI,iBACF,QAAO;EAET,MAAM,eAAe,KAAK,YAAY,cAAc,SAAS;AAC7D,MAAI,aACF,QAAO;;;;;CAgBX,oBACE,aACA,SACA,UAAoC,EAAE,EAChC;AACN,QAAKA,oBAAqB,aAAa,aAAa,SAAS,QAAQ;;;;;CAMvE,oBAAoB,aAA2B;AAC7C,QAAKA,oBAAqB,aAAa,YAAY;;;;;CAMrD,AAAQ,gBACN,WACA,YAAuC,MACvC,UAAU,IACJ;AACN,OAAK,eAAe;GAClB;GACA;GACA;GACD;;;;;;CAOH,MAAM,UAAkB,YAAwB;EAC9C,MAAM,gBAAgB,aAClB,MAAM,QAAQ,QAAQ,QAAQ,EAAE,WAAW,GAC3C;AAEJ,eACE,eACA;GACE,WAAW,KAAK,MAAM;GACtB;GACA,KAAK,KAAK,OAAO;GAClB,EACD,gBACC,SAAS;GACR,MAAM,KAAK,YAAY,KAAK;GAG5B,MAAM,wBAAwB,KAAK,6BAA6B;GAChE,MAAM,KAAK,YAAY,KAAK;AAC5B,QAAK,aAAa,yBAAyB,sBAAsB;AACjE,QAAK,aAAa,eAAe,KAAK,GAAG;AAGzC,OAAI;IACF,MAAM,KAAK,YAAY,KAAK;IAC5B,MAAM,cAAc,MAAKP;AACzB,SAAK,aAAa,kBAAkB,CAAC,CAAC,YAAY;AAClD,SAAK,aAAa,iBAAiB,KAAK,GAAG;AAE3C,QAAI,aAAa;KACf,MAAM,KAAK,YAAY,KAAK;KAC5B,MAAM,aAAa,YAAY,cAAc;KAC7C,MAAM,KAAK,YAAY,KAAK;AAC5B,UAAK,aAAa,kBAAkB,KAAK,GAAG;AAE5C,SAAI;MACF,MAAM,KAAK,YAAY,KAAK;AAC5B,WAAK,aAAa,YAAY,UAAU,KAAK;MAC7C,MAAM,KAAK,YAAY,KAAK;AAC5B,WAAK,aAAa,kBAAkB,KAAK,GAAG;eACpC;AACR,iBAAW,OAAO;;;YAGf,OAAO;AACd,YAAQ,KAAK,yBAAyB,MAAM;;AAI9C,OAAI,CAAC,uBAAuB;IAG1B,MAAM,kBAAkB,CAAC,CAAC,KAAK,QAAQ,6BAA6B;AAEpE,QAAI,gBACF,MAAK,aAAa,eAAe,KAAK;AAKxC,QACE,CAAC,oBACA,CAAC,KAAK,iBACN,KAAK,cAAc,kBAAkB,KACpC,KAAK,sBAAsB,IAC7B;AACA,UAAK,aAAa,WAAW,yBAAyB;AACtD;;UAGG;AAGL,QAAI,CAAC,KAAK,eAAe;AACvB,UAAK,aAAa,WAAW,oBAAoB;AACjD;;AAGF,QAAI,CAAC,KAAK,wBAAwB,EAAE;AAClC,UAAK,aAAa,WAAW,6BAA6B;AAC1D;;;GAMJ,MAAM,OAAO,YAAY,KAAK;AAC9B,QAAK,aAAa,gBAAgB,OAAO,GAAG;IAE/C;;;;;CAMH,cAAoB;AAClB,MAAI,CAAC,KAAK,cAAe;EAEzB,MAAM,MAAM,KAAK,cAAc,WAAW,KAAK;AAC/C,MAAI,IACF,KAAI,UAAU,GAAG,GAAG,KAAK,cAAc,OAAO,KAAK,cAAc,OAAO;;;;;CAO5E,aAAa,OAAmB,UAAkB,YAAwB;EACxE,MAAM,gBAAgB,aAClB,MAAM,QAAQ,QAAQ,QAAQ,EAAE,WAAW,GAC3C;AAEJ,eACE,sBACA;GACE,WAAW,KAAK,MAAM;GACtB;GACA,QAAQ,MAAM,UAAU;GACxB,OAAO,MAAM;GACb,QAAQ,MAAM;GACf,EACD,gBACC,SAAS;GACR,MAAM,KAAK,YAAY,KAAK;AAE5B,OAAI,6BAA6B;IAC/B;IACA,aAAa,MAAM;IACpB,CAAC;AAEF,OAAI,CAAC,KAAK,eAAe;AACvB,QAAI,kDAAkD;AACtD,UAAM,IAAI,MACR,iEAAiE,SAAS,0DAC3E;;GAEH,MAAM,KAAK,YAAY,KAAK;AAC5B,QAAK,aAAa,eAAe,KAAK,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI;GAEnE,MAAM,MAAM,KAAK,cAAc,WAAW,KAAK;GAC/C,MAAM,KAAK,YAAY,KAAK;AAC5B,QAAK,aAAa,YAAY,KAAK,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAEhE,OAAI,CAAC,KAAK;AACR,QAAI,kDAAkD;AACtD,UAAM,IAAI,MACR,iEAAiE,SAAS,2EAC3E;;GAGH,IAAI,UAAU;AACd,OAAI,OAAO,cAAc,OAAO,aAC9B;QACE,KAAK,cAAc,UAAU,MAAM,cACnC,KAAK,cAAc,WAAW,MAAM,aACpC;AACA,SAAI,qCAAqC;MACvC,OAAO,MAAM;MACb,QAAQ,MAAM;MACf,CAAC;AACF,UAAK,cAAc,QAAQ,MAAM;AACjC,UAAK,cAAc,SAAS,MAAM;AAClC,eAAU;KACV,MAAM,KAAK,YAAY,KAAK;AAC5B,UAAK,aAAa,YAAY,KAAK,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI;;;AAGpE,QAAK,aAAa,iBAAiB,QAAQ;AAE3C,OAAI,MAAM,WAAW,MAAM;AACzB,QAAI,kDAAkD;AACtD,UAAM,IAAI,MACR,6DAA6D,SAAS,0DACvE;;GAGH,MAAM,aAAa,YAAY,KAAK;AACpC,OAAI,UACF,OACA,GACA,GACA,KAAK,cAAc,OACnB,KAAK,cAAc,OACpB;GACD,MAAM,WAAW,YAAY,KAAK;AAClC,QAAK,aACH,eACA,KAAK,OAAO,WAAW,cAAc,IAAI,GAAG,IAC7C;AACD,QAAK,aACH,kBACA,KAAK,OAAO,WAAW,MAAM,IAAI,GAAG,IACrC;AACD,QAAK,aAAa,eAAe,KAAK,cAAc,MAAM;AAC1D,QAAK,aAAa,gBAAgB,KAAK,cAAc,OAAO;AAE5D,OAAI,gCAAgC,EAAE,UAAU,CAAC;IAEpD;;;;;CAMH,AAAQ,8BAAuC;AAE7C,MAAI,OAAO,OAAO,iBAAiB,WACjC,QAAO,OAAO,cAAc;AAK9B,MADkB,SAAS,cAAc,eAAe,EACzC,UACb,QAAO;AAIT,MAAI,OAAO,aAAa,cACtB,QAAO;AAIT,SAAO;;;;;CAMT,AAAQ,yBAAkC;AACxC,MAAI,CAAC,OAAO,aAAa,cACvB,QAAO;EAMT,MAAM,kBADgB,OAAO,YAAY,cACH,eAAe;AAKrD,UAJoB,KAAK,eAAe,iBAAiB,MAInC;;;;;;CAOxB,IAAI,oBAAoB;AACtB,SAAO,KAAK;;CAId,yBAAwC;CACxC,4BAAkD;;;;;;;;CASlD,MAAM,oBAAmC;EAIvC,MAAM,cAAc,KAAK;AACzB,MAAI,KAAK,sBAAsB,YAC7B,MAAK,oBAAoB;AAK3B,MAAI,MAAKQ,0BAA2B,eAAe,MAAKC,yBACtD,QAAO,MAAKA;AAId,QAAKD,wBAAyB;AAC9B,QAAKC,2BAA4B,MAAKC,oBAAqB,YAAY;AAEvE,MAAI;AACF,SAAM,MAAKD;YACH;AAER,OAAI,MAAKD,0BAA2B,aAAa;AAC/C,UAAKA,wBAAyB;AAC9B,UAAKC,2BAA4B;;;;CAKvC,OAAMC,oBAAqB,eAAsC;AAC/D,QAAM,KAAK;AAEX,MAAI;AACF,SAAM,KAAK,UAAU,KAAK;WACnB,OAAO;AAWd,OAPE,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UACf,MAAM,SAAS,gBACf,MAAM,SAAS,SAAS,oBAAoB,IAC5C,MAAM,SAAS,SAAS,6BAA6B,EAIvD;AAEF,SAAM;;;;;;;;;;;;;;;;;;CAmBV,MAAM,yBACJ,cACA,UAGI,EAAE,EAKL;EACD,MAAM,EAAE,UAAU,QAAQ,WAAW;AAGrC,UAAQ,gBAAgB;EAGxB,MAAM,cAAc,MAAM,KAAK,eAAe,OAAO;AACrD,UAAQ,gBAAgB;AAExB,MAAI,CAAC,YACH,OAAM,IAAI,MAAM,8CAA8C;EAIhE,MAAM,eAAe,YAAY,UAC9B,YAAY,UAAU,KAAK,6BAA6B;EAK3D,IAAIC;EAGJ,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAC9C,UAAQ,gBAAgB;AAExB,MAAI,cAAc;GAEhB,MAAM,iBAAiB,YAAY,qBAAqB,IAAI,YAAY;AACxE,OAAI,CAAC,eACH,OAAM,IAAI,MAAM,+BAA+B;GAGjD,MAAM,YAAY,YAAY,iBAAiB,cAAc,eAAe;AAC5E,OAAI,cAAc,OAChB,OAAM,IAAI,MAAM,sCAAsC,aAAa,IAAI;GAIzE,MAAM,eAAe,MAAM,oBAAoB,iBAC7C,YAAY,KACZ,WACA,eAAe,IACf,YAAY;IACV,MAAM,cAAc,UAAU,IAAI,iBAAiB,CAAC;IACpD,MAAM,CAAC,aAAa,gBAAgB,MAAM,QAAQ,IAAI,CACpD,YAAY,iBAAiB,gBAAgB,YAAY,EACzD,YAAY,kBAAkB,WAAW,gBAAgB,YAAY,CACtE,CAAC;AAEF,QAAI,CAAC,eAAe,CAAC,aACnB;AAMF,WAAO,IAAI,qBAFS,MADC,IAAI,KAAK,CAAC,aAAa,aAAa,CAAC,CACnB,aAAa,EAEP;KAC3C,iBAAiB,QAAQ;KACzB,iBAAiB,QAAQ;KACzB,mBAAmB,eAAe;KACnC,CAAC;KAEL;AACD,WAAQ,gBAAgB;AAExB,OAAI,CAAC,aACH,OAAM,IAAI,MAAM,2CAA2C,aAAa,IAAI;GAG9E,MAAM,aAAa,MAAM,aAAa,oBAAoB;AAC1D,WAAQ,gBAAgB;AAExB,OAAI,CAAC,WACH,OAAM,IAAI,MAAM,kCAAkC;AAGpD,iBAAc,MAAM,aAAa,KAAK,WAAW,IAAI,aAAa;AAClE,WAAQ,gBAAgB;SACnB;GAEL,MAAM,iBAAiB,YAAY,0BAA0B;AAC7D,OAAI,CAAC,eAEH,QAAO,KAAK,yBAAyB,cAAc;IAAE,SAAS;IAAQ;IAAQ,CAAC;GAGjF,MAAM,wBAAwB;IAAE,GAAG;IAAgB,KAAK,YAAY;IAAK;GACzE,MAAM,YAAY,YAAY,iBAAiB,cAAc,sBAAsB;AAEnF,OAAI,cAAc,OAChB,OAAM,IAAI,MAAM,4CAA4C,aAAa,IAAI;GAK/E,MAAM,eAAe,MAAM,gBAAgB,iBACzC,YAAY,KACZ,WACA,YAAY;IACV,MAAM,mBAAmB,UAAU,IAAI,iBAAiB,CAAC;IACzD,MAAM,CAAC,aAAa,gBAAgB,MAAM,QAAQ,IAAI,CACpD,YAAY,iBAAiB,uBAAuB,iBAAiB,EACrE,YAAY,kBAAkB,WAAW,uBAAuB,iBAAiB,CAClF,CAAC;AAEF,QAAI,CAAC,eAAe,CAAC,aACnB;AAMF,WAAO,IAAI,qBAFS,MADC,IAAI,KAAK,CAAC,aAAa,aAAa,CAAC,CACnB,aAAa,EAEP;KAC3C,iBAAiB,QAAQ;KACzB,iBAAiB,QAAQ;KACzB,mBAAmB,eAAe;KACnC,CAAC;KAEL;AACD,WAAQ,gBAAgB;AAExB,OAAI,CAAC,aAEH,QAAO,KAAK,yBAAyB,cAAc;IAAE,SAAS;IAAQ;IAAQ,CAAC;GAGjF,MAAM,aAAa,MAAM,aAAa,oBAAoB;AAC1D,WAAQ,gBAAgB;AAExB,OAAI,CAAC,WAEH,QAAO,KAAK,yBAAyB,cAAc;IAAE,SAAS;IAAQ;IAAQ,CAAC;AAGjF,iBAAc,MAAM,aAAa,KAAK,WAAW,IAAI,aAAa;AAClE,WAAQ,gBAAgB;;AAG1B,MAAI,CAAC,YACH,OAAM,IAAI,MAAM,4BAA4B,aAAa,IAAI;EAI/D,MAAM,aAAa,YAAY,cAAc;AAE7C,MAAI;AACF,WAAQ,gBAAgB;GAGxB,MAAM,SAAS,IAAI,gBAAgB,WAAW,YAAY,WAAW,YAAY;GACjF,MAAM,MAAM,OAAO,WAAW,KAAK;AACnC,OAAI,CAAC,IACH,OAAM,IAAI,MAAM,gDAAgD;AAElE,OAAI,UAAU,YAAY,GAAG,EAAE;AAE/B,WAAQ,gBAAgB;GAGxB,MAAM,OAAO,MAAM,OAAO,cAAc;IAAE,MAAM;IAAc,SAAS;IAAM,CAAC;AAC9E,WAAQ,gBAAgB;GAGxB,MAAM,UAAU,MAAM,IAAI,SAAiB,SAAS,WAAW;IAC7D,MAAM,SAAS,IAAI,YAAY;AAC/B,WAAO,eAAe,QAAQ,OAAO,OAAiB;AACtD,WAAO,UAAU;AACjB,WAAO,cAAc,KAAK;KAC1B;AACF,WAAQ,gBAAgB;AAExB,UAAO;IACL;IACA,OAAO,WAAW;IAClB,QAAQ,WAAW;IACpB;YACO;AACR,cAAW,OAAO;;;;;;;;;;;;;CActB,MAAM,sBACJ,YACA,YACe;EAEf,MAAM,cAAc,MAAM,KAAK,gBAAgB;AAC/C,MAAI,CAAC,aAAa;AAChB,OAAI,mDAAmD;AACvD;;EAIF,MAAM,iBAAiB,YAAY,wBAAwB;AAC3D,MAAI,CAAC,gBAAgB;AACnB,OAAI,sDAAsD;AAC1D;;EAGF,MAAM,wBAAwB;GAC5B,GAAG;GACH,KAAK,YAAY;GAClB;EAGD,MAAM,6BAAa,IAAI,KAAa;AACpC,OAAK,MAAM,MAAM,YAAY;GAC3B,MAAM,YAAY,YAAY,iBAAiB,IAAI,sBAAsB;AACzE,OAAI,cAAc,OAChB,YAAW,IAAI,UAAU;;AAI7B,MAAI,WAAW,SAAS,GAAG;AACzB,OAAI,iDAAiD;AACrD;;EAMF,MAAM,iBAAiB,MAAM,KAAK,WAAW,CAAC;AAC9C,MAAI,YAAY,gBAAgB,gBAAgB,sBAAsB,EAAE;AACtE,OAAI,oDAAoD;AACxD;;AAGF,MAAI,mDAAmD,WAAW,KAAK,cAAc;EAGrF,MAAM,aAAa,YAAY,cAAc;AAC7C,OAAK,cACH,IAAI,YAAY,yBAAyB;GACvC,QAAQ;IACN,WAAW;IACX,aAAa,CAAC,GAAG,WAAW;IAC5B,QAAQ;IACR,OAAO;IACP,QAAQ;IACT;GACD,SAAS;GACT,UAAU;GACX,CAAC,CACH;AAGD,MAAI;AACF,SAAM,YAAY,kBAAkB,gBAAgB,sBAAsB;AAC1E,OAAI,4CAA4C;WACzC,OAAO;AACd,OAAI,qDAAqD,MAAM;;AAIjE,OAAK,cACH,IAAI,YAAY,yBAAyB;GACvC,QAAQ;IACN,WAAW;IACX,aAAa,CAAC,GAAG,WAAW;IAC5B,QAAQ;IACR,OAAO;IACP,QAAQ;IACT;GACD,SAAS;GACT,UAAU;GACX,CAAC,CACH;AAGD,eAAa,GAAG,GAAG,CAAC,GAAG,WAAW,CAAC;AACnC,MAAI,kCAAkC;;;;;;;;;;;CAYxC,MAAM,0BACJ,YACA,YACe;EAEf,MAAM,cAAc,MAAM,KAAK,gBAAgB;AAC/C,MAAI,CAAC,aAAa;AAChB,OAAI,uDAAuD;AAC3D;;EAIF,MAAM,iBAAiB,YAAY,qBAAqB,IAAI,YAAY;AACxE,MAAI,CAAC,gBAAgB;AACnB,OAAI,0DAA0D;AAC9D;;EAIF,MAAM,6BAAa,IAAI,KAAa;AACpC,OAAK,MAAM,MAAM,YAAY;GAC3B,MAAM,YAAY,YAAY,iBAAiB,IAAI,eAAe;AAClE,OAAI,cAAc,OAChB,YAAW,IAAI,UAAU;;AAI7B,MAAI,WAAW,SAAS,GAAG;AACzB,OAAI,qDAAqD;AACzD;;EAIF,MAAMC,qBAA+B,EAAE;AACvC,OAAK,MAAM,aAAa,WACtB,KAAI,CAAC,YAAY,gBAAgB,WAAW,eAAe,CACzD,oBAAmB,KAAK,UAAU;AAItC,MAAI,mBAAmB,WAAW,GAAG;AACnC,OAAI,yDAAyD;AAC7D,gBAAa,WAAW,MAAM,WAAW,KAAK;AAC9C;;AAGF,MAAI,uCAAuC,mBAAmB,OAAO,cAAc;AAGnF,MAAI;AACF,SAAO,YAAoB,iBAAiB,eAAe;WACpD,OAAO;AACd,OAAI,2DAA2D,MAAM;AACrE;;EAIF,IAAI,SAAS,WAAW,OAAO,mBAAmB;AAClD,OAAK,MAAM,aAAa,mBACtB,KAAI;AACF,SAAM,YAAY,kBAAkB,WAAW,eAAe;AAC9D;AACA,gBAAa,QAAQ,WAAW,KAAK;WAC9B,OAAO;AACd,OAAI,sDAAsD,aAAa,MAAM;;AAKjF,MAAI,wCAAwC,OAAO,GAAG,WAAW,KAAK,YAAY;;;;;CAMpF,uBAA6B;AAC3B,QAAM,sBAAsB;AAG5B,QAAKL,oBAAqB,iBAAiB;;CAG7C,gBAAgB;AACd,QAAM,eAAe;;CAEvB,iBAAiB;AACf,QAAM,gBAAgB;;;;;;;;CASxB,uBAAiE;EAC/D,MAAM,SAAS,KAAK;AACpB,MAAI,CAAC,UAAU,OAAO,UAAU,KAAK,OAAO,WAAW,EACrD,QAAO;AAET,SAAO;GACL,OAAO,OAAO;GACd,QAAQ,OAAO;GAChB;;;YAlsCF,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAyB,CAAC;YAO9D,SAAS;CAAE,MAAM;CAAQ,WAAW;CAA4B,CAAC;YAOjE,SAAS;CAAE,MAAM;CAAS,WAAW;CAA0B,CAAC;YAwWhE,OAAO;sBA1bT,cAAc,WAAW"}
1
+ {"version":3,"file":"EFVideo.js","names":["#resolve","EFVideo","#currentRenditionId","#cachedVideoSample","#cachedVideoSampleTimeMs","#fetchVideoSampleForFrame","#getMainVideoSampleForFrame","#getScrubVideoSampleForFrame","#maybeScheduleQualityUpgrade","initSegment: ArrayBuffer | undefined","mediaSegment: ArrayBuffer | undefined","#delayedLoadingState","#invalidateUpgradeState","videoSample: any","#upgradeState","#computeLookaheadSegments","#fetchStandalone","results: { segmentId: number; deadlineMs: number }[]","#standaloneUpgradeController"],"sources":["../../src/elements/EFVideo.ts"],"sourcesContent":["import { context, trace } from \"@opentelemetry/api\";\nimport debug from \"debug\";\nimport { css, html, type PropertyValueMap } from \"lit\";\nimport { customElement, state } from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\nimport type { VideoSample } from \"mediabunny\";\nimport { DelayedLoadingState } from \"../DelayedLoadingState.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { withSpanSync } from \"../otel/tracingHelpers.js\";\nimport {\n type FrameRenderable,\n type FrameState,\n PRIORITY_VIDEO,\n} from \"../preview/FrameController.js\";\nimport { MainVideoInputCache } from \"./EFMedia/videoTasks/MainVideoInputCache.ts\";\nimport { ScrubInputCache } from \"./EFMedia/videoTasks/ScrubInputCache.ts\";\nimport { EFMedia } from \"./EFMedia.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\n\n// Shared caches for video seeking\nconst mainVideoInputCache = new MainVideoInputCache();\nconst scrubInputCache = new ScrubInputCache();\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 log = debug(\"ef:elements:EFVideo\");\n\ninterface LoadingState {\n isLoading: boolean;\n operation: \"scrub-segment\" | \"video-segment\" | \"seeking\" | \"decoding\" | null;\n message: string;\n}\n\n/**\n * Event detail for scrub segment loading progress.\n * Dispatched during prefetchScrubSegments to indicate network activity.\n */\nexport interface ScrubSegmentLoadingDetail {\n /** The segment ID being loaded (0-indexed) */\n segmentId: number;\n /** Time range covered by this segment [startMs, endMs] */\n timeRangeMs: [number, number];\n /** Number of segments loaded so far */\n loaded: number;\n /** Total number of segments to load */\n total: number;\n /** Current status: \"loading\" or \"loaded\" */\n status: \"loading\" | \"loaded\";\n}\n\nclass VideoSeekTask {\n value: VideoSample | undefined = undefined;\n task: ((...args: any[]) => any) | undefined = undefined;\n\n #resolve: ((v: VideoSample | undefined) => void) | undefined;\n taskComplete: Promise<VideoSample | undefined> = Promise.resolve(undefined);\n\n begin(): void {\n this.taskComplete = new Promise<VideoSample | undefined>((resolve) => {\n this.#resolve = resolve;\n });\n }\n\n complete(sample: VideoSample | undefined): void {\n this.value = sample;\n this.#resolve?.(sample);\n }\n\n abort(): void {\n this.#resolve?.(undefined);\n }\n}\n\n@customElement(\"ef-video\")\nexport class EFVideo extends TWMixin(EFMedia) implements FrameRenderable {\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n object-fit: contain;\n object-position: center;\n }\n canvas {\n overflow: hidden;\n position: static;\n width: 100%;\n height: 100%;\n object-fit: inherit;\n object-position: inherit;\n margin: 0;\n padding: 0;\n border: none;\n outline: none;\n box-shadow: none;\n }\n .loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10;\n backdrop-filter: blur(2px);\n }\n .loading-content {\n background: rgba(0, 0, 0, 0.8);\n border-radius: 8px;\n padding: 16px 24px;\n display: flex;\n align-items: center;\n gap: 12px;\n color: white;\n font-size: 14px;\n font-weight: 500;\n }\n .loading-spinner {\n width: 20px;\n height: 20px;\n border: 2px solid rgba(255, 255, 255, 0.2);\n border-left: 2px solid #fff;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n }\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n .loading-message {\n font-size: 12px;\n opacity: 0.8;\n }\n `,\n ];\n canvasRef = createRef<HTMLCanvasElement>();\n unifiedVideoSeekTask = new VideoSeekTask();\n\n // ============================================================================\n // FrameRenderable Implementation\n // Centralized frame control - no more Lit Tasks\n // ============================================================================\n\n /**\n * Cached video sample for the current frame.\n * Set by prepareFrame(), consumed by renderFrame().\n */\n #cachedVideoSample: VideoSample | undefined = undefined;\n #cachedVideoSampleTimeMs: number | undefined = undefined;\n\n /**\n * Quality upgrade intent tracking.\n * Tracks what upgrade tasks were last submitted to avoid redundant scheduler calls.\n */\n #upgradeState: {\n sourceTimeMs: number;\n segmentId: number;\n startTimeMs: number;\n submittedKeys: Set<string>;\n } | null = null;\n\n /**\n * Standalone upgrade controller for elements without a timegroup.\n */\n #standaloneUpgradeController: AbortController | null = null;\n\n /**\n * Current rendition being displayed (for observability).\n */\n #currentRenditionId: \"main\" | \"scrub\" | undefined = undefined;\n\n /**\n * Get the current rendition being displayed.\n * @public\n */\n get currentRenditionId(): \"main\" | \"scrub\" | undefined {\n return this.#currentRenditionId;\n }\n\n /**\n * Query readiness state for a given time.\n * @implements FrameRenderable\n *\n * Note: The timeMs parameter is the root timegroup's time. We check against\n * this.currentSourceTimeMs since that's what we cache in prepareFrame.\n */\n getFrameState(_timeMs: number): FrameState {\n // Use element's source time to match what prepareFrame caches\n const sourceTimeMs = this.currentSourceTimeMs;\n\n // Check if we have a cached sample for this exact source time\n const hasCache =\n this.#cachedVideoSample !== undefined &&\n this.#cachedVideoSampleTimeMs === sourceTimeMs;\n\n return {\n needsPreparation: !hasCache,\n isReady: hasCache,\n priority: PRIORITY_VIDEO,\n };\n }\n\n /**\n * Async preparation - seeks video and caches the sample.\n * @implements FrameRenderable\n *\n * Note: The timeMs parameter is the root timegroup's time. We ignore it and\n * use this.currentSourceTimeMs instead, which accounts for:\n * - Our position within the parent timegroup (ownCurrentTimeMs)\n * - Source trimming (sourceIn/sourceOut or trimStart/trimEnd)\n */\n async prepareFrame(_timeMs: number, signal: AbortSignal): Promise<void> {\n signal.throwIfAborted();\n this.unifiedVideoSeekTask.begin();\n\n // Use element's source time, not the passed root timegroup time.\n // currentSourceTimeMs = ownCurrentTimeMs + (sourceIn || trimStart || 0)\n // This correctly maps timeline position to actual media time.\n const sourceTimeMs = this.currentSourceTimeMs;\n\n const mediaEngine = await this.getMediaEngine(signal);\n if (!mediaEngine) {\n this.#cachedVideoSample = undefined;\n this.#cachedVideoSampleTimeMs = sourceTimeMs;\n this.unifiedVideoSeekTask.complete(undefined);\n return;\n }\n\n signal.throwIfAborted();\n\n // Fetch video sample at the correct source time\n // Handle errors gracefully so one failed seek doesn't break subsequent frames\n try {\n const videoSample = await this.#fetchVideoSampleForFrame(\n mediaEngine,\n sourceTimeMs,\n signal,\n );\n\n signal.throwIfAborted();\n\n // Cache the result\n this.#cachedVideoSample = videoSample;\n this.#cachedVideoSampleTimeMs = sourceTimeMs;\n this.unifiedVideoSeekTask.complete(videoSample);\n } catch (error) {\n // Re-throw abort errors to properly handle cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n this.unifiedVideoSeekTask.abort();\n throw error;\n }\n\n // For seek errors (NoSample, out of bounds, etc.), just clear cache\n // This allows subsequent frames to retry instead of being stuck\n console.warn(`Video seek error at ${sourceTimeMs}ms:`, error);\n this.#cachedVideoSample = undefined;\n this.#cachedVideoSampleTimeMs = sourceTimeMs;\n this.unifiedVideoSeekTask.complete(undefined);\n }\n }\n\n /**\n * Synchronous render - paints cached video sample to canvas.\n * @implements FrameRenderable\n *\n * Note: The timeMs parameter is the root timegroup's time. We use\n * this.currentSourceTimeMs to match what prepareFrame cached.\n */\n renderFrame(_timeMs: number): void {\n // Use element's source time to match what was cached in prepareFrame\n const sourceTimeMs = this.currentSourceTimeMs;\n\n // Use cached sample if available for this source time\n if (\n this.#cachedVideoSampleTimeMs === sourceTimeMs &&\n this.#cachedVideoSample\n ) {\n const videoFrame = this.#cachedVideoSample.toVideoFrame();\n try {\n this.displayFrame(videoFrame, sourceTimeMs);\n } finally {\n videoFrame.close();\n }\n }\n\n // Update animations if not in parent timegroup\n if (!this.parentTimegroup) {\n updateAnimations(this);\n }\n }\n\n /**\n * Fetch video sample for a given time.\n *\n * Uses a quality routing strategy:\n * - In production rendering: always use main (full quality) track\n * - In preview mode: try scrub track first for faster scrubbing, fall back to main\n * - If main track segment is already cached: use it (avoid redundant lower-quality fetch)\n */\n async #fetchVideoSampleForFrame(\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal,\n ): Promise<VideoSample | undefined> {\n // FIRST: Check if main quality content is already cached - use it if so\n // This avoids redundant lower-quality fetches when main is already loaded\n const mainRendition = mediaEngine.videoRendition;\n if (mainRendition) {\n const mainSegmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n mainRendition,\n );\n if (\n mainSegmentId !== undefined &&\n mediaEngine.isSegmentCached(mainSegmentId, mainRendition)\n ) {\n this.#currentRenditionId = \"main\";\n return this.#getMainVideoSampleForFrame(\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n }\n }\n\n // SECOND: In production rendering mode, always use main (full quality) track\n if (this.isInProductionRenderingMode()) {\n this.#currentRenditionId = \"main\";\n return this.#getMainVideoSampleForFrame(\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n }\n\n // THIRD: In preview mode, try scrub track first for faster scrubbing\n const scrubRendition = mediaEngine.getScrubVideoRendition?.();\n if (scrubRendition) {\n const scrubSample = await this.#getScrubVideoSampleForFrame(\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n if (scrubSample) {\n this.#currentRenditionId = \"scrub\";\n // Got scrub - schedule background quality upgrade\n this.#maybeScheduleQualityUpgrade(mediaEngine, desiredSeekTimeMs);\n return scrubSample;\n }\n // Scrub track failed, fall through to main track\n }\n\n // FOURTH: Fall back to main video path\n this.#currentRenditionId = \"main\";\n return this.#getMainVideoSampleForFrame(\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n }\n\n /**\n * Get scrub (low-resolution) video sample for fast preview scrubbing.\n * Used in preview mode for faster response during timeline scrubbing.\n */\n async #getScrubVideoSampleForFrame(\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal,\n ): Promise<VideoSample | undefined> {\n const scrubRendition = mediaEngine.getScrubVideoRendition?.();\n if (!scrubRendition) {\n return undefined;\n }\n\n const scrubRenditionWithSrc = { ...scrubRendition, src: mediaEngine.src };\n const segmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n scrubRenditionWithSrc,\n );\n if (segmentId === undefined) {\n return undefined;\n }\n\n // Get cached scrub video input or create new one\n const scrubInput = await scrubInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n async () => {\n // Fetch scrub video segment\n let initSegment: ArrayBuffer | undefined;\n let mediaSegment: ArrayBuffer | undefined;\n\n try {\n const initP = mediaEngine.fetchInitSegment(\n scrubRenditionWithSrc,\n signal,\n );\n const mediaP = mediaEngine.fetchMediaSegment(\n segmentId,\n scrubRenditionWithSrc,\n signal,\n );\n // Suppress the \"losing\" promise in Promise.all — when one rejects first,\n // the other's rejection would be unhandled without this.\n initP.catch(() => {});\n mediaP.catch(() => {});\n [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Return undefined for expected errors - will fall back to main track\n return undefined;\n }\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n signal.throwIfAborted();\n\n // Create combined blob and BufferedSeekingInput\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n signal.throwIfAborted();\n\n const arrayBuffer = await combinedBlob.arrayBuffer();\n signal.throwIfAborted();\n\n const { BufferedSeekingInput } =\n await import(\"./EFMedia/BufferedSeekingInput.js\");\n\n return new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n });\n },\n );\n\n if (!scrubInput) {\n return undefined;\n }\n\n signal.throwIfAborted();\n\n const videoTrack = await scrubInput.getFirstVideoTrack();\n if (!videoTrack) {\n return undefined;\n }\n\n signal.throwIfAborted();\n\n // Cast MediaSample to VideoSample (it's a video track, so it's a VideoSample)\n return scrubInput.seek(videoTrack.id, desiredSeekTimeMs) as Promise<\n VideoSample | undefined\n >;\n }\n\n /**\n * Get main video sample for a given time.\n */\n async #getMainVideoSampleForFrame(\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal,\n ): Promise<VideoSample | undefined> {\n const videoRendition =\n mediaEngine.getVideoRendition?.() ?? mediaEngine.videoRendition;\n if (!videoRendition) {\n return undefined;\n }\n\n const segmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n videoRendition,\n );\n if (segmentId === undefined) {\n return undefined;\n }\n\n // Get cached main video input or create new one\n const mainInput = await mainVideoInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n videoRendition.id,\n async () => {\n // Fetch main video segment\n let initSegment: ArrayBuffer | undefined;\n let mediaSegment: ArrayBuffer | undefined;\n\n try {\n const initP = mediaEngine.fetchInitSegment(videoRendition, signal);\n const mediaP = mediaEngine.fetchMediaSegment(\n segmentId,\n videoRendition,\n signal,\n );\n // Suppress the \"losing\" promise in Promise.all — when one rejects first,\n // the other's rejection would be unhandled without this.\n initP.catch(() => {});\n mediaP.catch(() => {});\n [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Return undefined for expected errors\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n error.message.includes(\"Failed to fetch\") ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"Media segment not found\") ||\n error.message.includes(\"Init segment not found\") ||\n error.message.includes(\"Track not found\"))\n ) {\n return undefined;\n }\n throw error;\n }\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n signal.throwIfAborted();\n\n // Create combined blob and BufferedSeekingInput\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n signal.throwIfAborted();\n\n const arrayBuffer = await combinedBlob.arrayBuffer();\n signal.throwIfAborted();\n\n const { BufferedSeekingInput } =\n await import(\"./EFMedia/BufferedSeekingInput.js\");\n\n const input = new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: videoRendition.startTimeOffsetMs,\n });\n return input;\n },\n );\n\n if (!mainInput) {\n return undefined;\n }\n\n signal.throwIfAborted();\n\n const videoTrack = await mainInput.getFirstVideoTrack();\n if (!videoTrack) {\n return undefined;\n }\n\n signal.throwIfAborted();\n\n // Cast MediaSample to VideoSample (it's a video track, so it's a VideoSample)\n const sample = (await mainInput.seek(videoTrack.id, desiredSeekTimeMs)) as\n | VideoSample\n | undefined;\n return sample;\n }\n\n // ============================================================================\n // End FrameRenderable Implementation\n // ============================================================================\n\n /**\n * Delayed loading state manager for user feedback\n */\n #delayedLoadingState: DelayedLoadingState;\n\n /**\n * Loading state for user feedback\n */\n @state()\n loadingState = {\n isLoading: false,\n operation: null as LoadingState[\"operation\"],\n message: \"\",\n };\n\n constructor() {\n super();\n\n // Initialize delayed loading state with callback to update UI\n this.#delayedLoadingState = new DelayedLoadingState(\n 250,\n (isLoading, message) => {\n this.setLoadingState(isLoading, null, message);\n },\n );\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Invalidate upgrade state on src/fileId change\n if (changedProperties.has(\"src\") || changedProperties.has(\"fileId\")) {\n this.#invalidateUpgradeState(\"src-change\");\n }\n\n // Invalidate upgrade state on trim/source changes\n const durationAffectingProps = [\n \"_trimStartMs\",\n \"_trimEndMs\",\n \"_sourceInMs\",\n \"_sourceOutMs\",\n ];\n const hasDurationChange = durationAffectingProps.some((prop) =>\n changedProperties.has(prop),\n );\n if (hasDurationChange) {\n this.#invalidateUpgradeState(\"bounds-change\");\n }\n\n // No need to clear canvas - displayFrame() overwrites it completely\n // and clearing creates blank frame gaps during transitions\n }\n\n render() {\n return html`\n <canvas ${ref(this.canvasRef)}></canvas>\n ${\n this.loadingState.isLoading\n ? html`\n <div class=\"loading-overlay\">\n <div class=\"loading-content\">\n <div class=\"loading-spinner\"></div>\n <div>\n <div>Loading Video...</div>\n <div class=\"loading-message\">${this.loadingState.message}</div>\n </div>\n </div>\n </div>\n `\n : \"\"\n }\n `;\n }\n\n get canvasElement() {\n const referencedCanvas = this.canvasRef.value;\n if (referencedCanvas) {\n return referencedCanvas;\n }\n const shadowCanvas = this.shadowRoot?.querySelector(\"canvas\");\n if (shadowCanvas) {\n return shadowCanvas;\n }\n return undefined;\n }\n\n /**\n * Start a delayed loading operation for testing\n */\n startDelayedLoading(\n operationId: string,\n message: string,\n options: { background?: boolean } = {},\n ): void {\n this.#delayedLoadingState.startLoading(operationId, message, options);\n }\n\n /**\n * Clear a delayed loading operation for testing\n */\n clearDelayedLoading(operationId: string): void {\n this.#delayedLoadingState.clearLoading(operationId);\n }\n\n /**\n * Set loading state for user feedback\n */\n private setLoadingState(\n isLoading: boolean,\n operation: LoadingState[\"operation\"] = null,\n message = \"\",\n ): void {\n this.loadingState = {\n isLoading,\n operation,\n message,\n };\n }\n\n /**\n * Paint the current video frame to canvas\n */\n paint(seekToMs: number, parentSpan?: any): void {\n const parentContext = parentSpan\n ? trace.setSpan(context.active(), parentSpan)\n : undefined;\n\n withSpanSync(\n \"video.paint\",\n {\n elementId: this.id || \"unknown\",\n seekToMs,\n src: this.src || \"none\",\n },\n parentContext,\n (span) => {\n const t0 = performance.now();\n\n // Check if we're in production rendering mode vs preview mode\n const isProductionRendering = this.isInProductionRenderingMode();\n const t1 = performance.now();\n span.setAttribute(\"isProductionRendering\", isProductionRendering);\n span.setAttribute(\"modeCheckMs\", t1 - t0);\n\n // Use cached video sample from prepareFrame\n try {\n const t2 = performance.now();\n const videoSample = this.#cachedVideoSample;\n span.setAttribute(\"hasVideoSample\", !!videoSample);\n span.setAttribute(\"valueAccessMs\", t2 - t1);\n\n if (videoSample) {\n const t3 = performance.now();\n const videoFrame = videoSample.toVideoFrame();\n const t4 = performance.now();\n span.setAttribute(\"toVideoFrameMs\", t4 - t3);\n\n try {\n const t5 = performance.now();\n this.displayFrame(videoFrame, seekToMs, span);\n const t6 = performance.now();\n span.setAttribute(\"displayFrameMs\", t6 - t5);\n } finally {\n videoFrame.close();\n }\n }\n } catch (error) {\n console.warn(\"Video pipeline error:\", error);\n }\n\n // EF_FRAMEGEN-aware rendering mode detection\n if (!isProductionRendering) {\n // Preview mode: always render\n // Visibility is handled by the phase/visibility system (CSS display:none)\n // No need to skip initialization frames - if element shouldn't be visible,\n // it will be hidden by CSS\n } else {\n // Production rendering mode: only render when EF_FRAMEGEN has explicitly started frame rendering\n // This prevents initialization frames before the actual render sequence begins\n if (!this.rootTimegroup) {\n span.setAttribute(\"skipped\", \"no-root-timegroup\");\n return;\n }\n\n if (!this.isFrameRenderingActive()) {\n span.setAttribute(\"skipped\", \"frame-rendering-not-active\");\n return; // Wait for EF_FRAMEGEN to start frame sequence\n }\n\n // Production mode: EF_FRAMEGEN has started frame sequence, proceed with rendering\n }\n\n const tEnd = performance.now();\n span.setAttribute(\"totalPaintMs\", tEnd - t0);\n },\n );\n }\n\n /**\n * Clear the canvas when element becomes inactive\n */\n clearCanvas(): void {\n if (!this.canvasElement) return;\n\n const ctx = this.canvasElement.getContext(\"2d\", {\n willReadFrequently: true,\n });\n if (ctx) {\n ctx.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height);\n }\n }\n\n /**\n * Display a video frame on the canvas\n */\n displayFrame(frame: VideoFrame, seekToMs: number, parentSpan?: any): void {\n const parentContext = parentSpan\n ? trace.setSpan(context.active(), parentSpan)\n : undefined;\n\n withSpanSync(\n \"video.displayFrame\",\n {\n elementId: this.id || \"unknown\",\n seekToMs,\n format: frame.format || \"unknown\",\n width: frame.codedWidth,\n height: frame.codedHeight,\n },\n parentContext,\n (span) => {\n const t0 = performance.now();\n\n log(\"trace: displayFrame start\", {\n seekToMs,\n frameFormat: frame.format,\n });\n\n if (!this.canvasElement) {\n log(\"trace: displayFrame aborted - no canvas element\");\n throw new Error(\n `Frame display failed: Canvas element is not available at time ${seekToMs}ms. The video component may not be properly initialized.`,\n );\n }\n const t1 = performance.now();\n span.setAttribute(\"getCanvasMs\", Math.round((t1 - t0) * 100) / 100);\n\n const ctx = this.canvasElement.getContext(\"2d\", {\n willReadFrequently: true,\n });\n const t2 = performance.now();\n span.setAttribute(\"getCtxMs\", Math.round((t2 - t1) * 100) / 100);\n\n if (!ctx) {\n log(\"trace: displayFrame aborted - no canvas context\");\n throw new Error(\n `Frame display failed: Unable to get 2D canvas context at time ${seekToMs}ms. This may indicate a browser compatibility issue or canvas corruption.`,\n );\n }\n\n const frameWidth = frame.displayWidth;\n const frameHeight = frame.displayHeight;\n\n let resized = false;\n if (frameWidth && frameHeight) {\n const needsResize =\n frameWidth > this.canvasElement.width ||\n frameHeight > this.canvasElement.height;\n if (needsResize) {\n const newWidth = Math.max(this.canvasElement.width, frameWidth);\n const newHeight = Math.max(this.canvasElement.height, frameHeight);\n log(\"trace: updating canvas dimensions\", {\n width: newWidth,\n height: newHeight,\n });\n this.canvasElement.width = newWidth;\n this.canvasElement.height = newHeight;\n resized = true;\n const t3 = performance.now();\n span.setAttribute(\"resizeMs\", Math.round((t3 - t2) * 100) / 100);\n }\n }\n span.setAttribute(\"canvasResized\", resized);\n\n if (frame.format === null) {\n log(\"trace: displayFrame aborted - null frame format\");\n throw new Error(\n `Frame display failed: Video frame has null format at time ${seekToMs}ms. This indicates corrupted or incompatible video data.`,\n );\n }\n\n const tDrawStart = performance.now();\n ctx.drawImage(\n frame,\n 0,\n 0,\n this.canvasElement.width,\n this.canvasElement.height,\n );\n const tDrawEnd = performance.now();\n span.setAttribute(\n \"drawImageMs\",\n Math.round((tDrawEnd - tDrawStart) * 100) / 100,\n );\n span.setAttribute(\n \"totalDisplayMs\",\n Math.round((tDrawEnd - t0) * 100) / 100,\n );\n span.setAttribute(\"canvasWidth\", this.canvasElement.width);\n span.setAttribute(\"canvasHeight\", this.canvasElement.height);\n\n log(\"trace: frame drawn to canvas\", { seekToMs });\n },\n );\n }\n\n /**\n * Check if we're in production rendering mode (EF_FRAMEGEN active) vs preview mode\n */\n private isInProductionRenderingMode(): boolean {\n // Check if EF_RENDERING function exists and returns true (production rendering)\n if (typeof window.EF_RENDERING === \"function\") {\n return window.EF_RENDERING();\n }\n\n // Check if workbench is in rendering mode\n const workbench = document.querySelector(\"ef-workbench\") as any;\n if (workbench?.rendering) {\n return true;\n }\n\n // Check if EF_FRAMEGEN exists and has render options (indicates active rendering)\n if (window.EF_FRAMEGEN?.renderOptions) {\n return true;\n }\n\n // Default to preview mode\n return false;\n }\n\n /**\n * Check if EF_FRAMEGEN has explicitly started frame rendering (not just initialization)\n */\n private isFrameRenderingActive(): boolean {\n if (!window.EF_FRAMEGEN?.renderOptions) {\n return false;\n }\n\n // In production mode, only render when EF_FRAMEGEN has actually begun frame sequence\n // Check if we're past the initialization phase by looking for explicit frame control\n const renderOptions = window.EF_FRAMEGEN.renderOptions;\n const renderStartTime = renderOptions.encoderOptions.fromMs;\n const currentTime = this.rootTimegroup?.currentTimeMs || 0;\n\n // We're in active frame rendering if:\n // 1. currentTime >= renderStartTime (includes the starting frame)\n return currentTime >= renderStartTime;\n }\n\n /**\n * Get a decoded VideoFrame at a specific source media timestamp.\n * Returns a standard WebCodecs VideoFrame — caller MUST call .close() when done.\n *\n * Uses the same routing logic as the unified video system:\n * - \"auto\": main track for production rendering, follows normal routing otherwise\n * - \"scrub\": force low-res scrub track (for thumbnails)\n * - \"main\": force full-quality main track\n *\n * @param sourceTimeMs - Timestamp in source media coordinates (not timeline)\n * @param options - Quality and abort signal\n * @returns VideoFrame that the caller must close()\n * @public\n */\n async getVideoFrameAtSourceTime(\n sourceTimeMs: number,\n options: {\n quality?: \"auto\" | \"scrub\" | \"main\";\n signal?: AbortSignal;\n } = {},\n ): Promise<VideoFrame> {\n const { quality = \"auto\", signal: providedSignal } = options;\n\n const signal = providedSignal ?? new AbortController().signal;\n signal.throwIfAborted();\n\n const mediaEngine = await this.getMediaEngine(signal);\n signal.throwIfAborted();\n\n if (!mediaEngine) {\n throw new Error(\"No media engine available for frame capture\");\n }\n\n const useMainTrack =\n quality === \"main\" ||\n (quality === \"auto\" && this.isInProductionRenderingMode());\n\n let videoSample: any;\n\n const { BufferedSeekingInput } =\n await import(\"./EFMedia/BufferedSeekingInput.js\");\n signal.throwIfAborted();\n\n if (useMainTrack) {\n const videoRendition =\n mediaEngine.getVideoRendition?.() || mediaEngine.videoRendition;\n if (!videoRendition) {\n throw new Error(\"No video rendition available\");\n }\n\n const segmentId = mediaEngine.computeSegmentId(\n sourceTimeMs,\n videoRendition,\n );\n if (segmentId === undefined) {\n throw new Error(`Cannot compute segment ID for time ${sourceTimeMs}ms`);\n }\n\n const seekingInput = await mainVideoInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n videoRendition.id,\n async () => {\n const initP = mediaEngine.fetchInitSegment(videoRendition, signal);\n const mediaP = mediaEngine.fetchMediaSegment(\n segmentId,\n videoRendition,\n signal,\n );\n initP.catch(() => {});\n mediaP.catch(() => {});\n const [initSegment, mediaSegment] = await Promise.all([\n initP,\n mediaP,\n ]);\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n const arrayBuffer = await combinedBlob.arrayBuffer();\n\n return new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: videoRendition.startTimeOffsetMs,\n });\n },\n );\n signal.throwIfAborted();\n\n if (!seekingInput) {\n throw new Error(\n `Failed to fetch video segments for time ${sourceTimeMs}ms`,\n );\n }\n\n const videoTrack = await seekingInput.getFirstVideoTrack();\n signal.throwIfAborted();\n\n if (!videoTrack) {\n throw new Error(\"No video track found in segment\");\n }\n\n videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);\n signal.throwIfAborted();\n } else {\n const scrubRendition = mediaEngine.getScrubVideoRendition?.();\n if (!scrubRendition) {\n return this.getVideoFrameAtSourceTime(sourceTimeMs, {\n quality: \"main\",\n signal,\n });\n }\n\n const scrubRenditionWithSrc = { ...scrubRendition, src: mediaEngine.src };\n const segmentId = mediaEngine.computeSegmentId(\n sourceTimeMs,\n scrubRenditionWithSrc,\n );\n\n if (segmentId === undefined) {\n throw new Error(\n `Cannot compute scrub segment ID for time ${sourceTimeMs}ms`,\n );\n }\n\n const seekingInput = await scrubInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n async () => {\n const initP = mediaEngine.fetchInitSegment(\n scrubRenditionWithSrc,\n signal,\n );\n const mediaP = mediaEngine.fetchMediaSegment(\n segmentId,\n scrubRenditionWithSrc,\n signal,\n );\n initP.catch(() => {});\n mediaP.catch(() => {});\n const [initSegment, mediaSegment] = await Promise.all([\n initP,\n mediaP,\n ]);\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n const arrayBuffer = await combinedBlob.arrayBuffer();\n\n return new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n });\n },\n );\n signal.throwIfAborted();\n\n if (!seekingInput) {\n return this.getVideoFrameAtSourceTime(sourceTimeMs, {\n quality: \"main\",\n signal,\n });\n }\n\n const videoTrack = await seekingInput.getFirstVideoTrack();\n signal.throwIfAborted();\n\n if (!videoTrack) {\n return this.getVideoFrameAtSourceTime(sourceTimeMs, {\n quality: \"main\",\n signal,\n });\n }\n\n videoSample = await seekingInput.seek(videoTrack.id, sourceTimeMs);\n signal.throwIfAborted();\n }\n\n if (!videoSample) {\n throw new Error(`No video sample found at ${sourceTimeMs}ms`);\n }\n\n return videoSample.toVideoFrame();\n }\n\n /**\n * Capture a video frame directly at a source media timestamp.\n * Designed for export/rendering.\n * Does NOT paint to the element's internal canvas.\n *\n * Uses the same routing logic as unified video system:\n * - \"auto\": main track for production rendering, follows normal routing otherwise\n * - \"scrub\": force low-res scrub track (for thumbnails)\n * - \"main\": force full-quality main track\n *\n * @param sourceTimeMs - Timestamp in source media coordinates (not timeline)\n * @param options - Capture options including quality and abort signal\n * @returns Frame data for serialization\n * @public\n */\n async captureFrameAtSourceTime(\n sourceTimeMs: number,\n options: {\n quality?: \"auto\" | \"scrub\" | \"main\";\n signal?: AbortSignal;\n } = {},\n ): Promise<{\n dataUrl: string;\n width: number;\n height: number;\n }> {\n const videoFrame = await this.getVideoFrameAtSourceTime(\n sourceTimeMs,\n options,\n );\n\n try {\n options.signal?.throwIfAborted();\n\n const canvas = new OffscreenCanvas(\n videoFrame.codedWidth,\n videoFrame.codedHeight,\n );\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n throw new Error(\"Failed to get 2d context from OffscreenCanvas\");\n }\n ctx.drawImage(videoFrame, 0, 0);\n\n options.signal?.throwIfAborted();\n\n const blob = await canvas.convertToBlob({\n type: \"image/jpeg\",\n quality: 0.92,\n });\n options.signal?.throwIfAborted();\n\n const dataUrl = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n options.signal?.throwIfAborted();\n\n return {\n dataUrl,\n width: videoFrame.codedWidth,\n height: videoFrame.codedHeight,\n };\n } finally {\n videoFrame.close();\n }\n }\n\n /**\n * Pre-fetch scrub segments for given timestamps.\n * Loads 30-second segments sequentially, emitting progress events.\n * This ensures scrub track is cached for fast thumbnail generation.\n *\n * @param timestamps - Array of timestamps (in ms) that will be captured\n * @param onProgress - Optional callback for loading progress\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise that resolves when all segments are cached\n * @public\n */\n async prefetchScrubSegments(\n timestamps: number[],\n onProgress?: (\n loaded: number,\n total: number,\n segmentTimeRange: [number, number],\n ) => void,\n signal?: AbortSignal,\n ): Promise<void> {\n // Wait for media engine to be ready\n const mediaEngine = await this.getMediaEngine(signal);\n if (!mediaEngine) {\n log(\"prefetchScrubSegments: no media engine available\");\n return;\n }\n\n // Get scrub rendition\n const scrubRendition = mediaEngine.getScrubVideoRendition();\n if (!scrubRendition) {\n log(\"prefetchScrubSegments: no scrub rendition available\");\n return;\n }\n\n const scrubRenditionWithSrc = {\n ...scrubRendition,\n src: mediaEngine.src,\n };\n\n // Compute unique segment IDs needed for all timestamps\n const segmentIds = new Set<number>();\n for (const ts of timestamps) {\n const segmentId = mediaEngine.computeSegmentId(ts, scrubRenditionWithSrc);\n if (segmentId !== undefined) {\n segmentIds.add(segmentId);\n }\n }\n\n if (segmentIds.size === 0) {\n log(\"prefetchScrubSegments: no segments to prefetch\");\n return;\n }\n\n // For AssetMediaEngine, the scrub track is a single file (not segmented).\n // We just need to fetch it once, and all segments become cached.\n // Check if ANY segment is already cached (meaning the file is loaded).\n const firstSegmentId = Array.from(segmentIds)[0]!;\n if (mediaEngine.isSegmentCached(firstSegmentId, scrubRenditionWithSrc)) {\n log(\"prefetchScrubSegments: scrub track already cached\");\n return;\n }\n\n log(\n `prefetchScrubSegments: fetching scrub track for ${segmentIds.size} segments...`,\n );\n\n // Emit loading event for the entire duration\n const durationMs = mediaEngine.durationMs || 0;\n this.dispatchEvent(\n new CustomEvent(\"scrub-segment-loading\", {\n detail: {\n segmentId: 0,\n timeRangeMs: [0, durationMs] as [number, number],\n loaded: 0,\n total: 1,\n status: \"loading\",\n },\n bubbles: true,\n composed: true,\n }),\n );\n\n // Fetch the scrub track (single file for all segments)\n // Create a signal if not provided (prefetch is often called without one)\n const fetchSignal = signal ?? new AbortController().signal;\n try {\n await mediaEngine.fetchMediaSegment(\n firstSegmentId,\n scrubRenditionWithSrc,\n fetchSignal,\n );\n log(`prefetchScrubSegments: scrub track loaded`);\n } catch (error) {\n log(`prefetchScrubSegments: failed to load scrub track`, error);\n }\n\n // Emit loaded event\n this.dispatchEvent(\n new CustomEvent(\"scrub-segment-loading\", {\n detail: {\n segmentId: 0,\n timeRangeMs: [0, durationMs] as [number, number],\n loaded: 1,\n total: 1,\n status: \"loaded\",\n },\n bubbles: true,\n composed: true,\n }),\n );\n\n // Report progress\n onProgress?.(1, 1, [0, durationMs]);\n log(`prefetchScrubSegments: complete`);\n }\n\n /**\n * Maybe schedule quality upgrade tasks for this element.\n * Called when returning a scrub sample - checks if state has changed and submits tasks.\n */\n #maybeScheduleQualityUpgrade(mediaEngine: any, sourceTimeMs: number): void {\n const mainRendition = mediaEngine.videoRendition;\n if (!mainRendition) return;\n\n const segmentId = mediaEngine.computeSegmentId(sourceTimeMs, mainRendition);\n if (segmentId === undefined) return;\n\n const startTimeMs = this.startTimeMs;\n\n // Check: has anything changed since last submission?\n const stateChanged =\n this.#upgradeState === null ||\n this.#upgradeState.segmentId !== segmentId ||\n this.#upgradeState.startTimeMs !== startTimeMs;\n\n if (!stateChanged) return; // Nothing changed - O(1) early exit\n\n const segments = this.#computeLookaheadSegments(\n mediaEngine,\n sourceTimeMs,\n mainRendition,\n );\n if (segments.length === 0) return; // All segments already cached\n\n const tasks = segments.map((seg) => ({\n key: `${this.id}:${seg.segmentId}:${mainRendition.id}`,\n fetch: async (signal: AbortSignal) => {\n await mediaEngine.fetchInitSegment(mainRendition, signal);\n await mediaEngine.fetchMediaSegment(\n seg.segmentId,\n mainRendition,\n signal,\n );\n },\n deadlineMs: seg.deadlineMs,\n owner: this.id,\n }));\n\n const scheduler = this.rootTimegroup?.qualityUpgradeScheduler;\n if (scheduler) {\n scheduler.replaceForOwner(this.id, tasks);\n } else {\n // Standalone mode: fire-and-forget\n this.#fetchStandalone(tasks);\n }\n\n // Update intent state\n this.#upgradeState = {\n sourceTimeMs,\n segmentId,\n startTimeMs,\n submittedKeys: new Set(tasks.map((t) => t.key)),\n };\n }\n\n /**\n * Compute lookahead segments with deadlines in timeline space.\n */\n #computeLookaheadSegments(\n mediaEngine: any,\n currentSourceTimeMs: number,\n rendition: any,\n maxLookahead: number = 5,\n ): { segmentId: number; deadlineMs: number }[] {\n const results: { segmentId: number; deadlineMs: number }[] = [];\n const playheadMs = this.rootTimegroup?.currentTimeMs ?? 0;\n const seen = new Set<number>();\n\n let probeTimeMs = currentSourceTimeMs;\n\n while (seen.size < maxLookahead) {\n const segmentId = mediaEngine.computeSegmentId(probeTimeMs, rendition);\n if (segmentId === undefined) break;\n if (seen.has(segmentId)) break; // shouldn't happen, but guard against infinite loop\n\n seen.add(segmentId);\n\n if (!mediaEngine.isSegmentCached(segmentId, rendition)) {\n const offsetFromCurrentMs = probeTimeMs - currentSourceTimeMs;\n const deadlineMs = playheadMs + offsetFromCurrentMs;\n results.push({ segmentId, deadlineMs });\n }\n\n // Advance to just past the end of this segment\n const thisDuration =\n rendition.segmentDurationsMs?.[segmentId - 1] ??\n rendition.segmentDurationMs ??\n 2000;\n probeTimeMs += thisDuration;\n }\n\n return results;\n }\n\n /**\n * Standalone mode: fetch tasks sequentially without scheduler.\n */\n #fetchStandalone(tasks: any[]): void {\n // Abort any previous standalone batch (e.g., after seek)\n this.#standaloneUpgradeController?.abort();\n this.#standaloneUpgradeController = new AbortController();\n const signal = this.#standaloneUpgradeController.signal;\n\n // Process sequentially\n (async () => {\n for (const task of tasks) {\n if (signal.aborted) break;\n try {\n await task.fetch(signal);\n } catch {\n // Continue on error\n }\n }\n // After all tasks complete, trigger re-render\n if (!signal.aborted) {\n this.playbackController?.runThrottledFrameTask();\n }\n })().catch(() => {});\n }\n\n /**\n * Invalidate upgrade state and optionally cancel queued tasks.\n */\n #invalidateUpgradeState(\n reason: \"src-change\" | \"bounds-change\" | \"disconnect\",\n ): void {\n if (reason === \"src-change\" || reason === \"disconnect\") {\n // Full cancel - old tasks reference a stale media engine\n this.rootTimegroup?.qualityUpgradeScheduler?.cancelForOwner(this.id);\n }\n // For bounds-change, don't cancel - old tasks may still be valid segments,\n // just with stale deadlines. replaceForOwner on next prepareFrame handles it.\n this.#upgradeState = null;\n }\n\n /**\n * Clean up resources when component is disconnected\n */\n disconnectedCallback(): void {\n super.disconnectedCallback();\n\n // Clean up delayed loading state\n this.#delayedLoadingState.clearAllLoading();\n\n // Cancel upgrade tasks (centralized or standalone)\n this.#invalidateUpgradeState(\"disconnect\");\n this.#standaloneUpgradeController?.abort();\n this.#standaloneUpgradeController = null;\n }\n\n didBecomeRoot() {\n super.didBecomeRoot();\n }\n didBecomeChild() {\n super.didBecomeChild();\n }\n\n /**\n * Get the natural dimensions of the video (coded width and height).\n * Returns null if the video hasn't loaded yet or canvas isn't available.\n *\n * @public\n */\n getNaturalDimensions(): { width: number; height: number } | null {\n const canvas = this.canvasElement;\n if (!canvas || canvas.width === 0 || canvas.height === 0) {\n return null;\n }\n return {\n width: canvas.width,\n height: canvas.height,\n };\n }\n\n /**\n * Render this video element to an MP4 using the direct video-to-video fast path.\n * Bypasses DOM serialization — decodes frames directly and re-encodes to MP4.\n * Respects trim, CSS filter, and opacity.\n *\n * @param options - Rendering options (fps, codec, bitrate, etc.)\n * @returns Promise resolving to video buffer (if returnBuffer), or undefined\n * @public\n */\n async renderToVideo(\n options?: import(\"../preview/renderTimegroupToVideo.types.js\").RenderToVideoOptions,\n ): Promise<Uint8Array | undefined> {\n const { renderVideoToVideo } =\n await import(\"../preview/renderVideoToVideo.js\");\n return renderVideoToVideo(this, options);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-video\": EFVideo;\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { FrameRenderable, FrameState, FrameTask } from "../preview/FrameController.js";
1
+ import { FrameRenderable, FrameState } from "../preview/FrameController.js";
2
2
  import { TemporalMixinInterface } from "./EFTemporal.js";
3
- import { EFVideo } from "./EFVideo.js";
4
3
  import { EFAudio } from "./EFAudio.js";
4
+ import { EFVideo } from "./EFVideo.js";
5
5
  import { TargetController } from "./TargetController.js";
6
6
  import * as lit6 from "lit";
7
7
  import { LitElement, PropertyValueMap } from "lit";
@@ -44,18 +44,13 @@ declare class EFWaveform extends EFWaveform_base implements FrameRenderable {
44
44
  protected drawPixel(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
45
45
  protected drawWave(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
46
46
  protected drawSpikes(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
47
- /**
48
- * @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.
49
- * This is a compatibility wrapper that delegates to the new system.
50
- */
51
- frameTask: FrameTask;
52
47
  /**
53
48
  * Query readiness state for a given time.
54
49
  * @implements FrameRenderable
55
50
  */
56
51
  getFrameState(timeMs: number): FrameState;
57
52
  /**
58
- * Async preparation - fetches frequency data from target.
53
+ * Async preparation - fetches frequency data from target and initializes canvas.
59
54
  * @implements FrameRenderable
60
55
  */
61
56
  prepareFrame(timeMs: number, signal: AbortSignal): Promise<void>;
@@ -1,8 +1,8 @@
1
1
  import { EF_RENDERING } from "../EF_RENDERING.js";
2
+ import { TWMixin } from "../gui/TWMixin2.js";
3
+ import { PRIORITY_WAVEFORM } from "../preview/FrameController.js";
2
4
  import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
3
5
  import { EFTemporal } from "./EFTemporal.js";
4
- import { TWMixin } from "../gui/TWMixin2.js";
5
- import { PRIORITY_WAVEFORM, createFrameTaskWrapper } from "../preview/FrameController.js";
6
6
  import { TargetController } from "./TargetController.js";
7
7
  import { CrossUpdateController } from "./CrossUpdateController.js";
8
8
  import { LitElement, css, html } from "lit";
@@ -24,7 +24,6 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
24
24
  this.targetElement = null;
25
25
  this.lineWidth = 4;
26
26
  this.targetController = new TargetController(this);
27
- this.frameTask = createFrameTaskWrapper(this);
28
27
  }
29
28
  static {
30
29
  this.styles = css`
@@ -40,6 +39,8 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
40
39
  left: 0;
41
40
  width: 100%;
42
41
  height: 100%;
42
+ object-fit: inherit;
43
+ object-position: inherit;
43
44
  }
44
45
  `;
45
46
  }
@@ -69,12 +70,12 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
69
70
  });
70
71
  this.resizeObserver.observe(this);
71
72
  this.mutationObserver = new MutationObserver((mutationsList) => {
72
- for (const mutation of mutationsList) if (mutation.type === "attributes" && mutation.attributeName !== "style") this.frameTask.run().catch(() => {});
73
+ for (const mutation of mutationsList) if (mutation.type === "attributes" && mutation.attributeName !== "style") this.requestUpdate();
73
74
  });
74
75
  this.mutationObserver.observe(this, { attributes: true });
75
76
  if (!EF_RENDERING()) {
76
77
  this.styleObserver = new CSSStyleObserver(["color"], () => {
77
- this.frameTask.run().catch(() => {});
78
+ this.requestUpdate();
78
79
  });
79
80
  this.styleObserver.attach(this);
80
81
  }
@@ -87,7 +88,7 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
87
88
  }
88
89
  resizeCanvas() {
89
90
  this.ctx = this.initCanvas();
90
- if (this.ctx) this.frameTask.run().catch(() => {});
91
+ if (this.ctx) this.requestUpdate();
91
92
  }
92
93
  initCanvas() {
93
94
  const canvas = this.canvasRef.value;
@@ -101,7 +102,7 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
101
102
  canvas.style.height = `${rect.height}px`;
102
103
  canvas.width = rect.width * dpr;
103
104
  canvas.height = rect.height * dpr;
104
- const ctx = canvas.getContext("2d");
105
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
105
106
  if (!ctx) return null;
106
107
  ctx.reset();
107
108
  return ctx;
@@ -328,11 +329,17 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
328
329
  };
329
330
  }
330
331
  /**
331
- * Async preparation - fetches frequency data from target.
332
+ * Async preparation - fetches frequency data from target and initializes canvas.
332
333
  * @implements FrameRenderable
333
334
  */
334
335
  async prepareFrame(timeMs, signal) {
335
336
  if (!this.targetElement) return;
337
+ if (!this.ctx) {
338
+ this.offsetWidth;
339
+ await new Promise((resolve) => requestAnimationFrame(resolve));
340
+ signal.throwIfAborted();
341
+ this.ctx = this.initCanvas();
342
+ }
336
343
  const [frequencyData, timeDomainData] = await Promise.all([this.targetElement.getFrequencyData(timeMs, signal), this.targetElement.getTimeDomainData(timeMs, signal)]);
337
344
  signal.throwIfAborted();
338
345
  this.#frequencyData = frequencyData;
@@ -345,7 +352,7 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
345
352
  */
346
353
  renderFrame(_timeMs) {
347
354
  if (!this.targetElement) return;
348
- this.ctx ||= this.initCanvas();
355
+ if (!this.ctx) this.ctx = this.initCanvas();
349
356
  const ctx = this.ctx;
350
357
  if (!ctx) return;
351
358
  const frequencyData = this.#frequencyData;
@@ -387,6 +394,7 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
387
394
  break;
388
395
  }
389
396
  ctx.restore();
397
+ this.#renderVersion++;
390
398
  }
391
399
  get durationMs() {
392
400
  if (!this.targetElement) return 0;
@@ -394,10 +402,7 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
394
402
  }
395
403
  updated(changedProperties) {
396
404
  super.updated(changedProperties);
397
- if (changedProperties.size > 0) {
398
- this.#renderVersion++;
399
- this.frameTask.run().catch(() => {});
400
- }
405
+ if (changedProperties.size > 0) this.#renderVersion++;
401
406
  }
402
407
  };
403
408
  __decorate([property({
@@ -1 +1 @@
1
- {"version":3,"file":"EFWaveform.js","names":["EFWaveform","#renderVersion","#dataTimeMs","#frequencyData","#timeDomainData"],"sources":["../../src/elements/EFWaveform.ts"],"sourcesContent":["import { CSSStyleObserver } from \"@bramus/style-observer\";\nimport { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, type Ref, ref } from \"lit/directives/ref.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport {\n type FrameRenderable,\n type FrameState,\n createFrameTaskWrapper,\n PRIORITY_WAVEFORM,\n} from \"../preview/FrameController.js\";\nimport { CrossUpdateController } from \"./CrossUpdateController.js\";\nimport type { EFAudio } from \"./EFAudio.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport type { EFVideo } from \"./EFVideo.js\";\nimport { TargetController } from \"./TargetController.ts\";\n\n@customElement(\"ef-waveform\")\nexport class EFWaveform extends EFTemporal(TWMixin(LitElement)) implements FrameRenderable {\n static styles = css`\n :host {\n all: inherit;\n display: block;\n position: relative;\n }\n\n canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n `;\n\n canvasRef: Ref<HTMLCanvasElement> = createRef();\n private ctx: CanvasRenderingContext2D | null = null;\n private styleObserver: CSSStyleObserver | null = null;\n\n private resizeObserver?: ResizeObserver;\n private mutationObserver?: MutationObserver;\n\n /**\n * Render version counter - increments when visual content changes.\n * Used by RenderContext to cache rendered dataURLs.\n */\n #renderVersion = 0;\n\n /**\n * Get the current render version.\n * Version increments when mode, color, barSpacing, lineWidth, or target changes.\n * @public\n */\n get renderVersion(): number {\n return this.#renderVersion;\n }\n\n render() {\n return html`<canvas ${ref(this.canvasRef)}></canvas>`;\n }\n\n @property({\n type: String,\n attribute: \"mode\",\n })\n mode:\n | \"roundBars\"\n | \"bars\"\n | \"bricks\"\n | \"line\"\n | \"curve\"\n | \"pixel\"\n | \"wave\"\n | \"spikes\" = \"bars\";\n\n @property({ type: String })\n color = \"currentColor\";\n\n @property({ type: String, reflect: true })\n target = \"\";\n\n @property({ type: Number, attribute: \"bar-spacing\" })\n barSpacing = 0.5;\n\n @state()\n targetElement: EFAudio | EFVideo | null = null;\n\n @property({ type: Number, attribute: \"line-width\" })\n lineWidth = 4;\n\n targetController: TargetController = new TargetController(this);\n\n connectedCallback() {\n super.connectedCallback();\n try {\n if (this.targetElement) {\n new CrossUpdateController(this.targetElement, this);\n }\n } catch (_error) {\n // TODO: determine if this is a critical error, or if we should just ignore it\n // currenty evidence suggests everything still works\n // no target element, no cross update controller\n }\n\n // Initialize ResizeObserver\n this.resizeObserver = new ResizeObserver(() => {\n this.resizeCanvas();\n });\n\n // Observe the host element\n this.resizeObserver.observe(this);\n\n // Initialize MutationObserver - only for non-style attributes\n // Style changes are handled by FrameController; triggering frameTask.run()\n // here would use ownCurrentTimeMs which is LOCAL time, not root time,\n // causing wrong audio data to be fetched and displayed.\n this.mutationObserver = new MutationObserver((mutationsList) => {\n for (const mutation of mutationsList) {\n if (mutation.type === \"attributes\" && mutation.attributeName !== \"style\") {\n this.frameTask.run().catch(() => {\n // AbortErrors are expected during cleanup\n });\n }\n }\n });\n\n // Observe attribute changes on the element\n this.mutationObserver.observe(this, { attributes: true });\n\n if (!EF_RENDERING()) {\n this.styleObserver = new CSSStyleObserver([\"color\"], () => {\n this.frameTask.run().catch(() => {\n // AbortErrors are expected during cleanup\n });\n });\n this.styleObserver.attach(this);\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n // Disconnect the observers when the element is removed from the DOM\n this.resizeObserver?.disconnect();\n this.mutationObserver?.disconnect();\n this.styleObserver?.detach();\n }\n\n private resizeCanvas() {\n this.ctx = this.initCanvas();\n if (this.ctx) {\n this.frameTask.run().catch(() => {\n // AbortErrors are expected during cleanup\n }); // Redraw the canvas\n }\n }\n\n protected initCanvas() {\n const canvas = this.canvasRef.value;\n if (!canvas) return null;\n\n const rect = {\n width: this.offsetWidth,\n height: this.offsetHeight,\n };\n const dpr = window.devicePixelRatio;\n\n canvas.style.width = `${rect.width}px`;\n canvas.style.height = `${rect.height}px`;\n\n canvas.width = rect.width * dpr;\n canvas.height = rect.height * dpr;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return null;\n ctx.reset();\n\n // Scale all drawing operations by dpr\n return ctx;\n }\n\n protected drawBars(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n\n const totalBars = frequencyData.length;\n const paddingInner = this.barSpacing;\n const paddingOuter = 0.01;\n const availableWidth = waveWidth;\n const barWidth =\n availableWidth / (totalBars + (totalBars - 1) * paddingInner);\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n frequencyData.forEach((value, i) => {\n const normalizedValue = value / 255;\n const barHeight = normalizedValue * waveHeight;\n const y = (waveHeight - barHeight) / 2;\n const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));\n path.rect(x, y, barWidth, barHeight);\n });\n\n ctx.fill(path);\n }\n\n protected drawBricks(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n const columnWidth = waveWidth / frequencyData.length;\n const boxSize = columnWidth * 0.9;\n const verticalGap = boxSize * 0.2; // Add spacing between bricks\n const maxBricks = Math.floor(waveHeight / (boxSize + verticalGap)); // Account for gaps in height calculation\n\n frequencyData.forEach((value, i) => {\n const normalizedValue = value / 255;\n const brickCount = Math.floor(normalizedValue * maxBricks);\n\n for (let j = 0; j < brickCount; j++) {\n const x = columnWidth * i;\n const y = waveHeight - (j + 1) * (boxSize + verticalGap); // Include gap in position calculation\n path.rect(x, y, boxSize, boxSize);\n }\n });\n\n ctx.fill(path);\n }\n\n protected drawRoundBars(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n\n // Similar padding calculation as drawBars\n const totalBars = frequencyData.length;\n const paddingInner = this.barSpacing;\n const paddingOuter = 0.01;\n const availableWidth = waveWidth;\n const barWidth =\n availableWidth / (totalBars + (totalBars - 1) * paddingInner);\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n\n // Create a single Path2D object for all rounded bars\n const path = new Path2D();\n\n frequencyData.forEach((value, i) => {\n const normalizedValue = value / 255;\n const height = normalizedValue * waveHeight; // Use full wave height like in drawBars\n const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));\n const y = (waveHeight - height) / 2; // Center vertically\n\n // Add rounded rectangle to path\n path.roundRect(x, y, barWidth, height, barWidth / 2);\n });\n\n // Single fill operation for all bars\n ctx.fill(path);\n }\n\n protected drawLine(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n // Sample fewer points to make sharp angles more visible\n const sampleRate = 1; // Only use every 4th point\n\n for (let i = 0; i < frequencyData.length; i += sampleRate) {\n const x = (i / frequencyData.length) * waveWidth;\n const y = (1 - (frequencyData[i] ?? 0) / 255) * waveHeight;\n\n if (i === 0) {\n path.moveTo(x, y);\n } else {\n path.lineTo(x, y);\n }\n }\n // Ensure we draw to the end\n const lastX = waveWidth;\n const lastY =\n (1 - (frequencyData[frequencyData.length - 1] ?? 0) / 255) * waveHeight;\n path.lineTo(lastX, lastY);\n\n ctx.lineWidth = this.lineWidth;\n ctx.stroke(path);\n }\n\n protected drawCurve(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n // Draw smooth curves between points using quadratic curves\n frequencyData.forEach((value, i) => {\n const x = (i / frequencyData.length) * waveWidth;\n const y = (1 - value / 255) * waveHeight;\n\n if (i === 0) {\n path.moveTo(x, y);\n } else {\n const prevX = ((i - 1) / frequencyData.length) * waveWidth;\n const prevY = (1 - (frequencyData[i - 1] ?? 0) / 255) * waveHeight;\n const xc = (prevX + x) / 2;\n const yc = (prevY + y) / 2;\n path.quadraticCurveTo(prevX, prevY, xc, yc);\n }\n });\n\n ctx.lineWidth = this.lineWidth;\n ctx.stroke(path);\n }\n\n protected drawPixel(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n const baseline = waveHeight / 2;\n const barWidth = waveWidth / frequencyData.length;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n frequencyData.forEach((value, i) => {\n const normalizedValue = value / 255;\n const x = i * (waveWidth / frequencyData.length);\n const barHeight = normalizedValue * (waveHeight / 2); // Half height since we extend both ways\n const y = baseline - barHeight;\n path.rect(x, y, barWidth, barHeight * 2); // Double height to extend both ways\n });\n\n ctx.fill(path);\n }\n\n protected drawWave(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n const paddingOuter = 0.01;\n const availableWidth = waveWidth * (1 - 2 * paddingOuter);\n const startX = waveWidth * paddingOuter;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n // Draw top curve\n const firstValue = Math.min(((frequencyData[0] ?? 0) / 255) * 2, 1);\n const firstY = (waveHeight - firstValue * waveHeight) / 2;\n path.moveTo(startX, firstY);\n\n // Draw top half\n frequencyData.forEach((value, i) => {\n const normalizedValue = Math.min((value / 255) * 2, 1);\n const x = startX + (i / (frequencyData.length - 1)) * availableWidth;\n const barHeight = normalizedValue * waveHeight;\n const y = (waveHeight - barHeight) / 2;\n\n if (i === 0) {\n path.moveTo(x, y);\n } else {\n const prevX =\n startX + ((i - 1) / (frequencyData.length - 1)) * availableWidth;\n const prevValue = Math.min(((frequencyData[i - 1] ?? 0) / 255) * 2, 1);\n const prevBarHeight = prevValue * waveHeight;\n const prevY = (waveHeight - prevBarHeight) / 2;\n const xc = (prevX + x) / 2;\n const yc = (prevY + y) / 2;\n path.quadraticCurveTo(prevX, prevY, xc, yc);\n }\n });\n\n // Draw bottom half\n for (let i = frequencyData.length - 1; i >= 0; i--) {\n const normalizedValue = Math.min(((frequencyData[i] ?? 0) / 255) * 2, 1);\n const x = startX + (i / (frequencyData.length - 1)) * availableWidth;\n const barHeight = normalizedValue * waveHeight;\n const y = (waveHeight + barHeight) / 2;\n\n if (i === frequencyData.length - 1) {\n path.lineTo(x, y);\n } else {\n const nextX =\n startX + ((i + 1) / (frequencyData.length - 1)) * availableWidth;\n const nextValue = Math.min(((frequencyData[i + 1] ?? 0) / 255) * 2, 1);\n const nextBarHeight = nextValue * waveHeight;\n const nextY = (waveHeight + nextBarHeight) / 2;\n const xc = (nextX + x) / 2;\n const yc = (nextY + y) / 2;\n path.quadraticCurveTo(nextX, nextY, xc, yc);\n }\n }\n\n // Close the path with a smooth curve back to start\n const lastY = (waveHeight + firstValue * waveHeight) / 2;\n const controlX = startX;\n const controlY = (lastY + firstY) / 2;\n path.quadraticCurveTo(controlX, controlY, startX, firstY);\n\n ctx.fill(path);\n }\n\n protected drawSpikes(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n const paddingOuter = 0.01;\n const availableWidth = waveWidth * (1 - 2 * paddingOuter);\n const startX = waveWidth * paddingOuter;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n // Draw top curve\n const firstValue = (frequencyData[0] ?? 0) / 255;\n const firstY = (waveHeight - firstValue * waveHeight) / 2;\n path.moveTo(startX, firstY);\n\n // Draw top half\n frequencyData.forEach((value, i) => {\n const normalizedValue = Math.min((value / 255) * 2, 1);\n const x = startX + (i / (frequencyData.length - 1)) * availableWidth;\n const barHeight = normalizedValue * (waveHeight / 2);\n const y = (waveHeight - barHeight * 2) / 2;\n\n if (i === 0) {\n path.moveTo(x, y);\n } else {\n const prevX =\n startX + ((i - 1) / (frequencyData.length - 1)) * availableWidth;\n const prevValue = (frequencyData[i - 1] ?? 0) / 255;\n const prevBarHeight = prevValue * (waveHeight / 2);\n const prevY = (waveHeight - prevBarHeight * 2) / 2;\n const xc = (prevX + x) / 2;\n const yc = (prevY + y) / 2;\n path.quadraticCurveTo(prevX, prevY, xc, yc);\n }\n });\n\n // Draw bottom half\n for (let i = frequencyData.length - 1; i >= 0; i--) {\n const normalizedValue = Math.min(((frequencyData[i] ?? 0) / 255) * 2, 1);\n const x = startX + (i / (frequencyData.length - 1)) * availableWidth;\n const barHeight = normalizedValue * (waveHeight / 2);\n const y = (waveHeight + barHeight * 2) / 2;\n\n if (i === frequencyData.length - 1) {\n path.lineTo(x, y);\n } else {\n const nextX =\n startX + ((i + 1) / (frequencyData.length - 1)) * availableWidth;\n const nextValue = (frequencyData[i + 1] ?? 0) / 255;\n const nextBarHeight = nextValue * (waveHeight / 2);\n const nextY = (waveHeight + nextBarHeight * 2) / 2;\n const xc = (nextX + x) / 2;\n const yc = (nextY + y) / 2;\n path.quadraticCurveTo(nextX, nextY, xc, yc);\n }\n }\n\n // Close the path with a smooth curve\n const lastY = (waveHeight + firstValue * waveHeight) / 2;\n const controlX = startX;\n const controlY = (lastY + firstY) / 2;\n path.quadraticCurveTo(controlX, controlY, startX, firstY);\n\n ctx.fill(path);\n }\n\n /**\n * @deprecated Use FrameRenderable methods (prepareFrame, renderFrame) via FrameController instead.\n * This is a compatibility wrapper that delegates to the new system.\n */\n frameTask = createFrameTaskWrapper(this);\n\n // ============================================================================\n // FrameRenderable Implementation\n // Centralized frame control - uses direct async methods\n // ============================================================================\n\n /**\n * Cached audio analysis data\n */\n #frequencyData: Uint8Array | null = null;\n #timeDomainData: Uint8Array | null = null;\n #dataTimeMs: number | undefined = undefined;\n\n /**\n * Query readiness state for a given time.\n * @implements FrameRenderable\n */\n getFrameState(timeMs: number): FrameState {\n // Waveform is ready when we have cached data for this time\n const hasTarget = !!this.targetElement;\n const hasData = hasTarget && \n this.#dataTimeMs === timeMs &&\n this.#frequencyData !== null;\n\n return {\n needsPreparation: hasTarget && !hasData,\n isReady: hasData,\n priority: PRIORITY_WAVEFORM,\n };\n }\n\n /**\n * Async preparation - fetches frequency data from target.\n * @implements FrameRenderable\n */\n async prepareFrame(timeMs: number, signal: AbortSignal): Promise<void> {\n if (!this.targetElement) return;\n\n // Get audio analysis data directly from target\n const [frequencyData, timeDomainData] = await Promise.all([\n this.targetElement.getFrequencyData(timeMs, signal),\n this.targetElement.getTimeDomainData(timeMs, signal),\n ]);\n\n signal.throwIfAborted();\n\n // Cache results\n this.#frequencyData = frequencyData;\n this.#timeDomainData = timeDomainData;\n this.#dataTimeMs = timeMs;\n }\n\n /**\n * Synchronous render - draws waveform to canvas.\n * @implements FrameRenderable\n */\n renderFrame(_timeMs: number): void {\n if (!this.targetElement) return;\n\n this.ctx ||= this.initCanvas();\n const ctx = this.ctx;\n if (!ctx) return;\n\n const frequencyData = this.#frequencyData;\n const byteTimeData = this.#timeDomainData;\n if (!frequencyData || !byteTimeData) return;\n\n ctx.save();\n if (this.color === \"currentColor\") {\n const computedStyle = getComputedStyle(this);\n const currentColor = computedStyle.color;\n ctx.strokeStyle = currentColor;\n ctx.fillStyle = currentColor;\n } else {\n ctx.strokeStyle = this.color;\n ctx.fillStyle = this.color;\n }\n\n switch (this.mode) {\n case \"bars\":\n this.drawBars(ctx, frequencyData);\n break;\n case \"bricks\":\n this.drawBricks(ctx, frequencyData);\n break;\n case \"line\":\n this.drawLine(ctx, byteTimeData);\n break;\n case \"curve\":\n this.drawCurve(ctx, byteTimeData);\n break;\n case \"pixel\":\n this.drawPixel(ctx, frequencyData);\n break;\n case \"wave\":\n this.drawWave(ctx, frequencyData);\n break;\n case \"spikes\":\n this.drawSpikes(ctx, frequencyData);\n break;\n case \"roundBars\":\n this.drawRoundBars(ctx, frequencyData);\n break;\n }\n\n ctx.restore();\n }\n\n // ============================================================================\n // End FrameRenderable Implementation\n // ============================================================================\n\n get durationMs() {\n if (!this.targetElement) return 0;\n return this.targetElement.durationMs;\n }\n\n protected updated(changedProperties: PropertyValueMap<this>): void {\n super.updated(changedProperties);\n\n // Increment render version on any property change.\n // This is intentionally broad to avoid cache staleness - the cache is\n // per-render-session so within a render the version will be stable.\n if (changedProperties.size > 0) {\n this.#renderVersion++;\n // Request a new frame\n this.frameTask.run().catch(() => {\n // AbortErrors are expected during cleanup\n });\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-waveform\": EFWaveform & Element;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EFWaveform.js","names":["EFWaveform","#renderVersion","#dataTimeMs","#frequencyData","#timeDomainData"],"sources":["../../src/elements/EFWaveform.ts"],"sourcesContent":["import { CSSStyleObserver } from \"@bramus/style-observer\";\nimport { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { createRef, type Ref, ref } from \"lit/directives/ref.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport {\n type FrameRenderable,\n type FrameState,\n PRIORITY_WAVEFORM,\n} from \"../preview/FrameController.js\";\nimport { CrossUpdateController } from \"./CrossUpdateController.js\";\nimport type { EFAudio } from \"./EFAudio.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport type { EFVideo } from \"./EFVideo.js\";\nimport { TargetController } from \"./TargetController.ts\";\n\n@customElement(\"ef-waveform\")\nexport class EFWaveform\n extends EFTemporal(TWMixin(LitElement))\n implements FrameRenderable\n{\n static styles = css`\n :host {\n all: inherit;\n display: block;\n position: relative;\n }\n\n canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: inherit;\n object-position: inherit;\n }\n `;\n\n canvasRef: Ref<HTMLCanvasElement> = createRef();\n private ctx: CanvasRenderingContext2D | null = null;\n private styleObserver: CSSStyleObserver | null = null;\n\n private resizeObserver?: ResizeObserver;\n private mutationObserver?: MutationObserver;\n\n /**\n * Render version counter - increments when visual content changes.\n * Used by RenderContext to cache rendered dataURLs.\n */\n #renderVersion = 0;\n\n /**\n * Get the current render version.\n * Version increments when mode, color, barSpacing, lineWidth, or target changes.\n * @public\n */\n get renderVersion(): number {\n return this.#renderVersion;\n }\n\n render() {\n return html`<canvas ${ref(this.canvasRef)}></canvas>`;\n }\n\n @property({\n type: String,\n attribute: \"mode\",\n })\n mode:\n | \"roundBars\"\n | \"bars\"\n | \"bricks\"\n | \"line\"\n | \"curve\"\n | \"pixel\"\n | \"wave\"\n | \"spikes\" = \"bars\";\n\n @property({ type: String })\n color = \"currentColor\";\n\n @property({ type: String, reflect: true })\n target = \"\";\n\n @property({ type: Number, attribute: \"bar-spacing\" })\n barSpacing = 0.5;\n\n @state()\n targetElement: EFAudio | EFVideo | null = null;\n\n @property({ type: Number, attribute: \"line-width\" })\n lineWidth = 4;\n\n targetController: TargetController = new TargetController(this);\n\n connectedCallback() {\n super.connectedCallback();\n try {\n if (this.targetElement) {\n new CrossUpdateController(this.targetElement, this);\n }\n } catch (_error) {\n // TODO: determine if this is a critical error, or if we should just ignore it\n // currenty evidence suggests everything still works\n // no target element, no cross update controller\n }\n\n // Initialize ResizeObserver\n this.resizeObserver = new ResizeObserver(() => {\n this.resizeCanvas();\n });\n\n // Observe the host element\n this.resizeObserver.observe(this);\n\n // Initialize MutationObserver - only for non-style attributes\n // Style changes are handled by FrameController\n this.mutationObserver = new MutationObserver((mutationsList) => {\n for (const mutation of mutationsList) {\n if (\n mutation.type === \"attributes\" &&\n mutation.attributeName !== \"style\"\n ) {\n this.requestUpdate();\n }\n }\n });\n\n // Observe attribute changes on the element\n this.mutationObserver.observe(this, { attributes: true });\n\n if (!EF_RENDERING()) {\n this.styleObserver = new CSSStyleObserver([\"color\"], () => {\n this.requestUpdate();\n });\n this.styleObserver.attach(this);\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n // Disconnect the observers when the element is removed from the DOM\n this.resizeObserver?.disconnect();\n this.mutationObserver?.disconnect();\n this.styleObserver?.detach();\n }\n\n private resizeCanvas() {\n this.ctx = this.initCanvas();\n if (this.ctx) {\n this.requestUpdate();\n }\n }\n\n protected initCanvas() {\n const canvas = this.canvasRef.value;\n if (!canvas) return null;\n\n const rect = {\n width: this.offsetWidth,\n height: this.offsetHeight,\n };\n const dpr = window.devicePixelRatio;\n\n canvas.style.width = `${rect.width}px`;\n canvas.style.height = `${rect.height}px`;\n\n canvas.width = rect.width * dpr;\n canvas.height = rect.height * dpr;\n\n const ctx = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx) return null;\n ctx.reset();\n\n // Scale all drawing operations by dpr\n return ctx;\n }\n\n protected drawBars(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n\n const totalBars = frequencyData.length;\n const paddingInner = this.barSpacing;\n const paddingOuter = 0.01;\n const availableWidth = waveWidth;\n const barWidth =\n availableWidth / (totalBars + (totalBars - 1) * paddingInner);\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n frequencyData.forEach((value, i) => {\n const normalizedValue = value / 255;\n const barHeight = normalizedValue * waveHeight;\n const y = (waveHeight - barHeight) / 2;\n const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));\n path.rect(x, y, barWidth, barHeight);\n });\n\n ctx.fill(path);\n }\n\n protected drawBricks(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n const columnWidth = waveWidth / frequencyData.length;\n const boxSize = columnWidth * 0.9;\n const verticalGap = boxSize * 0.2; // Add spacing between bricks\n const maxBricks = Math.floor(waveHeight / (boxSize + verticalGap)); // Account for gaps in height calculation\n\n frequencyData.forEach((value, i) => {\n const normalizedValue = value / 255;\n const brickCount = Math.floor(normalizedValue * maxBricks);\n\n for (let j = 0; j < brickCount; j++) {\n const x = columnWidth * i;\n const y = waveHeight - (j + 1) * (boxSize + verticalGap); // Include gap in position calculation\n path.rect(x, y, boxSize, boxSize);\n }\n });\n\n ctx.fill(path);\n }\n\n protected drawRoundBars(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n\n // Similar padding calculation as drawBars\n const totalBars = frequencyData.length;\n const paddingInner = this.barSpacing;\n const paddingOuter = 0.01;\n const availableWidth = waveWidth;\n const barWidth =\n availableWidth / (totalBars + (totalBars - 1) * paddingInner);\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n\n // Create a single Path2D object for all rounded bars\n const path = new Path2D();\n\n frequencyData.forEach((value, i) => {\n const normalizedValue = value / 255;\n const height = normalizedValue * waveHeight; // Use full wave height like in drawBars\n const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));\n const y = (waveHeight - height) / 2; // Center vertically\n\n // Add rounded rectangle to path\n path.roundRect(x, y, barWidth, height, barWidth / 2);\n });\n\n // Single fill operation for all bars\n ctx.fill(path);\n }\n\n protected drawLine(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n // Sample fewer points to make sharp angles more visible\n const sampleRate = 1; // Only use every 4th point\n\n for (let i = 0; i < frequencyData.length; i += sampleRate) {\n const x = (i / frequencyData.length) * waveWidth;\n const y = (1 - (frequencyData[i] ?? 0) / 255) * waveHeight;\n\n if (i === 0) {\n path.moveTo(x, y);\n } else {\n path.lineTo(x, y);\n }\n }\n // Ensure we draw to the end\n const lastX = waveWidth;\n const lastY =\n (1 - (frequencyData[frequencyData.length - 1] ?? 0) / 255) * waveHeight;\n path.lineTo(lastX, lastY);\n\n ctx.lineWidth = this.lineWidth;\n ctx.stroke(path);\n }\n\n protected drawCurve(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n // Draw smooth curves between points using quadratic curves\n frequencyData.forEach((value, i) => {\n const x = (i / frequencyData.length) * waveWidth;\n const y = (1 - value / 255) * waveHeight;\n\n if (i === 0) {\n path.moveTo(x, y);\n } else {\n const prevX = ((i - 1) / frequencyData.length) * waveWidth;\n const prevY = (1 - (frequencyData[i - 1] ?? 0) / 255) * waveHeight;\n const xc = (prevX + x) / 2;\n const yc = (prevY + y) / 2;\n path.quadraticCurveTo(prevX, prevY, xc, yc);\n }\n });\n\n ctx.lineWidth = this.lineWidth;\n ctx.stroke(path);\n }\n\n protected drawPixel(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n const baseline = waveHeight / 2;\n const barWidth = waveWidth / frequencyData.length;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n frequencyData.forEach((value, i) => {\n const normalizedValue = value / 255;\n const x = i * (waveWidth / frequencyData.length);\n const barHeight = normalizedValue * (waveHeight / 2); // Half height since we extend both ways\n const y = baseline - barHeight;\n path.rect(x, y, barWidth, barHeight * 2); // Double height to extend both ways\n });\n\n ctx.fill(path);\n }\n\n protected drawWave(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n const paddingOuter = 0.01;\n const availableWidth = waveWidth * (1 - 2 * paddingOuter);\n const startX = waveWidth * paddingOuter;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n // Draw top curve\n const firstValue = Math.min(((frequencyData[0] ?? 0) / 255) * 2, 1);\n const firstY = (waveHeight - firstValue * waveHeight) / 2;\n path.moveTo(startX, firstY);\n\n // Draw top half\n frequencyData.forEach((value, i) => {\n const normalizedValue = Math.min((value / 255) * 2, 1);\n const x = startX + (i / (frequencyData.length - 1)) * availableWidth;\n const barHeight = normalizedValue * waveHeight;\n const y = (waveHeight - barHeight) / 2;\n\n if (i === 0) {\n path.moveTo(x, y);\n } else {\n const prevX =\n startX + ((i - 1) / (frequencyData.length - 1)) * availableWidth;\n const prevValue = Math.min(((frequencyData[i - 1] ?? 0) / 255) * 2, 1);\n const prevBarHeight = prevValue * waveHeight;\n const prevY = (waveHeight - prevBarHeight) / 2;\n const xc = (prevX + x) / 2;\n const yc = (prevY + y) / 2;\n path.quadraticCurveTo(prevX, prevY, xc, yc);\n }\n });\n\n // Draw bottom half\n for (let i = frequencyData.length - 1; i >= 0; i--) {\n const normalizedValue = Math.min(((frequencyData[i] ?? 0) / 255) * 2, 1);\n const x = startX + (i / (frequencyData.length - 1)) * availableWidth;\n const barHeight = normalizedValue * waveHeight;\n const y = (waveHeight + barHeight) / 2;\n\n if (i === frequencyData.length - 1) {\n path.lineTo(x, y);\n } else {\n const nextX =\n startX + ((i + 1) / (frequencyData.length - 1)) * availableWidth;\n const nextValue = Math.min(((frequencyData[i + 1] ?? 0) / 255) * 2, 1);\n const nextBarHeight = nextValue * waveHeight;\n const nextY = (waveHeight + nextBarHeight) / 2;\n const xc = (nextX + x) / 2;\n const yc = (nextY + y) / 2;\n path.quadraticCurveTo(nextX, nextY, xc, yc);\n }\n }\n\n // Close the path with a smooth curve back to start\n const lastY = (waveHeight + firstValue * waveHeight) / 2;\n const controlX = startX;\n const controlY = (lastY + firstY) / 2;\n path.quadraticCurveTo(controlX, controlY, startX, firstY);\n\n ctx.fill(path);\n }\n\n protected drawSpikes(\n ctx: CanvasRenderingContext2D,\n frequencyData: Uint8Array,\n ) {\n const canvas = ctx.canvas;\n const waveWidth = canvas.width;\n const waveHeight = canvas.height;\n const paddingOuter = 0.01;\n const availableWidth = waveWidth * (1 - 2 * paddingOuter);\n const startX = waveWidth * paddingOuter;\n\n ctx.clearRect(0, 0, waveWidth, waveHeight);\n const path = new Path2D();\n\n // Draw top curve\n const firstValue = (frequencyData[0] ?? 0) / 255;\n const firstY = (waveHeight - firstValue * waveHeight) / 2;\n path.moveTo(startX, firstY);\n\n // Draw top half\n frequencyData.forEach((value, i) => {\n const normalizedValue = Math.min((value / 255) * 2, 1);\n const x = startX + (i / (frequencyData.length - 1)) * availableWidth;\n const barHeight = normalizedValue * (waveHeight / 2);\n const y = (waveHeight - barHeight * 2) / 2;\n\n if (i === 0) {\n path.moveTo(x, y);\n } else {\n const prevX =\n startX + ((i - 1) / (frequencyData.length - 1)) * availableWidth;\n const prevValue = (frequencyData[i - 1] ?? 0) / 255;\n const prevBarHeight = prevValue * (waveHeight / 2);\n const prevY = (waveHeight - prevBarHeight * 2) / 2;\n const xc = (prevX + x) / 2;\n const yc = (prevY + y) / 2;\n path.quadraticCurveTo(prevX, prevY, xc, yc);\n }\n });\n\n // Draw bottom half\n for (let i = frequencyData.length - 1; i >= 0; i--) {\n const normalizedValue = Math.min(((frequencyData[i] ?? 0) / 255) * 2, 1);\n const x = startX + (i / (frequencyData.length - 1)) * availableWidth;\n const barHeight = normalizedValue * (waveHeight / 2);\n const y = (waveHeight + barHeight * 2) / 2;\n\n if (i === frequencyData.length - 1) {\n path.lineTo(x, y);\n } else {\n const nextX =\n startX + ((i + 1) / (frequencyData.length - 1)) * availableWidth;\n const nextValue = (frequencyData[i + 1] ?? 0) / 255;\n const nextBarHeight = nextValue * (waveHeight / 2);\n const nextY = (waveHeight + nextBarHeight * 2) / 2;\n const xc = (nextX + x) / 2;\n const yc = (nextY + y) / 2;\n path.quadraticCurveTo(nextX, nextY, xc, yc);\n }\n }\n\n // Close the path with a smooth curve\n const lastY = (waveHeight + firstValue * waveHeight) / 2;\n const controlX = startX;\n const controlY = (lastY + firstY) / 2;\n path.quadraticCurveTo(controlX, controlY, startX, firstY);\n\n ctx.fill(path);\n }\n\n // ============================================================================\n // FrameRenderable Implementation\n // ============================================================================\n\n /**\n * Cached audio analysis data\n */\n #frequencyData: Uint8Array | null = null;\n #timeDomainData: Uint8Array | null = null;\n #dataTimeMs: number | undefined = undefined;\n\n /**\n * Query readiness state for a given time.\n * @implements FrameRenderable\n */\n getFrameState(timeMs: number): FrameState {\n // Waveform is ready when we have cached data for this time\n const hasTarget = !!this.targetElement;\n const hasData =\n hasTarget && this.#dataTimeMs === timeMs && this.#frequencyData !== null;\n\n return {\n needsPreparation: hasTarget && !hasData,\n isReady: hasData,\n priority: PRIORITY_WAVEFORM,\n };\n }\n\n /**\n * Async preparation - fetches frequency data from target and initializes canvas.\n * @implements FrameRenderable\n */\n async prepareFrame(timeMs: number, signal: AbortSignal): Promise<void> {\n if (!this.targetElement) return;\n\n // Ensure canvas is initialized before rendering\n // This must happen in prepareFrame (not renderFrame) to ensure layout is stable\n if (!this.ctx) {\n // Force layout computation\n void this.offsetWidth;\n\n // Wait for next frame to ensure layout is fully stable\n await new Promise((resolve) => requestAnimationFrame(resolve));\n signal.throwIfAborted();\n\n this.ctx = this.initCanvas();\n }\n\n // Get audio analysis data directly from target\n const [frequencyData, timeDomainData] = await Promise.all([\n this.targetElement.getFrequencyData(timeMs, signal),\n this.targetElement.getTimeDomainData(timeMs, signal),\n ]);\n\n signal.throwIfAborted();\n\n // Cache results\n this.#frequencyData = frequencyData;\n this.#timeDomainData = timeDomainData;\n this.#dataTimeMs = timeMs;\n }\n\n /**\n * Synchronous render - draws waveform to canvas.\n * @implements FrameRenderable\n */\n renderFrame(_timeMs: number): void {\n if (!this.targetElement) return;\n\n // Canvas should already be initialized in prepareFrame\n // Fall back to lazy initialization for compatibility with non-FrameController contexts\n if (!this.ctx) {\n this.ctx = this.initCanvas();\n }\n\n const ctx = this.ctx;\n if (!ctx) return;\n\n const frequencyData = this.#frequencyData;\n const byteTimeData = this.#timeDomainData;\n if (!frequencyData || !byteTimeData) return;\n\n ctx.save();\n if (this.color === \"currentColor\") {\n const computedStyle = getComputedStyle(this);\n const currentColor = computedStyle.color;\n ctx.strokeStyle = currentColor;\n ctx.fillStyle = currentColor;\n } else {\n ctx.strokeStyle = this.color;\n ctx.fillStyle = this.color;\n }\n\n switch (this.mode) {\n case \"bars\":\n this.drawBars(ctx, frequencyData);\n break;\n case \"bricks\":\n this.drawBricks(ctx, frequencyData);\n break;\n case \"line\":\n this.drawLine(ctx, byteTimeData);\n break;\n case \"curve\":\n this.drawCurve(ctx, byteTimeData);\n break;\n case \"pixel\":\n this.drawPixel(ctx, frequencyData);\n break;\n case \"wave\":\n this.drawWave(ctx, frequencyData);\n break;\n case \"spikes\":\n this.drawSpikes(ctx, frequencyData);\n break;\n case \"roundBars\":\n this.drawRoundBars(ctx, frequencyData);\n break;\n }\n\n ctx.restore();\n\n this.#renderVersion++;\n }\n\n // ============================================================================\n // End FrameRenderable Implementation\n // ============================================================================\n\n get durationMs() {\n if (!this.targetElement) return 0;\n return this.targetElement.durationMs;\n }\n\n protected updated(changedProperties: PropertyValueMap<this>): void {\n super.updated(changedProperties);\n\n // Increment render version on any property change.\n // This is intentionally broad to avoid cache staleness - the cache is\n // per-render-session so within a render the version will be stable.\n if (changedProperties.size > 0) {\n this.#renderVersion++;\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-waveform\": EFWaveform & Element;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ElementPositionInfo.js","names":[],"sources":["../../src/elements/ElementPositionInfo.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\n/**\n * Element position information interface.\n * Provides computed position, bounds, and transform information for elements.\n */\nexport interface ElementPositionInfo {\n /**\n * The bounding rectangle of the element in screen coordinates.\n */\n bounds: DOMRect;\n\n /**\n * The computed transform string (e.g., \"translate(10px, 20px) rotate(45deg)\").\n */\n transform: string;\n\n /**\n * The rotation angle in degrees, extracted from the transform.\n */\n rotation: number;\n}\n\n/**\n * Mixin that adds getPositionInfo() method to LitElement.\n * Elements can use this to expose their position information.\n */\nexport function PositionInfoMixin<T extends Constructor<LitElement>>(superClass: T) {\n class PositionInfoElement extends superClass {\n\n /**\n * Get position information for this element.\n * Returns computed bounds, transform, and rotation.\n *\n * @public\n */\n getPositionInfo(): ElementPositionInfo | null {\n return getPositionInfoFromElement(this as unknown as HTMLElement);\n }\n }\n\n return PositionInfoElement as T;\n}\n\n/**\n * Helper function to get position info from any HTMLElement.\n * Reads computed styles and bounding rect to determine position.\n */\nexport function getPositionInfoFromElement(\n element: HTMLElement | null,\n): ElementPositionInfo | null {\n if (!element) {\n return null;\n }\n\n const rect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n // Parse rotation from transform matrix\n let rotation = 0;\n if (transform && transform !== \"none\") {\n // Transform matrix format: matrix(a, b, c, d, e, f)\n // Rotation = atan2(b, a) * (180 / Math.PI)\n const matrixMatch = transform.match(/matrix\\(([^)]+)\\)/);\n if (matrixMatch && matrixMatch[1]) {\n const values = matrixMatch[1].split(\",\").map((v) => parseFloat(v.trim()));\n if (values.length >= 4) {\n const a = values[0]!;\n const b = values[1]!;\n rotation = Math.atan2(b, a) * (180 / Math.PI);\n }\n }\n }\n\n return {\n bounds: rect,\n transform: transform || \"none\",\n rotation,\n };\n}\n"],"mappings":";;;;;AA6BA,SAAgB,kBAAqD,YAAe;CAClF,MAAM,4BAA4B,WAAW;;;;;;;EAQ3C,kBAA8C;AAC5C,UAAO,2BAA2B,KAA+B;;;AAIrE,QAAO;;;;;;AAOT,SAAgB,2BACd,SAC4B;AAC5B,KAAI,CAAC,QACH,QAAO;CAGT,MAAM,OAAO,QAAQ,uBAAuB;CAE5C,MAAM,YADgB,OAAO,iBAAiB,QAAQ,CACtB;CAGhC,IAAI,WAAW;AACf,KAAI,aAAa,cAAc,QAAQ;EAGrC,MAAM,cAAc,UAAU,MAAM,oBAAoB;AACxD,MAAI,eAAe,YAAY,IAAI;GACjC,MAAM,SAAS,YAAY,GAAG,MAAM,IAAI,CAAC,KAAK,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC;AACzE,OAAI,OAAO,UAAU,GAAG;IACtB,MAAM,IAAI,OAAO;IACjB,MAAM,IAAI,OAAO;AACjB,eAAW,KAAK,MAAM,GAAG,EAAE,IAAI,MAAM,KAAK;;;;AAKhD,QAAO;EACL,QAAQ;EACR,WAAW,aAAa;EACxB;EACD"}
1
+ {"version":3,"file":"ElementPositionInfo.js","names":[],"sources":["../../src/elements/ElementPositionInfo.ts"],"sourcesContent":["import type { LitElement } from \"lit\";\n\ntype Constructor<T = {}> = new (...args: any[]) => T;\n\n/**\n * Element position information interface.\n * Provides computed position, bounds, and transform information for elements.\n */\nexport interface ElementPositionInfo {\n /**\n * The bounding rectangle of the element in screen coordinates.\n */\n bounds: DOMRect;\n\n /**\n * The computed transform string (e.g., \"translate(10px, 20px) rotate(45deg)\").\n */\n transform: string;\n\n /**\n * The rotation angle in degrees, extracted from the transform.\n */\n rotation: number;\n}\n\n/**\n * Mixin that adds getPositionInfo() method to LitElement.\n * Elements can use this to expose their position information.\n */\nexport function PositionInfoMixin<T extends Constructor<LitElement>>(\n superClass: T,\n) {\n class PositionInfoElement extends superClass {\n /**\n * Get position information for this element.\n * Returns computed bounds, transform, and rotation.\n *\n * @public\n */\n getPositionInfo(): ElementPositionInfo | null {\n return getPositionInfoFromElement(this as unknown as HTMLElement);\n }\n }\n\n return PositionInfoElement as T;\n}\n\n/**\n * Helper function to get position info from any HTMLElement.\n * Reads computed styles and bounding rect to determine position.\n */\nexport function getPositionInfoFromElement(\n element: HTMLElement | null,\n): ElementPositionInfo | null {\n if (!element) {\n return null;\n }\n\n const rect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n // Parse rotation from transform matrix\n let rotation = 0;\n if (transform && transform !== \"none\") {\n // Transform matrix format: matrix(a, b, c, d, e, f)\n // Rotation = atan2(b, a) * (180 / Math.PI)\n const matrixMatch = transform.match(/matrix\\(([^)]+)\\)/);\n if (matrixMatch && matrixMatch[1]) {\n const values = matrixMatch[1].split(\",\").map((v) => parseFloat(v.trim()));\n if (values.length >= 4) {\n const a = values[0]!;\n const b = values[1]!;\n rotation = Math.atan2(b, a) * (180 / Math.PI);\n }\n }\n }\n\n return {\n bounds: rect,\n transform: transform || \"none\",\n rotation,\n };\n}\n"],"mappings":";;;;;AA6BA,SAAgB,kBACd,YACA;CACA,MAAM,4BAA4B,WAAW;;;;;;;EAO3C,kBAA8C;AAC5C,UAAO,2BAA2B,KAA+B;;;AAIrE,QAAO;;;;;;AAOT,SAAgB,2BACd,SAC4B;AAC5B,KAAI,CAAC,QACH,QAAO;CAGT,MAAM,OAAO,QAAQ,uBAAuB;CAE5C,MAAM,YADgB,OAAO,iBAAiB,QAAQ,CACtB;CAGhC,IAAI,WAAW;AACf,KAAI,aAAa,cAAc,QAAQ;EAGrC,MAAM,cAAc,UAAU,MAAM,oBAAoB;AACxD,MAAI,eAAe,YAAY,IAAI;GACjC,MAAM,SAAS,YAAY,GAAG,MAAM,IAAI,CAAC,KAAK,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC;AACzE,OAAI,OAAO,UAAU,GAAG;IACtB,MAAM,IAAI,OAAO;IACjB,MAAM,IAAI,OAAO;AACjB,eAAW,KAAK,MAAM,GAAG,EAAE,IAAI,MAAM,KAAK;;;;AAKhD,QAAO;EACL,QAAQ;EACR,WAAW,aAAa;EACxB;EACD"}