@editframe/elements 0.37.3-beta → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) 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 +4 -4
  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 +7 -10
  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 +4 -4
  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 +4 -4
  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 +4 -4
  107. package/dist/gui/EFFocusOverlay.js +3 -3
  108. package/dist/gui/EFFocusOverlay.js.map +1 -1
  109. package/dist/gui/EFOverlayItem.d.ts +4 -4
  110. package/dist/gui/EFOverlayLayer.d.ts +4 -4
  111. package/dist/gui/EFPause.d.ts +4 -4
  112. package/dist/gui/EFPause.js +1 -1
  113. package/dist/gui/EFPlay.d.ts +4 -4
  114. package/dist/gui/EFPlay.js +1 -1
  115. package/dist/gui/EFPreview.js +1 -1
  116. package/dist/gui/EFResizableBox.d.ts +4 -4
  117. package/dist/gui/EFResizableBox.js +5 -5
  118. package/dist/gui/EFResizableBox.js.map +1 -1
  119. package/dist/gui/EFScrubber.d.ts +4 -4
  120. package/dist/gui/EFScrubber.js +8 -13
  121. package/dist/gui/EFScrubber.js.map +1 -1
  122. package/dist/gui/EFTimeDisplay.d.ts +8 -4
  123. package/dist/gui/EFTimeDisplay.js +25 -7
  124. package/dist/gui/EFTimeDisplay.js.map +1 -1
  125. package/dist/gui/EFTimelineRuler.d.ts +4 -4
  126. package/dist/gui/EFTimelineRuler.js +3 -3
  127. package/dist/gui/EFTimelineRuler.js.map +1 -1
  128. package/dist/gui/EFToggleLoop.d.ts +4 -4
  129. package/dist/gui/EFToggleLoop.js +1 -1
  130. package/dist/gui/EFTogglePlay.d.ts +4 -4
  131. package/dist/gui/EFTogglePlay.js +1 -1
  132. package/dist/gui/EFTransformHandles.d.ts +4 -4
  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 +3 -1
  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 +4 -4
  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 +4 -4
  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.js +267 -145
  238. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  239. package/dist/preview/renderTimegroupToCanvas.types.d.ts +30 -0
  240. package/dist/preview/renderTimegroupToVideo.js +85 -105
  241. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  242. package/dist/preview/{renderTimegroupToVideo.d.ts → renderTimegroupToVideo.types.d.ts} +9 -9
  243. package/dist/preview/renderVideoToVideo.js +286 -0
  244. package/dist/preview/renderVideoToVideo.js.map +1 -0
  245. package/dist/preview/renderers.js.map +1 -1
  246. package/dist/preview/rendering/ScaleConfig.js +74 -0
  247. package/dist/preview/rendering/ScaleConfig.js.map +1 -0
  248. package/dist/preview/rendering/inlineImages.js +1 -44
  249. package/dist/preview/rendering/inlineImages.js.map +1 -1
  250. package/dist/preview/rendering/loadImage.js +22 -0
  251. package/dist/preview/rendering/loadImage.js.map +1 -0
  252. package/dist/preview/rendering/renderToImageNative.js +3 -3
  253. package/dist/preview/rendering/renderToImageNative.js.map +1 -1
  254. package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
  255. package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
  256. package/dist/preview/statsTrackingStrategy.js +1 -101
  257. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  258. package/dist/preview/workers/WorkerPool.js +0 -1
  259. package/dist/preview/workers/WorkerPool.js.map +1 -1
  260. package/dist/preview/workers/encoderWorkerInline.js +21 -54
  261. package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
  262. package/dist/render/EFRenderAPI.d.ts +2 -1
  263. package/dist/render/EFRenderAPI.js +12 -36
  264. package/dist/render/EFRenderAPI.js.map +1 -1
  265. package/dist/render/getRenderData.js +4 -4
  266. package/dist/render/getRenderData.js.map +1 -1
  267. package/dist/style.css +114 -163
  268. package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
  269. package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
  270. package/dist/transcoding/types/index.d.ts +1 -1
  271. package/dist/transcoding/utils/UrlGenerator.js +10 -3
  272. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  273. package/dist/utils/LRUCache.js +1 -0
  274. package/dist/utils/LRUCache.js.map +1 -1
  275. package/dist/utils/frameTime.js +23 -1
  276. package/dist/utils/frameTime.js.map +1 -1
  277. package/package.json +21 -8
  278. package/scripts/build-css.js +8 -1
  279. package/test/setup.ts +0 -1
  280. package/test/useAssetMSW.ts +50 -0
  281. package/test/visualRegressionUtils.ts +23 -9
  282. package/dist/_virtual/rolldown_runtime.js +0 -27
  283. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
  284. package/dist/elements/EFThumbnailStrip.d.ts +0 -167
  285. package/dist/elements/EFThumbnailStrip.js +0 -731
  286. package/dist/elements/EFThumbnailStrip.js.map +0 -1
  287. package/dist/elements/SessionThumbnailCache.js +0 -154
  288. package/dist/elements/SessionThumbnailCache.js.map +0 -1
  289. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
  290. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
  291. package/dist/node_modules/react/cjs/react.development.js +0 -1521
  292. package/dist/node_modules/react/cjs/react.development.js.map +0 -1
  293. package/dist/node_modules/react/index.js +0 -13
  294. package/dist/node_modules/react/index.js.map +0 -1
  295. package/dist/node_modules/react/jsx-runtime.js +0 -13
  296. package/dist/node_modules/react/jsx-runtime.js.map +0 -1
  297. package/dist/preview/encoding/types.d.ts +0 -1
  298. package/dist/preview/renderTimegroupPreview.js +0 -686
  299. package/dist/preview/renderTimegroupPreview.js.map +0 -1
  300. package/dist/preview/renderTimegroupToCanvas.d.ts +0 -42
  301. package/dist/preview/rendering/renderToImage.d.ts +0 -2
  302. package/dist/preview/rendering/renderToImage.js +0 -95
  303. package/dist/preview/rendering/renderToImage.js.map +0 -1
  304. package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
  305. package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
  306. package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
  307. package/dist/preview/rendering/svgSerializer.js +0 -43
  308. package/dist/preview/rendering/svgSerializer.js.map +0 -1
  309. package/dist/preview/rendering/types.d.ts +0 -2
  310. package/dist/preview/thumbnailCacheSettings.js +0 -52
  311. package/dist/preview/thumbnailCacheSettings.js.map +0 -1
  312. package/dist/sandbox/PlaybackControls.d.ts +0 -1
  313. package/dist/sandbox/PlaybackControls.js +0 -10
  314. package/dist/sandbox/PlaybackControls.js.map +0 -1
  315. package/dist/sandbox/ScenarioRunner.d.ts +0 -1
  316. package/dist/sandbox/ScenarioRunner.js +0 -1
  317. package/dist/sandbox/defineSandbox.d.ts +0 -1
  318. package/dist/sandbox/index.d.ts +0 -3
  319. package/dist/sandbox/index.js +0 -2
  320. package/test/EFVideo.framegen.browsertest.ts +0 -80
  321. package/test/thumbnail-performance-test.html +0 -116
