@editframe/elements 0.33.0-beta → 0.34.5-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/dist/EF_FRAMEGEN.js +5 -3
  2. package/dist/EF_FRAMEGEN.js.map +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.94.0 → _@oxc-project_runtime@0.95.0}/helpers/decorate.js +1 -1
  4. package/dist/canvas/EFCanvas.d.ts +7 -4
  5. package/dist/canvas/EFCanvas.js +1 -1
  6. package/dist/canvas/EFCanvasItem.d.ts +4 -4
  7. package/dist/canvas/EFCanvasItem.js +1 -1
  8. package/dist/canvas/overlays/SelectionOverlay.d.ts +95 -0
  9. package/dist/canvas/overlays/SelectionOverlay.js +1 -1
  10. package/dist/canvas/selection/SelectionController.js +7 -11
  11. package/dist/canvas/selection/SelectionController.js.map +1 -1
  12. package/dist/elements/EFAudio.d.ts +25 -7
  13. package/dist/elements/EFAudio.js +31 -61
  14. package/dist/elements/EFAudio.js.map +1 -1
  15. package/dist/elements/EFCaptions.d.ts +65 -52
  16. package/dist/elements/EFCaptions.js +186 -400
  17. package/dist/elements/EFCaptions.js.map +1 -1
  18. package/dist/elements/EFImage.d.ts +34 -6
  19. package/dist/elements/EFImage.js +114 -79
  20. package/dist/elements/EFImage.js.map +1 -1
  21. package/dist/elements/EFMedia/AssetIdMediaEngine.js +17 -17
  22. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
  23. package/dist/elements/EFMedia/AssetMediaEngine.js +41 -25
  24. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  25. package/dist/elements/EFMedia/BaseMediaEngine.js +4 -4
  26. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  27. package/dist/elements/EFMedia/BufferedSeekingInput.js +1 -1
  28. package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
  29. package/dist/elements/EFMedia/JitMediaEngine.js +31 -17
  30. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  31. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
  32. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  33. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +17 -9
  34. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  35. package/dist/elements/EFMedia.d.ts +66 -20
  36. package/dist/elements/EFMedia.js +412 -30
  37. package/dist/elements/EFMedia.js.map +1 -1
  38. package/dist/elements/EFPanZoom.d.ts +4 -4
  39. package/dist/elements/EFPanZoom.js +1 -1
  40. package/dist/elements/EFSourceMixin.js +43 -15
  41. package/dist/elements/EFSourceMixin.js.map +1 -1
  42. package/dist/elements/EFSurface.d.ts +23 -10
  43. package/dist/elements/EFSurface.js +64 -22
  44. package/dist/elements/EFSurface.js.map +1 -1
  45. package/dist/elements/EFTemporal.d.ts +8 -2
  46. package/dist/elements/EFTemporal.js +42 -31
  47. package/dist/elements/EFTemporal.js.map +1 -1
  48. package/dist/elements/EFText.d.ts +5 -4
  49. package/dist/elements/EFText.js +11 -2
  50. package/dist/elements/EFText.js.map +1 -1
  51. package/dist/elements/EFTextSegment.d.ts +4 -4
  52. package/dist/elements/EFTextSegment.js +1 -1
  53. package/dist/elements/EFThumbnailStrip.d.ts +4 -4
  54. package/dist/elements/EFThumbnailStrip.js +1 -1
  55. package/dist/elements/EFTimegroup.d.ts +22 -8
  56. package/dist/elements/EFTimegroup.js +203 -115
  57. package/dist/elements/EFTimegroup.js.map +1 -1
  58. package/dist/elements/EFVideo.d.ts +57 -20
  59. package/dist/elements/EFVideo.js +324 -72
  60. package/dist/elements/EFVideo.js.map +1 -1
  61. package/dist/elements/EFWaveform.d.ts +33 -7
  62. package/dist/elements/EFWaveform.js +103 -59
  63. package/dist/elements/EFWaveform.js.map +1 -1
  64. package/dist/elements/renderTemporalAudio.js +14 -3
  65. package/dist/elements/renderTemporalAudio.js.map +1 -1
  66. package/dist/getRenderInfo.d.ts +2 -2
  67. package/dist/gui/ContextMixin.js +1 -1
  68. package/dist/gui/Controllable.d.ts +2 -0
  69. package/dist/gui/EFActiveRootTemporal.d.ts +4 -4
  70. package/dist/gui/EFActiveRootTemporal.js +1 -1
  71. package/dist/gui/EFConfiguration.d.ts +4 -4
  72. package/dist/gui/EFConfiguration.js +1 -1
  73. package/dist/gui/EFControls.d.ts +2 -2
  74. package/dist/gui/EFControls.js +1 -1
  75. package/dist/gui/EFDial.d.ts +4 -4
  76. package/dist/gui/EFDial.js +1 -1
  77. package/dist/gui/EFFilmstrip.d.ts +3 -2
  78. package/dist/gui/EFFilmstrip.js +1 -1
  79. package/dist/gui/EFFitScale.js +1 -1
  80. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  81. package/dist/gui/EFFocusOverlay.js +1 -1
  82. package/dist/gui/EFOverlayItem.d.ts +4 -4
  83. package/dist/gui/EFOverlayItem.js +1 -1
  84. package/dist/gui/EFOverlayLayer.d.ts +4 -4
  85. package/dist/gui/EFOverlayLayer.js +1 -1
  86. package/dist/gui/EFPause.d.ts +4 -4
  87. package/dist/gui/EFPause.js +1 -1
  88. package/dist/gui/EFPlay.d.ts +4 -4
  89. package/dist/gui/EFPlay.js +1 -1
  90. package/dist/gui/EFPreview.d.ts +4 -4
  91. package/dist/gui/EFPreview.js +1 -1
  92. package/dist/gui/EFResizableBox.d.ts +4 -4
  93. package/dist/gui/EFResizableBox.js +1 -1
  94. package/dist/gui/EFScrubber.d.ts +4 -4
  95. package/dist/gui/EFScrubber.js +1 -1
  96. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  97. package/dist/gui/EFTimeDisplay.js +1 -1
  98. package/dist/gui/EFTimelineRuler.d.ts +4 -4
  99. package/dist/gui/EFTimelineRuler.js +1 -1
  100. package/dist/gui/EFToggleLoop.d.ts +4 -4
  101. package/dist/gui/EFToggleLoop.js +1 -1
  102. package/dist/gui/EFTogglePlay.d.ts +4 -4
  103. package/dist/gui/EFTogglePlay.js +1 -1
  104. package/dist/gui/EFTransformHandles.d.ts +4 -4
  105. package/dist/gui/EFTransformHandles.js +1 -1
  106. package/dist/gui/EFWorkbench.d.ts +5 -4
  107. package/dist/gui/EFWorkbench.js +1 -1
  108. package/dist/gui/PlaybackController.d.ts +10 -2
  109. package/dist/gui/PlaybackController.js +52 -30
  110. package/dist/gui/PlaybackController.js.map +1 -1
  111. package/dist/gui/TWMixin.js +1 -1
  112. package/dist/gui/TWMixin.js.map +1 -1
  113. package/dist/gui/TargetOrContextMixin.js +1 -1
  114. package/dist/gui/hierarchy/EFHierarchy.d.ts +4 -4
  115. package/dist/gui/hierarchy/EFHierarchy.js +1 -1
  116. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
  117. package/dist/gui/hierarchy/EFHierarchyItem.js +1 -1
  118. package/dist/gui/timeline/EFTimeline.d.ts +6 -2
  119. package/dist/gui/timeline/EFTimeline.js +1 -1
  120. package/dist/gui/timeline/EFTimelineRow.d.ts +57 -0
  121. package/dist/gui/timeline/EFTimelineRow.js +1 -1
  122. package/dist/gui/timeline/TrimHandles.d.ts +4 -4
  123. package/dist/gui/timeline/TrimHandles.js +1 -1
  124. package/dist/gui/timeline/tracks/AudioTrack.d.ts +2 -0
  125. package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
  126. package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +58 -0
  127. package/dist/gui/timeline/tracks/CaptionsTrack.js +1 -1
  128. package/dist/gui/timeline/tracks/HTMLTrack.d.ts +13 -0
  129. package/dist/gui/timeline/tracks/HTMLTrack.js +1 -1
  130. package/dist/gui/timeline/tracks/ImageTrack.d.ts +14 -0
  131. package/dist/gui/timeline/tracks/ImageTrack.js +1 -1
  132. package/dist/gui/timeline/tracks/TextTrack.d.ts +26 -0
  133. package/dist/gui/timeline/tracks/TextTrack.js +1 -1
  134. package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +47 -0
  135. package/dist/gui/timeline/tracks/TimegroupTrack.js +4 -12
  136. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
  137. package/dist/gui/timeline/tracks/TrackItem.d.ts +81 -0
  138. package/dist/gui/timeline/tracks/TrackItem.js +1 -1
  139. package/dist/gui/timeline/tracks/VideoTrack.d.ts +25 -0
  140. package/dist/gui/timeline/tracks/VideoTrack.js +1 -1
  141. package/dist/gui/timeline/tracks/WaveformTrack.d.ts +14 -0
  142. package/dist/gui/timeline/tracks/WaveformTrack.js +1 -1
  143. package/dist/gui/timeline/tracks/ensureTrackItemInit.d.ts +1 -0
  144. package/dist/gui/timeline/tracks/preloadTracks.d.ts +9 -0
  145. package/dist/gui/tree/EFTree.d.ts +5 -4
  146. package/dist/gui/tree/EFTree.js +1 -1
  147. package/dist/gui/tree/EFTreeItem.d.ts +4 -4
  148. package/dist/gui/tree/EFTreeItem.js +1 -1
  149. package/dist/index.d.ts +4 -1
  150. package/dist/preview/AdaptiveResolutionTracker.js +6 -14
  151. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
  152. package/dist/preview/FrameController.d.ts +123 -0
  153. package/dist/preview/FrameController.js +216 -0
  154. package/dist/preview/FrameController.js.map +1 -0
  155. package/dist/preview/RenderContext.d.ts +1 -0
  156. package/dist/preview/RenderContext.js +193 -0
  157. package/dist/preview/RenderContext.js.map +1 -0
  158. package/dist/preview/encoding/canvasEncoder.js +166 -0
  159. package/dist/preview/encoding/canvasEncoder.js.map +1 -0
  160. package/dist/preview/encoding/mainThreadEncoder.js +39 -0
  161. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -0
  162. package/dist/preview/encoding/types.d.ts +1 -0
  163. package/dist/preview/encoding/workerEncoder.js +58 -0
  164. package/dist/preview/encoding/workerEncoder.js.map +1 -0
  165. package/dist/preview/logger.js +41 -0
  166. package/dist/preview/logger.js.map +1 -0
  167. package/dist/preview/previewTypes.js +11 -10
  168. package/dist/preview/previewTypes.js.map +1 -1
  169. package/dist/preview/renderTimegroupPreview.js +259 -236
  170. package/dist/preview/renderTimegroupPreview.js.map +1 -1
  171. package/dist/preview/renderTimegroupToCanvas.d.ts +5 -0
  172. package/dist/preview/renderTimegroupToCanvas.js +99 -489
  173. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  174. package/dist/preview/renderTimegroupToVideo.d.ts +1 -0
  175. package/dist/preview/renderTimegroupToVideo.js +80 -22
  176. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  177. package/dist/preview/renderers.js.map +1 -1
  178. package/dist/preview/rendering/inlineImages.js +56 -0
  179. package/dist/preview/rendering/inlineImages.js.map +1 -0
  180. package/dist/preview/rendering/renderToImage.d.ts +1 -0
  181. package/dist/preview/rendering/renderToImage.js +120 -0
  182. package/dist/preview/rendering/renderToImage.js.map +1 -0
  183. package/dist/preview/rendering/renderToImageForeignObject.js +135 -0
  184. package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -0
  185. package/dist/preview/rendering/renderToImageNative.d.ts +1 -0
  186. package/dist/preview/rendering/renderToImageNative.js +129 -0
  187. package/dist/preview/rendering/renderToImageNative.js.map +1 -0
  188. package/dist/preview/rendering/svgSerializer.js +43 -0
  189. package/dist/preview/rendering/svgSerializer.js.map +1 -0
  190. package/dist/preview/rendering/types.d.ts +2 -0
  191. package/dist/preview/statsTrackingStrategy.js +3 -1
  192. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  193. package/dist/preview/workers/WorkerPool.js +8 -57
  194. package/dist/preview/workers/WorkerPool.js.map +1 -1
  195. package/dist/render/EFRenderAPI.d.ts +35 -0
  196. package/dist/render/EFRenderAPI.js +1 -0
  197. package/dist/render/EFRenderAPI.js.map +1 -1
  198. package/dist/sandbox/PlaybackControls.d.ts +1 -0
  199. package/dist/sandbox/ScenarioRunner.d.ts +1 -0
  200. package/dist/sandbox/defineSandbox.d.ts +1 -0
  201. package/dist/sandbox/index.d.ts +3 -0
  202. package/dist/style.css +3 -0
  203. package/dist/transcoding/types/index.d.ts +6 -3
  204. package/package.json +2 -3
  205. package/test/EFVideo.framegen.browsertest.ts +8 -1
  206. package/test/profilingPlugin.ts +1 -3
  207. package/test/setup.ts +23 -1
  208. package/dist/EF_INTERACTIVE.js +0 -7
  209. package/dist/EF_INTERACTIVE.js.map +0 -1
  210. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +0 -50
  211. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +0 -12
  212. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +0 -104
  213. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +0 -1
  214. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -168
  215. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +0 -1
  216. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +0 -46
  217. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +0 -1
  218. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +0 -49
  219. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +0 -1
  220. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +0 -30
  221. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +0 -1
  222. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +0 -49
  223. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +0 -1
  224. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +0 -47
  225. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +0 -1
  226. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +0 -140
  227. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +0 -1
  228. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +0 -13
  229. package/dist/elements/EFMedia/shared/BufferUtils.js +0 -86
  230. package/dist/elements/EFMedia/shared/BufferUtils.js.map +0 -1
  231. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +0 -17
  232. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +0 -90
  233. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +0 -1
  234. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +0 -80
  235. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +0 -1
  236. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -49
  237. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +0 -1
  238. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +0 -58
  239. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +0 -1
  240. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +0 -71
  241. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +0 -1
  242. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -52
  243. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +0 -1
  244. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +0 -50
  245. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +0 -1
  246. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +0 -109
  247. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +0 -1
  248. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +0 -12
  249. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +0 -97
  250. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +0 -1
  251. package/dist/elements/SampleBuffer.d.ts +0 -19