@@ -0,0 +1,72 @@
1
+ import { RenderContext } from "./RenderContext.js";
2
+ import { DEFAULT_HEIGHT, DEFAULT_WIDTH } from "./previewTypes.js";
3
+ import { captureTimelineToDataUri } from "./rendering/serializeTimelineDirect.js";
4
+ import { getEffectiveRenderMode } from "./renderers.js";
5
+ import { loadImageFromDataUri } from "./rendering/loadImage.js";
6
+ import { renderToImageNative } from "./rendering/renderToImageNative.js";
7
+
8
+ //#region src/preview/renderElementToCanvas.ts
9
+ /**
10
+ * Render any element to canvas or image.
11
+ *
12
+ * This is a low-level rendering function that renders the element as-is.
13
+ * The caller is responsible for:
14
+ * - Creating clones if needed
15
+ * - Seeking to the correct time
16
+ * - Finding the correct element to render
17
+ *
18
+ * Use cases:
19
+ * - Preview: Pass prime timeline element (already at correct time)
20
+ * - Video/thumbnails: Pass element from reused clone (already seeked)
21
+ * - One-off capture: Create clone, seek, pass element, clean up
22
+ *
23
+ * @param element - Element to render (timegroup, temporal element, or plain DOM)
24
+ * @param options - Render options
25
+ * @returns Canvas or Image (both are CanvasImageSource)
26
+ */
27
+ async function renderElementToCanvas(element, options) {
28
+ return await renderElementToImage(element, options);
29
+ }
30
+ /**
31
+ * Render an element using either native or foreignObject mode.
32
+ * Returns Canvas or Image directly without unnecessary copying.
33
+ */
34
+ async function renderElementToImage(element, options) {
35
+ const { timeMs, scale = 1 } = options;
36
+ const computedStyle = getComputedStyle(element);
37
+ const width = options.width ?? (parseFloat(computedStyle.width) || DEFAULT_WIDTH);
38
+ const height = options.height ?? (parseFloat(computedStyle.height) || DEFAULT_HEIGHT);
39
+ const renderContext = options.renderContext ?? new RenderContext();
40
+ const shouldDisposeContext = !options.renderContext;
41
+ try {
42
+ if ((options.renderMode ?? getEffectiveRenderMode()) === "native") {
43
+ const elementContainer = document.createElement("div");
44
+ elementContainer.style.cssText = `
45
+ position: fixed;
46
+ left: 0;
47
+ top: 0;
48
+ width: ${width}px;
49
+ height: ${height}px;
50
+ pointer-events: none;
51
+ overflow: hidden;
52
+ `;
53
+ elementContainer.appendChild(element.cloneNode(true));
54
+ document.body.appendChild(elementContainer);
55
+ try {
56
+ return await renderToImageNative(elementContainer, width, height, { skipDprScaling: true });
57
+ } finally {
58
+ elementContainer.remove();
59
+ }
60
+ } else return await loadImageFromDataUri(await captureTimelineToDataUri(element, width, height, {
61
+ renderContext,
62
+ canvasScale: scale,
63
+ timeMs
64
+ }));
65
+ } finally {
66
+ if (shouldDisposeContext) renderContext.dispose();
67
+ }
68
+ }
69
+
70
+ //#endregion
71
+ export { renderElementToCanvas };
72
+ //# sourceMappingURL=renderElementToCanvas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderElementToCanvas.js","names":[],"sources":["../../src/preview/renderElementToCanvas.ts"],"sourcesContent":["/**\n * Render any DOM element to canvas.\n *\n * Low-level rendering function that renders elements as-is.\n * Supports both native (drawElementImage) and foreignObject render modes.\n *\n * Caller is responsible for clone management and seeking.\n */\n\nimport { getEffectiveRenderMode } from \"./renderers.js\";\nimport type { RenderMode } from \"./previewSettings.js\";\nimport { RenderContext } from \"./RenderContext.js\";\nimport { captureTimelineToDataUri } from \"./rendering/serializeTimelineDirect.js\";\nimport { loadImageFromDataUri } from \"./rendering/loadImage.js\";\nimport { renderToImageNative } from \"./rendering/renderToImageNative.js\";\nimport { DEFAULT_WIDTH, DEFAULT_HEIGHT } from \"./previewTypes.js\";\n\n/**\n * Options for rendering an element to canvas.\n */\nexport interface RenderElementOptions {\n /** Time to render at in milliseconds (used for serialization metadata) */\n timeMs: number;\n /** Scale factor for canvas encoding (default: 1.0) */\n scale?: number;\n /** Output width in pixels (defaults to element's computed width or 1920) */\n width?: number;\n /** Output height in pixels (defaults to element's computed height or 1080) */\n height?: number;\n /** Render context for canvas pixel caching */\n renderContext?: RenderContext;\n /** Override render mode (native or foreignObject) */\n renderMode?: RenderMode;\n}\n\n/**\n * Render any element to canvas or image.\n *\n * This is a low-level rendering function that renders the element as-is.\n * The caller is responsible for:\n * - Creating clones if needed\n * - Seeking to the correct time\n * - Finding the correct element to render\n *\n * Use cases:\n * - Preview: Pass prime timeline element (already at correct time)\n * - Video/thumbnails: Pass element from reused clone (already seeked)\n * - One-off capture: Create clone, seek, pass element, clean up\n *\n * @param element - Element to render (timegroup, temporal element, or plain DOM)\n * @param options - Render options\n * @returns Canvas or Image (both are CanvasImageSource)\n */\nexport async function renderElementToCanvas(\n element: Element,\n options: RenderElementOptions,\n): Promise<CanvasImageSource> {\n return await renderElementToImage(element, options);\n}\n\n/**\n * Render an element using either native or foreignObject mode.\n * Returns Canvas or Image directly without unnecessary copying.\n */\nasync function renderElementToImage(\n element: Element,\n options: RenderElementOptions,\n): Promise<CanvasImageSource> {\n const { timeMs, scale = 1.0 } = options;\n\n // Get element dimensions\n const computedStyle = getComputedStyle(element);\n const width =\n options.width ?? (parseFloat(computedStyle.width) || DEFAULT_WIDTH);\n const height =\n options.height ?? (parseFloat(computedStyle.height) || DEFAULT_HEIGHT);\n\n // Create render context for caching\n const renderContext = options.renderContext ?? new RenderContext();\n const shouldDisposeContext = !options.renderContext;\n\n try {\n // Determine render mode\n const renderMode = options.renderMode ?? getEffectiveRenderMode();\n\n if (renderMode === \"native\") {\n // NATIVE PATH: Render element using drawElementImage\n const elementContainer = document.createElement(\"div\");\n elementContainer.style.cssText = `\n position: fixed;\n left: 0;\n top: 0;\n width: ${width}px;\n height: ${height}px;\n pointer-events: none;\n overflow: hidden;\n `;\n\n // Clone element into container\n elementContainer.appendChild(element.cloneNode(true));\n document.body.appendChild(elementContainer);\n\n try {\n // Return canvas directly - no copy needed!\n return await renderToImageNative(elementContainer, width, height, {\n skipDprScaling: true,\n });\n } finally {\n elementContainer.remove();\n }\n } else {\n // FOREIGNOBJECT PATH: Direct serialization\n const dataUri = await captureTimelineToDataUri(element, width, height, {\n renderContext,\n canvasScale: scale,\n timeMs,\n });\n\n // Return image directly - no copy needed!\n return await loadImageFromDataUri(dataUri);\n }\n } finally {\n if (shouldDisposeContext) {\n renderContext.dispose();\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,eAAsB,sBACpB,SACA,SAC4B;AAC5B,QAAO,MAAM,qBAAqB,SAAS,QAAQ;;;;;;AAOrD,eAAe,qBACb,SACA,SAC4B;CAC5B,MAAM,EAAE,QAAQ,QAAQ,MAAQ;CAGhC,MAAM,gBAAgB,iBAAiB,QAAQ;CAC/C,MAAM,QACJ,QAAQ,UAAU,WAAW,cAAc,MAAM,IAAI;CACvD,MAAM,SACJ,QAAQ,WAAW,WAAW,cAAc,OAAO,IAAI;CAGzD,MAAM,gBAAgB,QAAQ,iBAAiB,IAAI,eAAe;CAClE,MAAM,uBAAuB,CAAC,QAAQ;AAEtC,KAAI;AAIF,OAFmB,QAAQ,cAAc,wBAAwB,MAE9C,UAAU;GAE3B,MAAM,mBAAmB,SAAS,cAAc,MAAM;AACtD,oBAAiB,MAAM,UAAU;;;;iBAItB,MAAM;kBACL,OAAO;;;;AAMnB,oBAAiB,YAAY,QAAQ,UAAU,KAAK,CAAC;AACrD,YAAS,KAAK,YAAY,iBAAiB;AAE3C,OAAI;AAEF,WAAO,MAAM,oBAAoB,kBAAkB,OAAO,QAAQ,EAChE,gBAAgB,MACjB,CAAC;aACM;AACR,qBAAiB,QAAQ;;QAW3B,QAAO,MAAM,qBAPG,MAAM,yBAAyB,SAAS,OAAO,QAAQ;GACrE;GACA,aAAa;GACb;GACD,CAAC,CAGwC;WAEpC;AACR,MAAI,qBACF,eAAc,SAAS"}
@@ -1,13 +1,15 @@
1
1
  import { FrameController } from "./FrameController.js";
2
+ import { updateAnimations } from "../elements/updateAnimations.js";
2
3
  import { logger } from "./logger.js";
3
- import { DEFAULT_BLOCKING_TIMEOUT_MS, DEFAULT_HEIGHT, DEFAULT_THUMBNAIL_SCALE, DEFAULT_WIDTH, createPreviewContainer, isVisibleAtTime } from "./previewTypes.js";
4
- import { buildCloneStructure, collectDocumentStyles, overrideRootCloneStyles, removeHiddenNodesForSerialization, restoreHiddenNodes } from "./renderTimegroupPreview.js";
5
- import { getEffectiveRenderMode } from "./renderers.js";
6
4
  import { RenderContext } from "./RenderContext.js";
5
+ import { DEFAULT_BLOCKING_TIMEOUT_MS, DEFAULT_CAPTURE_SCALE, DEFAULT_HEIGHT, DEFAULT_WIDTH, isVisibleAtTime } from "./previewTypes.js";
6
+ import { captureTimelineToDataUri } from "./rendering/serializeTimelineDirect.js";
7
+ import { getRenderMode, isNativeCanvasApiAvailable } from "./previewSettings.js";
8
+ import { getEffectiveRenderMode } from "./renderers.js";
7
9
  import { defaultProfiler } from "./RenderProfiler.js";