@@ -1 +1 @@
1
- {"version":3,"file":"EFVideo.js","names":["EFVideo","uncachedSegmentIds: number[]"],"sources":["../../src/elements/EFVideo.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { 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 { DelayedLoadingState } from \"../DelayedLoadingState.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { withSpan, withSpanSync } from \"../otel/tracingHelpers.js\";\nimport { makeScrubVideoBufferTask } from \"./EFMedia/videoTasks/makeScrubVideoBufferTask.ts\";\nimport { makeScrubVideoInitSegmentFetchTask } from \"./EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts\";\nimport { makeScrubVideoInputTask } from \"./EFMedia/videoTasks/makeScrubVideoInputTask.ts\";\nimport { makeScrubVideoSeekTask } from \"./EFMedia/videoTasks/makeScrubVideoSeekTask.ts\";\nimport { makeScrubVideoSegmentFetchTask } from \"./EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts\";\nimport { makeScrubVideoSegmentIdTask } from \"./EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts\";\nimport { makeUnifiedVideoSeekTask } from \"./EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts\";\nimport { makeVideoBufferTask } from \"./EFMedia/videoTasks/makeVideoBufferTask.ts\";\nimport { EFMedia } from \"./EFMedia.js\";\nimport { updateAnimations } from \"./updateAnimations.js\";\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) {\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 // Unified video system - single smart seek task that routes to scrub or main\n unifiedVideoSeekTask = makeUnifiedVideoSeekTask(this);\n videoBufferTask = makeVideoBufferTask(this); // Keep for main video buffering\n\n // Scrub video preloading system\n scrubVideoBufferTask = makeScrubVideoBufferTask(this);\n scrubVideoInputTask = makeScrubVideoInputTask(this);\n scrubVideoSeekTask = makeScrubVideoSeekTask(this);\n scrubVideoSegmentIdTask = makeScrubVideoSegmentIdTask(this);\n scrubVideoSegmentFetchTask = makeScrubVideoSegmentFetchTask(this);\n scrubVideoInitSegmentFetchTask = makeScrubVideoInitSegmentFetchTask(this);\n\n /**\n * Delayed loading state manager for user feedback\n */\n private 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 frameTask = new Task(this, {\n autoRun: false,\n args: () => [this.desiredSeekTimeMs] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete BEFORE the promise is rejected.\n // This prevents unhandled rejection when hostUpdate() triggers _performTask() without awaiting.\n this.frameTask.taskComplete.catch(() => {});\n \n // Don't log AbortErrors - these are expected when tasks are cancelled\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 \n // Only log unexpected errors - expected conditions handled gracefully\n if (\n error instanceof Error &&\n !error.message.includes(\"Video rendition unavailable\") &&\n !error.message.includes(\"No valid media source\") &&\n !error.message.includes(\"Sample not found for time\") // Seeking beyond video duration\n ) {\n console.error(\"frameTask error\", error);\n }\n },\n onComplete: () => {},\n task: async ([_desiredSeekTimeMs], { signal }) => {\n const t0 = performance.now();\n\n await withSpan(\n \"video.frameTask\",\n {\n elementId: this.id || \"unknown\",\n desiredSeekTimeMs: _desiredSeekTimeMs,\n src: this.src || \"none\",\n },\n undefined,\n async (span) => {\n const t1 = performance.now();\n span.setAttribute(\"preworkMs\", t1 - t0);\n\n // Attach .catch() to prevent unhandled rejection - errors handled via taskComplete\n this.unifiedVideoSeekTask.run().catch(() => {});\n const t2 = performance.now();\n span.setAttribute(\"seekRunMs\", t2 - t1);\n\n try {\n await this.unifiedVideoSeekTask.taskComplete;\n } catch (error) {\n // If aborted, check our signal and return early if it's also aborted\n if (error instanceof DOMException && error.name === \"AbortError\") {\n signal?.throwIfAborted();\n return; // Our signal not aborted, but seek task was - exit gracefully\n }\n throw error;\n }\n const t3 = performance.now();\n span.setAttribute(\"seekAwaitMs\", t3 - t2);\n // Check abort after async operation\n signal?.throwIfAborted();\n\n const t4 = performance.now();\n this.paint(_desiredSeekTimeMs, span);\n const t5 = performance.now();\n span.setAttribute(\"paintMs\", t5 - t4);\n\n if (!this.parentTimegroup) {\n updateAnimations(this);\n }\n\n span.setAttribute(\"totalFrameMs\", t5 - t0);\n },\n );\n },\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 // Unified video system: smart routing to scrub or main, with background upgrades\n // Note: frameTask guarantees unifiedVideoSeekTask is complete before calling paint\n try {\n const t2 = performance.now();\n const videoSample = this.unifiedVideoSeekTask.value;\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(\"Unified 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 unified video seek task\n */\n get fragmentIndexTask() {\n return this.unifiedVideoSeekTask;\n }\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 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 * 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.mediaEngineTask.taskComplete;\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.mediaEngineTask.taskComplete;\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":"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"}
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":"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"}
@@ -1,23 +1,30 @@
1
+ import { FrameRenderable, FrameState, FrameTask } from "../preview/FrameController.js";
1
2
  import { TemporalMixinInterface } from "./EFTemporal.js";
2
3
  import { EFVideo } from "./EFVideo.js";
3
4
  import { EFAudio } from "./EFAudio.js";
4
5
  import { TargetController } from "./TargetController.js";
5
- import { Task } from "@lit/task";
6
- import * as lit10 from "lit";
6
+ import * as lit6 from "lit";
7
7
  import { LitElement, PropertyValueMap } from "lit";
8
8
  import { Ref } from "lit/directives/ref.js";
9
- import * as lit_html10 from "lit-html";
9
+ import * as lit_html6 from "lit-html";
10
10
 
11
11
  //#region src/elements/EFWaveform.d.ts
12
12
  declare const EFWaveform_base: (new (...args: any[]) => TemporalMixinInterface) & typeof LitElement;
13
- declare class EFWaveform extends EFWaveform_base {
14
- static styles: lit10.CSSResult;
13
+ declare class EFWaveform extends EFWaveform_base implements FrameRenderable {
14
+ #private;
15
+ static styles: lit6.CSSResult;
15
16
  canvasRef: Ref<HTMLCanvasElement>;
16
17
  private ctx;
17
18
  private styleObserver;
18
19
  private resizeObserver?;
19
20
  private mutationObserver?;
20
- render(): lit_html10.TemplateResult<1>;
21
+ /**
22
+ * Get the current render version.
23
+ * Version increments when mode, color, barSpacing, lineWidth, or target changes.
24
+ * @public
25
+ */
26
+ get renderVersion(): number;
27
+ render(): lit_html6.TemplateResult<1>;
21
28
  mode: "roundBars" | "bars" | "bricks" | "line" | "curve" | "pixel" | "wave" | "spikes";
22
29
  color: string;
23
30
  target: string;
@@ -37,7 +44,26 @@ declare class EFWaveform extends EFWaveform_base {
37
44
  protected drawPixel(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
38
45
  protected drawWave(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
39
46
  protected drawSpikes(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array): void;
40
- frameTask: Task<readonly [EFAudio | EFVideo | null, Uint8Array<ArrayBufferLike> | null | undefined], 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
+ /**
53
+ * Query readiness state for a given time.
54
+ * @implements FrameRenderable
55
+ */
56
+ getFrameState(timeMs: number): FrameState;
57
+ /**
58
+ * Async preparation - fetches frequency data from target.
59
+ * @implements FrameRenderable
60
+ */
61
+ prepareFrame(timeMs: number, signal: AbortSignal): Promise<void>;
62
+ /**
63
+ * Synchronous render - draws waveform to canvas.
64
+ * @implements FrameRenderable
65
+ */
66
+ renderFrame(_timeMs: number): void;
41
67
  get durationMs(): number;
42
68
  protected updated(changedProperties: PropertyValueMap<this>): void;
43
69
  }
@@ -1,10 +1,10 @@
1
1
  import { EF_RENDERING } from "../EF_RENDERING.js";
2
- import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
2
+ import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
3
3
  import { EFTemporal } from "./EFTemporal.js";
4
4
  import { TWMixin } from "../gui/TWMixin2.js";
5
+ import { PRIORITY_WAVEFORM, createFrameTaskWrapper } from "../preview/FrameController.js";
5
6
  import { TargetController } from "./TargetController.js";
6
7
  import { CrossUpdateController } from "./CrossUpdateController.js";
7
- import { Task } from "@lit/task";
8
8
  import { LitElement, css, html } from "lit";
9
9
  import { customElement, property, state } from "lit/decorators.js";
10
10
  import { createRef, ref } from "lit/directives/ref.js";
@@ -24,59 +24,7 @@ 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 = new Task(this, {
28
- autoRun: false,
29
- args: () => {
30
- return [this.targetElement, this.targetElement?.frequencyDataTask.value];
31
- },
32
- task: async ([_targetElement, _frequencyData], { signal }) => {
33
- if (!this.targetElement) return;
34
- await this.targetElement.frequencyDataTask.taskComplete;
35
- signal?.throwIfAborted();
36
- this.ctx ||= this.initCanvas();
37
- const ctx = this.ctx;
38
- if (!ctx) return;
39
- const frequencyData = this.targetElement.frequencyDataTask.value;
40
- const byteTimeData = this.targetElement.byteTimeDomainTask.value;
41
- if (!frequencyData || !byteTimeData) return;
42
- ctx.save();
43
- if (this.color === "currentColor") {
44
- const currentColor = getComputedStyle(this).color;
45
- ctx.strokeStyle = currentColor;
46
- ctx.fillStyle = currentColor;
47
- } else {
48
- ctx.strokeStyle = this.color;
49
- ctx.fillStyle = this.color;
50
- }
51
- switch (this.mode) {
52
- case "bars":
53
- this.drawBars(ctx, frequencyData);
54
- break;
55
- case "bricks":
56
- this.drawBricks(ctx, frequencyData);
57
- break;
58
- case "line":
59
- this.drawLine(ctx, byteTimeData);
60
- break;
61
- case "curve":
62
- this.drawCurve(ctx, byteTimeData);
63
- break;
64
- case "pixel":
65
- this.drawPixel(ctx, frequencyData);
66
- break;
67
- case "wave":
68
- this.drawWave(ctx, frequencyData);
69
- break;
70
- case "spikes":
71
- this.drawSpikes(ctx, frequencyData);
72
- break;
73
- case "roundBars":
74
- this.drawRoundBars(ctx, frequencyData);
75
- break;
76
- }
77
- ctx.restore();
78
- }
79
- });
27
+ this.frameTask = createFrameTaskWrapper(this);
80
28
  }
81
29
  static {
82
30
  this.styles = css`
@@ -95,6 +43,19 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
95
43
  }
96
44
  `;
97
45
  }
46
+ /**
47
+ * Render version counter - increments when visual content changes.
48
+ * Used by RenderContext to cache rendered dataURLs.
49
+ */
50
+ #renderVersion = 0;
51
+ /**
52
+ * Get the current render version.
53
+ * Version increments when mode, color, barSpacing, lineWidth, or target changes.
54
+ * @public
55
+ */
56
+ get renderVersion() {
57
+ return this.#renderVersion;
58
+ }
98
59
  render() {
99
60
  return html`<canvas ${ref(this.canvasRef)}></canvas>`;
100
61
  }
@@ -108,12 +69,12 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
108
69
  });
109
70
  this.resizeObserver.observe(this);
110
71
  this.mutationObserver = new MutationObserver((mutationsList) => {
111
- for (const mutation of mutationsList) if (mutation.type === "attributes") this.frameTask.run();
72
+ for (const mutation of mutationsList) if (mutation.type === "attributes" && mutation.attributeName !== "style") this.frameTask.run().catch(() => {});
112
73
  });
113
74
  this.mutationObserver.observe(this, { attributes: true });
114
75
  if (!EF_RENDERING()) {
115
76
  this.styleObserver = new CSSStyleObserver(["color"], () => {
116
- this.frameTask.run();
77
+ this.frameTask.run().catch(() => {});
117
78
  });
118
79
  this.styleObserver.attach(this);
119
80
  }
@@ -126,7 +87,7 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
126
87
  }
127
88
  resizeCanvas() {
128
89
  this.ctx = this.initCanvas();
129
- if (this.ctx) this.frameTask.run();
90
+ if (this.ctx) this.frameTask.run().catch(() => {});
130
91
  }
131
92
  initCanvas() {
132
93
  const canvas = this.canvasRef.value;
@@ -347,13 +308,96 @@ let EFWaveform = class EFWaveform$1 extends EFTemporal(TWMixin(LitElement)) {
347
308
  path.quadraticCurveTo(controlX, controlY, startX, firstY);
348
309
  ctx.fill(path);
349
310
  }
311
+ /**
312
+ * Cached audio analysis data
313
+ */
314
+ #frequencyData = null;
315
+ #timeDomainData = null;
316
+ #dataTimeMs = void 0;
317
+ /**
318
+ * Query readiness state for a given time.
319
+ * @implements FrameRenderable
320
+ */
321
+ getFrameState(timeMs) {
322
+ const hasTarget = !!this.targetElement;
323
+ const hasData = hasTarget && this.#dataTimeMs === timeMs && this.#frequencyData !== null;
324
+ return {
325
+ needsPreparation: hasTarget && !hasData,
326
+ isReady: hasData,
327
+ priority: PRIORITY_WAVEFORM
328
+ };
329
+ }
330
+ /**
331
+ * Async preparation - fetches frequency data from target.
332
+ * @implements FrameRenderable
333
+ */
334
+ async prepareFrame(timeMs, signal) {
335
+ if (!this.targetElement) return;
336
+ const [frequencyData, timeDomainData] = await Promise.all([this.targetElement.getFrequencyData(timeMs, signal), this.targetElement.getTimeDomainData(timeMs, signal)]);
337
+ signal.throwIfAborted();
338
+ this.#frequencyData = frequencyData;
339
+ this.#timeDomainData = timeDomainData;
340
+ this.#dataTimeMs = timeMs;
341
+ }
342
+ /**
343
+ * Synchronous render - draws waveform to canvas.
344
+ * @implements FrameRenderable
345
+ */
346
+ renderFrame(_timeMs) {
347
+ if (!this.targetElement) return;
348
+ this.ctx ||= this.initCanvas();
349
+ const ctx = this.ctx;
350
+ if (!ctx) return;
351
+ const frequencyData = this.#frequencyData;
352
+ const byteTimeData = this.#timeDomainData;
353
+ if (!frequencyData || !byteTimeData) return;
354
+ ctx.save();
355
+ if (this.color === "currentColor") {
356
+ const currentColor = getComputedStyle(this).color;
357
+ ctx.strokeStyle = currentColor;
358
+ ctx.fillStyle = currentColor;
359
+ } else {
360
+ ctx.strokeStyle = this.color;
361
+ ctx.fillStyle = this.color;
362
+ }
363
+ switch (this.mode) {
364
+ case "bars":
365
+ this.drawBars(ctx, frequencyData);
366
+ break;
367
+ case "bricks":
368
+ this.drawBricks(ctx, frequencyData);
369
+ break;
370
+ case "line":
371
+ this.drawLine(ctx, byteTimeData);
372
+ break;
373
+ case "curve":
374
+ this.drawCurve(ctx, byteTimeData);
375
+ break;
376
+ case "pixel":
377
+ this.drawPixel(ctx, frequencyData);
378
+ break;
379
+ case "wave":
380
+ this.drawWave(ctx, frequencyData);
381
+ break;
382
+ case "spikes":
383
+ this.drawSpikes(ctx, frequencyData);
384
+ break;
385
+ case "roundBars":
386
+ this.drawRoundBars(ctx, frequencyData);
387
+ break;
388
+ }
389
+ ctx.restore();
390
+ }
350
391
  get durationMs() {
351
392
  if (!this.targetElement) return 0;
352
393
  return this.targetElement.durationMs;
353
394
  }
354
395
  updated(changedProperties) {
355
396
  super.updated(changedProperties);
356
- if (changedProperties.size > 0) this.frameTask.run();
397
+ if (changedProperties.size > 0) {
398
+ this.#renderVersion++;
399
+ this.frameTask.run().catch(() => {});
400
+ }
357
401
  }
358
402
  };
359
403
  __decorate([property({