10
+ import { loadImageFromDataUri } from "./rendering/loadImage.js";
8
11
  import { createDprCanvas, renderToImageNative } from "./rendering/renderToImageNative.js";
9
12
  import { clearInlineImageCache } from "./rendering/inlineImages.js";
10
- import { loadImageFromDataUri, renderToImage } from "./rendering/renderToImage.js";
11
13
 
12
14
  //#region src/preview/renderTimegroupToCanvas.ts
13
15
  /** Number of rows to sample when checking canvas content */
@@ -57,37 +59,34 @@ function resetRenderState() {
57
59
  resetCacheMetrics();
58
60
  }
59
61
  /**
60
- * Create a debug label for showing render info.
61
- */
62
- function createDebugLabel() {
63
- const debugLabel = document.createElement("div");
64
- debugLabel.style.cssText = `
65
- position: absolute;
66
- top: -24px;
67
- left: 0;
68
- padding: 2px 8px;
69
- font: bold 12px monospace;
70
- background: rgba(0, 0, 0, 0.8);
71
- border-radius: 3px;
72
- white-space: nowrap;
73
- z-index: 1000;
74
- pointer-events: none;
75
- `;
76
- return debugLabel;
77
- }
78
- /**
79
- * Update debug label with resolution info.
62
+ * DEBUG: Capture a single thumbnail at the current time.
63
+ * Call from console: window.debugCaptureThumbnail()
80
64
  */
81
- function updateDebugLabel(label, renderWidth, renderHeight, resolutionScale) {
82
- const scaleColors = {
83
- 1: "#00ff00",
84
- .75: "#ffff00",
85
- .5: "#ff8800",
86
- .25: "#ff0000"
87
- };
88
- label.style.color = scaleColors[resolutionScale] || "#ffffff";
89
- label.textContent = `Render: ${renderWidth}x${renderHeight} (${Math.round(resolutionScale * 100)}%)`;
90
- }
65
+ if (typeof window !== "undefined") window.debugCaptureThumbnail = async function() {
66
+ const timegroup = document.querySelector("ef-timegroup");
67
+ if (!timegroup) {
68
+ console.error("No timegroup found");
69
+ return;
70
+ }
71
+ const currentTime = timegroup.currentTimeMs ?? 0;
72
+ try {
73
+ const result = await captureTimegroupAtTime(timegroup, {
74
+ timeMs: currentTime,
75
+ scale: .25,
76
+ contentReadyMode: "blocking",
77
+ blockingTimeoutMs: 1e3
78
+ });
79
+ const img = document.createElement("img");
80
+ if (result instanceof HTMLCanvasElement) img.src = result.toDataURL();
81
+ else if (result instanceof HTMLImageElement) img.src = result.src;
82
+ img.style.cssText = "position:fixed;top:10px;right:10px;border:2px solid red;z-index:99999;";
83
+ document.body.appendChild(img);
84
+ return result;
85
+ } catch (err) {
86
+ console.error("[DEBUG] Capture failed:", err);
87
+ throw err;
88
+ }
89
+ };
91
90
  /**
92
91
  * Wait for next animation frame (allows browser to complete layout)
93
92
  */
@@ -99,7 +98,7 @@ function waitForFrame() {
99
98
  * Returns true if there's ANY non-transparent pixel.
100
99
  */
101
100
  function canvasHasContent(canvas) {
102
- const ctx = canvas.getContext("2d");
101
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
103
102
  if (!ctx) return false;
104
103
  try {
105
104
  const width = canvas.width;
@@ -167,105 +166,157 @@ async function waitForVideoContent(timegroup, timeMs, maxWaitMs) {
167
166
  /**
168
167
  * Captures a frame from an already-seeked render clone.
169
168
  * Used internally by captureBatch for efficiency (reuses one clone across all captures).
170
- *
169
+ *
171
170
  * @param renderClone - A render clone that has already been seeked to the target time
172
171
  * @param renderContainer - The container holding the render clone (from createRenderClone)
173
172
  * @param options - Capture options
174
- * @returns Canvas with the rendered frame
173
+ * @returns Canvas or Image with the rendered frame (both are CanvasImageSource)
175
174
  */
176
- async function captureFromClone(renderClone, renderContainer, options = {}) {
177
- const { scale = DEFAULT_THUMBNAIL_SCALE, contentReadyMode = "immediate", blockingTimeoutMs = DEFAULT_BLOCKING_TIMEOUT_MS, originalTimegroup } = options;
175
+ async function captureFromClone(renderClone, _renderContainer, options = {}) {
176
+ const { scale = DEFAULT_CAPTURE_SCALE, contentReadyMode = "immediate", blockingTimeoutMs = DEFAULT_BLOCKING_TIMEOUT_MS, originalTimegroup, timeMs: explicitTimeMs, canvasMode } = options;
177
+ const timeMs = explicitTimeMs ?? renderClone.currentTimeMs;
178
178
  const sourceForDimensions = originalTimegroup ?? renderClone;
179
179
  const width = sourceForDimensions.offsetWidth || DEFAULT_WIDTH;
180
180
  const height = sourceForDimensions.offsetHeight || DEFAULT_HEIGHT;
181
- const dpr = window.devicePixelRatio || 1;
182
- const canvas = document.createElement("canvas");
183
- canvas.width = Math.floor(width * scale * dpr);
184
- canvas.height = Math.floor(height * scale * dpr);
185
- canvas.style.width = `${Math.floor(width * scale)}px`;
186
- canvas.style.height = `${Math.floor(height * scale)}px`;
187
- const ctx = canvas.getContext("2d");
188
- if (!ctx) throw new Error("Failed to get canvas 2d context");
189
- const timeMs = renderClone.currentTimeMs;
190
181
  if (contentReadyMode === "blocking") {
191
182
  const result = await waitForVideoContent(renderClone, timeMs, blockingTimeoutMs);
192
183
  if (!result.ready) throw new ContentNotReadyError(timeMs, blockingTimeoutMs, result.blankVideos);
193
184
  }
185
+ const effectiveCanvasMode = (() => {
186
+ if (!canvasMode) return "foreignObject";
187
+ if (canvasMode === "native" && !isNativeCanvasApiAvailable()) {
188
+ logger.debug("[captureFromClone] Native canvas mode requested but not available, falling back to foreignObject");
189
+ return "foreignObject";
190
+ }
191
+ return canvasMode;
192
+ })();
194
193
  const renderContext = new RenderContext();
195
194
  try {
196
- let image;
197
- if (getEffectiveRenderMode() === "native") {
198
- renderContainer.style.cssText = `
199
- position: fixed;
200
- left: 0;
201
- top: 0;
202
- width: ${width}px;
203
- height: ${height}px;
204
- pointer-events: none;
205
- overflow: hidden;
206
- `;
207
- image = await renderToImageNative(renderContainer, width, height, { skipDprScaling: true });
195
+ if (effectiveCanvasMode === "native") {
196
+ const t0 = performance.now();
197
+ const canvas = await renderToImageNative(renderClone, width, height, { skipDprScaling: true });
198
+ const renderTime = performance.now() - t0;
199
+ logger.debug(`[captureFromClone] native render=${renderTime.toFixed(0)}ms (canvasScale=${scale})`);
200
+ return canvas;
208
201
  } else {
209
202
  const t0 = performance.now();
210
- const { container: container$1, syncState } = buildCloneStructure(renderClone, timeMs);
211
- const buildTime = performance.now() - t0;
212
- const bgSource = originalTimegroup ?? renderClone;
213
- const previewContainer = createPreviewContainer({
214
- width,
215
- height,
216
- background: getComputedStyle(bgSource).background || "#000"
217
- });
218
- const t1 = performance.now();
219
- const styleEl = document.createElement("style");
220
- styleEl.textContent = collectDocumentStyles();
221
- const stylesTime = performance.now() - t1;
222
- previewContainer.appendChild(styleEl);
223
- previewContainer.appendChild(container$1);
224
- overrideRootCloneStyles(syncState, true);
225
- const t2 = performance.now();
226
- image = await renderToImage(previewContainer, width, height, {
227
- canvasScale: scale,
203
+ const dataUri = await captureTimelineToDataUri(renderClone, width, height, {
228
204
  renderContext,
229
- sourceMap: syncState.canvasSourceMap
205
+ canvasScale: scale,
206
+ timeMs
230
207
  });
231
- const renderTime = performance.now() - t2;
232
- logger.debug(`[captureFromClone] build=${buildTime.toFixed(0)}ms, styles=${stylesTime.toFixed(0)}ms, render=${renderTime.toFixed(0)}ms (canvasScale=${scale})`);
208
+ const serializeTime = performance.now() - t0;
209
+ const t1 = performance.now();
210
+ const image = await loadImageFromDataUri(dataUri);
211
+ const loadTime = performance.now() - t1;
212
+ logger.debug(`[captureFromClone] foreignObject serialize=${serializeTime.toFixed(0)}ms, load=${loadTime.toFixed(0)}ms (canvasScale=${scale})`);
213
+ return image;
233
214
  }
234
- const srcWidth = image.width;
235
- const srcHeight = image.height;
236
- ctx.drawImage(image, 0, 0, srcWidth, srcHeight, 0, 0, canvas.width, canvas.height);
237
- return canvas;
238
215
  } finally {
239
216
  renderContext.dispose();
240
217
  }
241
218
  }
242
219
  /**
243
220
  * Captures a single frame from a timegroup at a specific time.
244
- *
221
+ *
245
222
  * CLONE-TIMELINE ARCHITECTURE:
246
223
  * Creates an independent render clone, seeks it to the target time, and captures.
247
224
  * Prime-timeline is NEVER seeked - user can continue previewing/editing during capture.
248
- *
225
+ *
249
226
  * @param timegroup - The source timegroup
250
227
  * @param options - Capture options including timeMs, scale, contentReadyMode
251
228
  * @returns Canvas with the rendered frame
252
229
  * @throws ContentNotReadyError if blocking mode times out waiting for video content
253
230
  */
254
231
  async function captureTimegroupAtTime(timegroup, options) {
255
- const { timeMs, scale = DEFAULT_THUMBNAIL_SCALE, contentReadyMode = "immediate", blockingTimeoutMs = DEFAULT_BLOCKING_TIMEOUT_MS } = options;
232
+ const { timeMs, scale = DEFAULT_CAPTURE_SCALE, contentReadyMode = "immediate", blockingTimeoutMs = DEFAULT_BLOCKING_TIMEOUT_MS, canvasMode, skipClone = false } = options;
233
+ if (skipClone) {
234
+ const seekStart = performance.now();
235
+ await timegroup.seekForRender(timeMs);
236
+ const seekMs = performance.now() - seekStart;
237
+ const renderStart = performance.now();
238
+ const result = await captureFromClone(timegroup, timegroup.parentElement || document.body, {
239
+ scale,
240
+ contentReadyMode,
241
+ blockingTimeoutMs,
242
+ originalTimegroup: void 0,
243
+ canvasMode,
244
+ timeMs
245
+ });
246
+ const renderMs = performance.now() - renderStart;
247
+ if (typeof result === "object" && result !== null) result.__perfTiming = {
248
+ cloneMs: 0,
249
+ seekMs,
250
+ renderMs
251
+ };
252
+ return result;
253
+ }
254
+ const cloneStart = performance.now();
256
255
  const { clone: renderClone, container: renderContainer, cleanup: cleanupRenderClone } = await timegroup.createRenderClone();
256
+ const cloneMs = performance.now() - cloneStart;
257
257
  try {
258
+ const seekStart = performance.now();
258
259
  await renderClone.seekForRender(timeMs);
259
- return await captureFromClone(renderClone, renderContainer, {
260
+ const seekMs = performance.now() - seekStart;
261
+ const renderStart = performance.now();
262
+ const result = await captureFromClone(renderClone, renderContainer, {
260
263
  scale,
261
264
  contentReadyMode,
262
265
  blockingTimeoutMs,
263
- originalTimegroup: timegroup
266
+ originalTimegroup: timegroup,
267
+ canvasMode
264
268
  });
269
+ const renderMs = performance.now() - renderStart;
270
+ if (typeof result === "object" && result !== null) result.__perfTiming = {
271
+ cloneMs,
272
+ seekMs,
273
+ renderMs
274
+ };
275
+ return result;
265
276
  } finally {
266
277
  cleanupRenderClone();
267
278
  }
268
279
  }
280
+ /**
281
+ * Generate thumbnails using an existing render clone and mutable queue.
282
+ * The queue can be modified while generation is in progress.
283
+ *
284
+ * @param renderClone - Pre-created render clone to use
285
+ * @param renderContainer - Container for the render clone
286
+ * @param queue - Mutable queue that provides timestamps
287
+ * @param options - Capture options (scale, contentReadyMode, etc.)
288
+ * @yields Objects with { timeMs, canvas } for each captured thumbnail
289
+ *
290
+ * @example
291
+ * ```ts
292
+ * const queue = new MutableTimestampQueue();
293
+ * queue.reset([0, 100, 200]);
294
+ *
295
+ * for await (const { timeMs, canvas } of generateThumbnailsFromClone(clone, container, queue)) {
296
+ * cache.set(timeMs, canvas);
297
+ * // Queue can be modified here while generator continues
298
+ * }
299
+ * ```
300
+ */
301
+ async function* generateThumbnailsFromClone(renderClone, renderContainer, queue, options = {}) {
302
+ const { scale = DEFAULT_CAPTURE_SCALE, contentReadyMode = "immediate", blockingTimeoutMs = DEFAULT_BLOCKING_TIMEOUT_MS, signal } = options;
303
+ while (true) {
304
+ if (signal?.aborted) break;
305
+ const timeMs = queue.shift();
306
+ if (timeMs === void 0) break;
307
+ await renderClone.seekForRender(timeMs);
308
+ if (signal?.aborted) break;
309
+ yield {
310
+ timeMs,
311
+ canvas: await captureFromClone(renderClone, renderContainer, {
312
+ scale,
313
+ contentReadyMode,
314
+ blockingTimeoutMs,
315
+ timeMs
316
+ })
317
+ };
318
+ }
319
+ }
269
320
  /** Epsilon for comparing time values (ms) - times within this are considered equal */
270
321
  const TIME_EPSILON_MS = 1;
271
322
  /** Default scale for preview rendering */
@@ -282,10 +333,10 @@ function toAbsoluteTime(timegroup, relativeTimeMs) {
282
333
  }
283
334
  /**
284
335
  * Renders a timegroup preview to a canvas using SVG foreignObject.
285
- *
336
+ *
286
337
  * Captures the prime timeline's current visual state including DOM changes
287
338
  * from frame tasks (SVG paths, canvas content, text updates, etc.).
288
- *
339
+ *
289
340
  * Optimized with:
290
341
  * - Passive clone structure rebuilt each frame from prime's current state
291
342
  * - Temporal bucketing for time-based culling
@@ -302,7 +353,7 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
302
353
  let currentResolutionScale = options.resolutionScale ?? DEFAULT_RESOLUTION_SCALE;
303
354
  const width = timegroup.offsetWidth || DEFAULT_WIDTH;
304
355
  const height = timegroup.offsetHeight || DEFAULT_HEIGHT;
305
- const dpr = window.devicePixelRatio || 1;
356
+ const dpr = (typeof window !== "undefined" ? window.devicePixelRatio : 1) || 1;
306
357
  let renderWidth = Math.floor(width * currentResolutionScale);
307
358
  let renderHeight = Math.floor(height * currentResolutionScale);
308
359
  const canvas = createDprCanvas({
@@ -313,11 +364,7 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
313
364
  fullHeight: height,
314
365
  dpr
315
366
  });
316
- const wrapperContainer = document.createElement("div");
317
- wrapperContainer.style.cssText = "position: relative; display: inline-block;";
318
- const debugLabel = createDebugLabel();
319
- wrapperContainer.appendChild(debugLabel);
320
- wrapperContainer.appendChild(canvas);
367
+ const wrapperContainer = canvas;
321
368
  const ctx = canvas.getContext("2d");
322
369
  if (!ctx) throw new Error("Failed to get canvas 2d context");
323
370
  let rendering = false;
@@ -325,16 +372,34 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
325
372
  let disposed = false;
326
373
  const renderContext = new RenderContext();
327
374
  const frameController = new FrameController(timegroup);
328
- const previewContainer = createPreviewContainer({
329
- width: renderWidth,
330
- height: renderHeight,
331
- background: getComputedStyle(timegroup).background || "#000"
332
- });
333
- const styleEl = document.createElement("style");
334
- styleEl.textContent = collectDocumentStyles();
335
- previewContainer.appendChild(styleEl);
336
375
  let hasLoggedScale = false;
337
376
  let pendingResolutionScale = null;
377
+ const useNative = getRenderMode() === "native" && isNativeCanvasApiAvailable();
378
+ let captureCanvas = null;
379
+ let captureCtx = null;
380
+ let originalParent = null;
381
+ let originalNextSibling = null;
382
+ let savedClipPath = "";
383
+ let savedPointerEvents = "";
384
+ if (useNative) {
385
+ captureCanvas = document.createElement("canvas");
386
+ captureCanvas.setAttribute("layoutsubtree", "");
387
+ captureCanvas.layoutSubtree = true;
388
+ captureCanvas.width = renderWidth;
389
+ captureCanvas.height = renderHeight;
390
+ captureCanvas.style.cssText = `position:fixed;left:0;top:0;width:${width}px;height:${height}px;opacity:0;pointer-events:none;z-index:-9999;`;
391
+ originalParent = timegroup.parentNode;
392
+ originalNextSibling = timegroup.nextSibling;
393
+ savedClipPath = timegroup.style.clipPath;
394
+ savedPointerEvents = timegroup.style.pointerEvents;
395
+ timegroup.style.clipPath = "";
396
+ timegroup.style.pointerEvents = "";
397
+ captureCanvas.appendChild(timegroup);
398
+ document.body.appendChild(captureCanvas);
399
+ captureCtx = captureCanvas.getContext("2d");
400
+ captureCanvas.offsetHeight;
401
+ timegroup.offsetHeight;
402
+ }
338
403
  /**
339
404
  * Apply pending resolution scale changes.
340
405
  * Called at the start of refresh() before rendering, so the old content
@@ -347,12 +412,10 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
347
412
  currentResolutionScale = newScale;
348
413
  renderWidth = Math.floor(width * currentResolutionScale);
349
414
  renderHeight = Math.floor(height * currentResolutionScale);
350
- previewContainer.style.width = `${renderWidth}px`;
351
- previewContainer.style.height = `${renderHeight}px`;
352
- if (currentResolutionScale < 1) {
353
- container.style.transform = `scale(${currentResolutionScale})`;
354
- container.style.transformOrigin = "top left";
355
- } else container.style.transform = "";
415
+ if (captureCanvas) {
416
+ captureCanvas.width = renderWidth;
417
+ captureCanvas.height = renderHeight;
418
+ }
356
419
  };
357
420
  /**
358
421
  * Dynamically change resolution scale without rebuilding clone structure.
@@ -366,52 +429,104 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
366
429
  lastTimeMs = -1;
367
430
  };
368
431
  const getResolutionScale = () => pendingResolutionScale ?? currentResolutionScale;
432
+ let frameCount = 0;
433
+ let totalFrameControllerMs = 0;
434
+ let totalCaptureMs = 0;
435
+ let totalCopyMs = 0;
436
+ let totalFrameMs = 0;
369
437
  const refresh = async () => {
370
- if (rendering || disposed) return;
438
+ if (disposed) return;
371
439
  const sourceTimeMs = timegroup.currentTimeMs ?? 0;
372
440
  const userTimeMs = timegroup.userTimeMs ?? 0;
373
441
  if (Math.abs(sourceTimeMs - userTimeMs) > TIME_EPSILON_MS) return;
374
442
  if (userTimeMs === lastTimeMs) return;
443
+ if (rendering) return;
375
444
  lastTimeMs = userTimeMs;
376
445
  rendering = true;
377
446
  applyPendingResolutionChange();
378
447
  if (!hasLoggedScale) {
379
448
  hasLoggedScale = true;
380
- const mode = getEffectiveRenderMode();
449
+ const mode = useNative ? "native" : "foreignObject";
381
450
  logger.debug(`[renderTimegroupToCanvas] Resolution scale: ${currentResolutionScale} (${width}x${height} → ${renderWidth}x${renderHeight}), canvas buffer: ${canvas.width}x${canvas.height}, CSS size: ${canvas.style.width}x${canvas.style.height}, renderMode: ${mode}`);
382
451
  }
383
452
  try {
384
- await frameController.renderFrame(userTimeMs);
385
- const { container: container$1, syncState } = buildCloneStructure(timegroup, toAbsoluteTime(timegroup, userTimeMs));
386
- if (currentResolutionScale < 1) {
387
- container$1.style.transform = `scale(${currentResolutionScale})`;
388
- container$1.style.transformOrigin = "top left";
389
- }
390
- while (previewContainer.firstChild !== styleEl && previewContainer.firstChild) previewContainer.removeChild(previewContainer.firstChild);
391
- previewContainer.appendChild(container$1);
392
- overrideRootCloneStyles(syncState);
393
- const removedNodes = removeHiddenNodesForSerialization(syncState);
394
- const t0 = performance.now();
395
- const image = await renderToImage(previewContainer, renderWidth, renderHeight, {
396
- canvasScale: currentResolutionScale,
397
- renderContext,
398
- sourceMap: syncState.canvasSourceMap
453
+ const tFrame = performance.now();
454
+ const tFC0 = performance.now();
455
+ await frameController.renderFrame(userTimeMs, {
456
+ waitForLitUpdate: false,
457
+ onAnimationsUpdate: (root) => {
458
+ updateAnimations(root);
459
+ }
399
460
  });
400
- const renderTime = performance.now() - t0;
401
- restoreHiddenNodes(removedNodes);
402
- const targetWidth = Math.floor(renderWidth * scale * dpr);
403
- const targetHeight = Math.floor(renderHeight * scale * dpr);
404
- if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
405
- canvas.width = targetWidth;
406
- canvas.height = targetHeight;
407
- } else ctx.clearRect(0, 0, canvas.width, canvas.height);
408
- ctx.save();
409
- ctx.scale(dpr * scale, dpr * scale);
410
- ctx.drawImage(image, 0, 0);
411
- ctx.restore();
412
- defaultProfiler.incrementRenderCount();
413
- if (defaultProfiler.shouldLogByFrameCount(60)) logger.debug(`[renderTimegroupToCanvas] Frame render: ${renderTime.toFixed(1)}ms (resolutionScale=${currentResolutionScale}, image=${image.width}x${image.height})`);
414
- updateDebugLabel(debugLabel, renderWidth, renderHeight, currentResolutionScale);
461
+ const fcMs = performance.now() - tFC0;
462
+ const tCapture0 = performance.now();
463
+ if (useNative && captureCanvas && captureCtx) {
464
+ if (captureCanvas.width !== width || captureCanvas.height !== height) {
465
+ captureCtx.save();
466
+ captureCtx.scale(captureCanvas.width / width, captureCanvas.height / height);
467
+ captureCtx.drawElementImage(timegroup, 0, 0);
468
+ captureCtx.restore();
469
+ } else captureCtx.drawElementImage(timegroup, 0, 0);
470
+ const captureMs = performance.now() - tCapture0;
471
+ const tCopy0 = performance.now();
472
+ const targetWidth = Math.floor(renderWidth * scale * dpr);
473
+ const targetHeight = Math.floor(renderHeight * scale * dpr);
474
+ if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
475
+ canvas.width = targetWidth;
476
+ canvas.height = targetHeight;
477
+ } else ctx.clearRect(0, 0, canvas.width, canvas.height);
478
+ ctx.drawImage(captureCanvas, 0, 0, canvas.width, canvas.height);
479
+ const copyMs = performance.now() - tCopy0;
480
+ const frameMs = performance.now() - tFrame;
481
+ frameCount++;
482
+ totalFrameControllerMs += fcMs;
483
+ totalCaptureMs += captureMs;
484
+ totalCopyMs += copyMs;
485
+ totalFrameMs += frameMs;
486
+ defaultProfiler.incrementRenderCount();
487
+ if (defaultProfiler.shouldLogByFrameCount(60)) {
488
+ frameCount = 0;
489
+ totalFrameControllerMs = 0;
490
+ totalCaptureMs = 0;
491
+ totalCopyMs = 0;
492
+ totalFrameMs = 0;
493
+ }
494
+ } else {
495
+ const absoluteTimeMs = toAbsoluteTime(timegroup, userTimeMs);
496
+ const dataUri = await captureTimelineToDataUri(timegroup, width, height, {
497
+ renderContext,
498
+ canvasScale: currentResolutionScale,
499
+ timeMs: absoluteTimeMs
500
+ });
501
+ const captureMs = performance.now() - tCapture0;
502
+ const tCopy0 = performance.now();
503
+ const image = await loadImageFromDataUri(dataUri);
504
+ const copyMs = performance.now() - tCopy0;
505
+ const targetWidth = Math.floor(renderWidth * scale * dpr);
506
+ const targetHeight = Math.floor(renderHeight * scale * dpr);
507
+ if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
508
+ canvas.width = targetWidth;
509
+ canvas.height = targetHeight;
510
+ } else ctx.clearRect(0, 0, canvas.width, canvas.height);
511
+ ctx.save();
512
+ ctx.scale(dpr * scale, dpr * scale);
513
+ ctx.drawImage(image, 0, 0, renderWidth, renderHeight);
514
+ ctx.restore();
515
+ const frameMs = performance.now() - tFrame;
516
+ frameCount++;
517
+ totalFrameControllerMs += fcMs;
518
+ totalCaptureMs += captureMs;
519
+ totalCopyMs += copyMs;
520
+ totalFrameMs += frameMs;
521
+ defaultProfiler.incrementRenderCount();
522
+ if (defaultProfiler.shouldLogByFrameCount(60)) {
523
+ frameCount = 0;
524
+ totalFrameControllerMs = 0;
525
+ totalCaptureMs = 0;
526
+ totalCopyMs = 0;
527
+ totalFrameMs = 0;
528
+ }
529
+ }
415
530
  } catch (e) {
416
531
  logger.error("Canvas preview render failed:", e);
417
532
  } finally {
@@ -426,6 +541,13 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
426
541
  disposed = true;
427
542
  frameController.abort();
428
543
  renderContext.dispose();
544
+ if (useNative && originalParent) {
545
+ timegroup.style.clipPath = savedClipPath;
546
+ timegroup.style.pointerEvents = savedPointerEvents;
547
+ if (originalNextSibling) originalParent.insertBefore(timegroup, originalNextSibling);
548
+ else originalParent.appendChild(timegroup);
549
+ captureCanvas?.remove();
550
+ }
429
551
  };
430
552
  refresh();
431
553
  return {
@@ -439,5 +561,5 @@ function renderTimegroupToCanvas(timegroup, scaleOrOptions = DEFAULT_PREVIEW_SCA
439
561
  }
440
562
 
441
563
  //#endregion
442
- export { captureFromClone, captureTimegroupAtTime, renderTimegroupToCanvas, resetRenderState };
564
+ export { generateThumbnailsFromClone, renderTimegroupToCanvas, resetRenderState, waitForVideoContent };
443
565
  //# sourceMappingURL=renderTimegroupToCanvas.js.map