@editframe/elements 0.30.2-beta.0 → 0.31.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +5 -0
  2. package/dist/EF_FRAMEGEN.js +20 -4
  3. package/dist/EF_FRAMEGEN.js.map +1 -1
  4. package/dist/EF_INTERACTIVE.js.map +1 -1
  5. package/dist/_virtual/rolldown_runtime.js +27 -0
  6. package/dist/canvas/EFCanvas.d.ts +311 -0
  7. package/dist/canvas/EFCanvas.js +1089 -0
  8. package/dist/canvas/EFCanvas.js.map +1 -0
  9. package/dist/canvas/EFCanvasItem.d.ts +55 -0
  10. package/dist/canvas/EFCanvasItem.js +72 -0
  11. package/dist/canvas/EFCanvasItem.js.map +1 -0
  12. package/dist/canvas/api/CanvasAPI.d.ts +115 -0
  13. package/dist/canvas/api/CanvasAPI.js +182 -0
  14. package/dist/canvas/api/CanvasAPI.js.map +1 -0
  15. package/dist/canvas/api/types.d.ts +42 -0
  16. package/dist/canvas/coordinateTransform.js +90 -0
  17. package/dist/canvas/coordinateTransform.js.map +1 -0
  18. package/dist/canvas/getElementBounds.js +40 -0
  19. package/dist/canvas/getElementBounds.js.map +1 -0
  20. package/dist/canvas/overlays/SelectionOverlay.js +265 -0
  21. package/dist/canvas/overlays/SelectionOverlay.js.map +1 -0
  22. package/dist/canvas/overlays/overlayState.js +153 -0
  23. package/dist/canvas/overlays/overlayState.js.map +1 -0
  24. package/dist/canvas/selection/SelectionController.js +105 -0
  25. package/dist/canvas/selection/SelectionController.js.map +1 -0
  26. package/dist/canvas/selection/SelectionModel.d.ts +98 -0
  27. package/dist/canvas/selection/SelectionModel.js +229 -0
  28. package/dist/canvas/selection/SelectionModel.js.map +1 -0
  29. package/dist/canvas/selection/selectionContext.d.ts +31 -0
  30. package/dist/canvas/selection/selectionContext.js +12 -0
  31. package/dist/canvas/selection/selectionContext.js.map +1 -0
  32. package/dist/elements/ContainerInfo.d.ts +29 -0
  33. package/dist/elements/ContainerInfo.js +30 -0
  34. package/dist/elements/ContainerInfo.js.map +1 -0
  35. package/dist/elements/EFAudio.d.ts +13 -3
  36. package/dist/elements/EFAudio.js +64 -10
  37. package/dist/elements/EFAudio.js.map +1 -1
  38. package/dist/elements/EFCaptions.d.ts +18 -16
  39. package/dist/elements/EFCaptions.js +110 -19
  40. package/dist/elements/EFCaptions.js.map +1 -1
  41. package/dist/elements/EFImage.d.ts +16 -6
  42. package/dist/elements/EFImage.js +79 -9
  43. package/dist/elements/EFImage.js.map +1 -1
  44. package/dist/elements/EFMedia/AssetIdMediaEngine.js +51 -4
  45. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
  46. package/dist/elements/EFMedia/AssetMediaEngine.js +125 -52
  47. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  48. package/dist/elements/EFMedia/BaseMediaEngine.js +24 -6
  49. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  50. package/dist/elements/EFMedia/JitMediaEngine.js +12 -8
  51. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  52. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +46 -7
  53. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
  54. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +98 -73
  55. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -1
  56. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +28 -5
  57. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -1
  58. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +18 -6
  59. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -1
  60. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +8 -2
  61. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -1
  62. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +31 -6
  63. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -1
  64. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +28 -5
  65. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -1
  66. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +97 -72
  67. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -1
  68. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -1
  69. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  70. package/dist/elements/EFMedia/shared/BufferUtils.js +1 -1
  71. package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
  72. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +25 -14
  73. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
  74. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +47 -16
  75. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -1
  76. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +37 -19
  77. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
  78. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +65 -21
  79. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  80. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +8 -3
  81. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -1
  82. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +32 -9
  83. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -1
  84. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +33 -10
  85. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -1
  86. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +23 -8
  87. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -1
  88. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +34 -10
  89. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -1
  90. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +31 -8
  91. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -1
  92. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +31 -114
  93. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -1
  94. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +44 -8
  95. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
  96. package/dist/elements/EFMedia.d.ts +18 -7
  97. package/dist/elements/EFMedia.js +23 -3
  98. package/dist/elements/EFMedia.js.map +1 -1
  99. package/dist/elements/EFPanZoom.d.ts +96 -0
  100. package/dist/elements/EFPanZoom.js +290 -0
  101. package/dist/elements/EFPanZoom.js.map +1 -0
  102. package/dist/elements/EFSourceMixin.js +7 -6
  103. package/dist/elements/EFSourceMixin.js.map +1 -1
  104. package/dist/elements/EFSurface.d.ts +6 -6
  105. package/dist/elements/EFSurface.js +7 -2
  106. package/dist/elements/EFSurface.js.map +1 -1
  107. package/dist/elements/EFTemporal.d.ts +2 -1
  108. package/dist/elements/EFTemporal.js +192 -71
  109. package/dist/elements/EFTemporal.js.map +1 -1
  110. package/dist/elements/EFText.d.ts +5 -4
  111. package/dist/elements/EFText.js +102 -13
  112. package/dist/elements/EFText.js.map +1 -1
  113. package/dist/elements/EFTextSegment.d.ts +32 -6
  114. package/dist/elements/EFTextSegment.js +53 -15
  115. package/dist/elements/EFTextSegment.js.map +1 -1
  116. package/dist/elements/EFThumbnailStrip.d.ts +129 -56
  117. package/dist/elements/EFThumbnailStrip.js +605 -359
  118. package/dist/elements/EFThumbnailStrip.js.map +1 -1
  119. package/dist/elements/EFTimegroup.d.ts +233 -25
  120. package/dist/elements/EFTimegroup.js +865 -144
  121. package/dist/elements/EFTimegroup.js.map +1 -1
  122. package/dist/elements/EFVideo.d.ts +42 -5
  123. package/dist/elements/EFVideo.js +165 -11
  124. package/dist/elements/EFVideo.js.map +1 -1
  125. package/dist/elements/EFWaveform.d.ts +6 -6
  126. package/dist/elements/EFWaveform.js +2 -1
  127. package/dist/elements/EFWaveform.js.map +1 -1
  128. package/dist/elements/ElementPositionInfo.d.ts +35 -0
  129. package/dist/elements/ElementPositionInfo.js +49 -0
  130. package/dist/elements/ElementPositionInfo.js.map +1 -0
  131. package/dist/elements/FetchMixin.js +16 -1
  132. package/dist/elements/FetchMixin.js.map +1 -1
  133. package/dist/elements/SessionThumbnailCache.js +154 -0
  134. package/dist/elements/SessionThumbnailCache.js.map +1 -0
  135. package/dist/elements/TargetController.js +3 -1
  136. package/dist/elements/TargetController.js.map +1 -1
  137. package/dist/elements/TimegroupController.js +9 -3
  138. package/dist/elements/TimegroupController.js.map +1 -1
  139. package/dist/elements/findRootTemporal.js +30 -0
  140. package/dist/elements/findRootTemporal.js.map +1 -0
  141. package/dist/elements/renderTemporalAudio.js +18 -5
  142. package/dist/elements/renderTemporalAudio.js.map +1 -1
  143. package/dist/elements/updateAnimations.js +171 -28
  144. package/dist/elements/updateAnimations.js.map +1 -1
  145. package/dist/getRenderInfo.d.ts +2 -2
  146. package/dist/gui/ContextMixin.js +4 -2
  147. package/dist/gui/ContextMixin.js.map +1 -1
  148. package/dist/gui/Controllable.js +74 -1
  149. package/dist/gui/Controllable.js.map +1 -1
  150. package/dist/gui/EFActiveRootTemporal.d.ts +50 -0
  151. package/dist/gui/EFActiveRootTemporal.js +94 -0
  152. package/dist/gui/EFActiveRootTemporal.js.map +1 -0
  153. package/dist/gui/EFConfiguration.d.ts +7 -1
  154. package/dist/gui/EFConfiguration.js.map +1 -1
  155. package/dist/gui/EFControls.d.ts +2 -2
  156. package/dist/gui/EFControls.js +109 -13
  157. package/dist/gui/EFControls.js.map +1 -1
  158. package/dist/gui/EFDial.d.ts +4 -4
  159. package/dist/gui/EFFilmstrip.d.ts +11 -214
  160. package/dist/gui/EFFilmstrip.js +53 -1152
  161. package/dist/gui/EFFilmstrip.js.map +1 -1
  162. package/dist/gui/EFFitScale.d.ts +3 -3
  163. package/dist/gui/EFFitScale.js +39 -12
  164. package/dist/gui/EFFitScale.js.map +1 -1
  165. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  166. package/dist/gui/EFOverlayItem.d.ts +48 -0
  167. package/dist/gui/EFOverlayItem.js +97 -0
  168. package/dist/gui/EFOverlayItem.js.map +1 -0
  169. package/dist/gui/EFOverlayLayer.d.ts +70 -0
  170. package/dist/gui/EFOverlayLayer.js +104 -0
  171. package/dist/gui/EFOverlayLayer.js.map +1 -0
  172. package/dist/gui/EFPause.d.ts +4 -4
  173. package/dist/gui/EFPlay.d.ts +4 -4
  174. package/dist/gui/EFResizableBox.d.ts +12 -16
  175. package/dist/gui/EFResizableBox.js +109 -451
  176. package/dist/gui/EFResizableBox.js.map +1 -1
  177. package/dist/gui/EFScrubber.d.ts +30 -5
  178. package/dist/gui/EFScrubber.js +224 -31
  179. package/dist/gui/EFScrubber.js.map +1 -1
  180. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  181. package/dist/gui/EFTimeDisplay.js +4 -1
  182. package/dist/gui/EFTimeDisplay.js.map +1 -1
  183. package/dist/gui/EFTimelineRuler.d.ts +71 -0
  184. package/dist/gui/EFTimelineRuler.js +320 -0
  185. package/dist/gui/EFTimelineRuler.js.map +1 -0
  186. package/dist/gui/EFToggleLoop.d.ts +4 -4
  187. package/dist/gui/EFTogglePlay.d.ts +4 -4
  188. package/dist/gui/EFTransformHandles.d.ts +91 -0
  189. package/dist/gui/EFTransformHandles.js +393 -0
  190. package/dist/gui/EFTransformHandles.js.map +1 -0
  191. package/dist/gui/EFWorkbench.d.ts +178 -0
  192. package/dist/gui/EFWorkbench.js +2067 -22
  193. package/dist/gui/EFWorkbench.js.map +1 -1
  194. package/dist/gui/FitScaleHelpers.d.ts +31 -0
  195. package/dist/gui/FitScaleHelpers.js +41 -0
  196. package/dist/gui/FitScaleHelpers.js.map +1 -0
  197. package/dist/gui/PlaybackController.d.ts +2 -1
  198. package/dist/gui/PlaybackController.js +46 -15
  199. package/dist/gui/PlaybackController.js.map +1 -1
  200. package/dist/gui/TWMixin.js +1 -1
  201. package/dist/gui/TWMixin.js.map +1 -1
  202. package/dist/gui/hierarchy/EFHierarchy.d.ts +65 -0
  203. package/dist/gui/hierarchy/EFHierarchy.js +338 -0
  204. package/dist/gui/hierarchy/EFHierarchy.js.map +1 -0
  205. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +118 -0
  206. package/dist/gui/hierarchy/EFHierarchyItem.js +551 -0
  207. package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -0
  208. package/dist/gui/hierarchy/hierarchyContext.d.ts +38 -0
  209. package/dist/gui/hierarchy/hierarchyContext.js +8 -0
  210. package/dist/gui/hierarchy/hierarchyContext.js.map +1 -0
  211. package/dist/gui/icons.js +34 -0
  212. package/dist/gui/icons.js.map +1 -0
  213. package/dist/gui/panZoomTransformContext.js +12 -0
  214. package/dist/gui/panZoomTransformContext.js.map +1 -0
  215. package/dist/gui/previewSettingsContext.js +12 -0
  216. package/dist/gui/previewSettingsContext.js.map +1 -0
  217. package/dist/gui/timeline/EFTimeline.d.ts +270 -0
  218. package/dist/gui/timeline/EFTimeline.js +1369 -0
  219. package/dist/gui/timeline/EFTimeline.js.map +1 -0
  220. package/dist/gui/timeline/EFTimelineRow.js +374 -0
  221. package/dist/gui/timeline/EFTimelineRow.js.map +1 -0
  222. package/dist/gui/timeline/TrimHandles.d.ts +36 -0
  223. package/dist/gui/timeline/TrimHandles.js +204 -0
  224. package/dist/gui/timeline/TrimHandles.js.map +1 -0
  225. package/dist/gui/timeline/flattenHierarchy.js +31 -0
  226. package/dist/gui/timeline/flattenHierarchy.js.map +1 -0
  227. package/dist/gui/timeline/timelineStateContext.d.ts +26 -0
  228. package/dist/gui/timeline/timelineStateContext.js +42 -0
  229. package/dist/gui/timeline/timelineStateContext.js.map +1 -0
  230. package/dist/gui/timeline/tracks/AudioTrack.js +264 -0
  231. package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -0
  232. package/dist/gui/timeline/tracks/CaptionsTrack.js +595 -0
  233. package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -0
  234. package/dist/gui/timeline/tracks/HTMLTrack.js +19 -0
  235. package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -0
  236. package/dist/gui/timeline/tracks/ImageTrack.js +53 -0
  237. package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -0
  238. package/dist/gui/timeline/tracks/TextTrack.js +250 -0
  239. package/dist/gui/timeline/tracks/TextTrack.js.map +1 -0
  240. package/dist/gui/timeline/tracks/TimegroupTrack.js +143 -0
  241. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -0
  242. package/dist/gui/timeline/tracks/TrackItem.js +269 -0
  243. package/dist/gui/timeline/tracks/TrackItem.js.map +1 -0
  244. package/dist/gui/timeline/tracks/VideoTrack.js +265 -0
  245. package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -0
  246. package/dist/gui/timeline/tracks/WaveformTrack.js +19 -0
  247. package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -0
  248. package/dist/gui/timeline/tracks/ensureTrackItemInit.js +1 -0
  249. package/dist/gui/timeline/tracks/preloadTracks.js +9 -0
  250. package/dist/gui/timeline/tracks/renderTrackChildren.js +119 -0
  251. package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -0
  252. package/dist/gui/timeline/tracks/waveformUtils.js +80 -0
  253. package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -0
  254. package/dist/gui/transformCalculations.js +217 -0
  255. package/dist/gui/transformCalculations.js.map +1 -0
  256. package/dist/gui/transformUtils.d.ts +37 -0
  257. package/dist/gui/transformUtils.js +77 -0
  258. package/dist/gui/transformUtils.js.map +1 -0
  259. package/dist/gui/tree/EFTree.d.ts +59 -0
  260. package/dist/gui/tree/EFTree.js +174 -0
  261. package/dist/gui/tree/EFTree.js.map +1 -0
  262. package/dist/gui/tree/EFTreeItem.d.ts +38 -0
  263. package/dist/gui/tree/EFTreeItem.js +146 -0
  264. package/dist/gui/tree/EFTreeItem.js.map +1 -0
  265. package/dist/gui/tree/treeContext.d.ts +60 -0
  266. package/dist/gui/tree/treeContext.js +23 -0
  267. package/dist/gui/tree/treeContext.js.map +1 -0
  268. package/dist/index.d.ts +32 -8
  269. package/dist/index.js +30 -6
  270. package/dist/index.js.map +1 -1
  271. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +688 -0
  272. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
  273. package/dist/node_modules/react/cjs/react.development.js +1521 -0
  274. package/dist/node_modules/react/cjs/react.development.js.map +1 -0
  275. package/dist/node_modules/react/index.js +13 -0
  276. package/dist/node_modules/react/index.js.map +1 -0
  277. package/dist/node_modules/react/jsx-runtime.js +13 -0
  278. package/dist/node_modules/react/jsx-runtime.js.map +1 -0
  279. package/dist/preview/AdaptiveResolutionTracker.js +228 -0
  280. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -0
  281. package/dist/preview/RenderProfiler.js +135 -0
  282. package/dist/preview/RenderProfiler.js.map +1 -0
  283. package/dist/preview/previewSettings.js +131 -0
  284. package/dist/preview/previewSettings.js.map +1 -0
  285. package/dist/preview/previewTypes.js +64 -0
  286. package/dist/preview/previewTypes.js.map +1 -0
  287. package/dist/preview/renderTimegroupPreview.js +656 -0
  288. package/dist/preview/renderTimegroupPreview.js.map +1 -0
  289. package/dist/preview/renderTimegroupToCanvas.d.ts +37 -0
  290. package/dist/preview/renderTimegroupToCanvas.js +833 -0
  291. package/dist/preview/renderTimegroupToCanvas.js.map +1 -0
  292. package/dist/preview/renderTimegroupToVideo.d.ts +39 -0
  293. package/dist/preview/renderTimegroupToVideo.js +274 -0
  294. package/dist/preview/renderTimegroupToVideo.js.map +1 -0
  295. package/dist/preview/renderers.js +16 -0
  296. package/dist/preview/renderers.js.map +1 -0
  297. package/dist/preview/statsTrackingStrategy.js +201 -0
  298. package/dist/preview/statsTrackingStrategy.js.map +1 -0
  299. package/dist/preview/thumbnailCacheSettings.js +52 -0
  300. package/dist/preview/thumbnailCacheSettings.js.map +1 -0
  301. package/dist/preview/workers/WorkerPool.js +178 -0
  302. package/dist/preview/workers/WorkerPool.js.map +1 -0
  303. package/dist/preview/workers/encoderWorkerInline.js +103 -0
  304. package/dist/preview/workers/encoderWorkerInline.js.map +1 -0
  305. package/dist/sandbox/PlaybackControls.js +10 -0
  306. package/dist/sandbox/PlaybackControls.js.map +1 -0
  307. package/dist/sandbox/ScenarioRunner.js +1 -0
  308. package/dist/sandbox/index.js +2 -0
  309. package/dist/style.css +71 -67
  310. package/dist/transcoding/types/index.d.ts +2 -1
  311. package/dist/transcoding/utils/UrlGenerator.d.ts +6 -1
  312. package/dist/transcoding/utils/UrlGenerator.js +12 -3
  313. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  314. package/dist/utils/LRUCache.js +1 -375
  315. package/dist/utils/LRUCache.js.map +1 -1
  316. package/dist/utils/frameTime.js +14 -0
  317. package/dist/utils/frameTime.js.map +1 -0
  318. package/package.json +3 -3
  319. package/test/profilingPlugin.ts +223 -0
  320. package/test/recordReplayProxyPlugin.js +22 -27
  321. package/test/thumbnail-performance-test.html +116 -0
  322. package/test/visualRegressionUtils.ts +286 -0
  323. package/types.json +1 -1
  324. package/dist/elements/TimegroupController.d.ts +0 -18
  325. package/dist/msToTimeCode.js +0 -17
  326. package/dist/msToTimeCode.js.map +0 -1
@@ -1,165 +1,97 @@
1
1
  import { __decorate } from "../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js";
2
- import { OrderedLRUCache } from "../utils/LRUCache.js";
3
2
  import { TargetController } from "./TargetController.js";
4
- import { Task } from "@lit/task";
3
+ import { findRootTemporal } from "./findRootTemporal.js";
4
+ import { timelineStateContext } from "../gui/timeline/timelineStateContext.js";
5
+ import { getCacheKey, sessionThumbnailCache } from "./SessionThumbnailCache.js";
6
+ import { consume } from "@lit/context";
5
7
  import { LitElement, css, html } from "lit";
6
8
  import { customElement, property, state } from "lit/decorators.js";
7
9
  import { createRef, ref } from "lit/directives/ref.js";
8
10
 
9
11
  //#region src/elements/EFThumbnailStrip.ts
10
- /**
11
- * Global thumbnail image cache for smooth resize performance
12
- * Shared across all thumbnail strip instances
13
- * Uses OrderedLRUCache for efficient timestamp-based searching
14
- */
15
- const thumbnailImageCache = new OrderedLRUCache(200, (a, b) => {
16
- const partsA = a.split(":");
17
- const partsB = b.split(":");
18
- return Number.parseFloat(partsA[partsA.length - 1] || "0") - Number.parseFloat(partsB[partsB.length - 1] || "0");
19
- });
20
- globalThis.debugThumbnailCache = thumbnailImageCache;
21
- /**
22
- * Quantize timestamp to 30fps frame boundaries for consistent caching
23
- * This eliminates cache misses from floating point precision differences
24
- */
25
- function quantizeTimestamp(timeMs) {
26
- const frameIntervalMs = 1e3 / 30;
27
- return Math.round(timeMs / frameIntervalMs) * frameIntervalMs;
12
+ /** Type guard to check if element is EFVideo */
13
+ function isEFVideo(element) {
14
+ return element?.tagName.toLowerCase() === "ef-video";
28
15
  }
29
- /**
30
- * Generate cache key for thumbnail image data (dimension-independent, quantized)
31
- */
32
- function getThumbnailCacheKey(videoSrc, timeMs) {
33
- return `${videoSrc}:${quantizeTimestamp(timeMs)}`;
16
+ /** Type guard to check if element is EFTimegroup */
17
+ function isEFTimegroup(element) {
18
+ return element?.tagName.toLowerCase() === "ef-timegroup";
34
19
  }
35
- const THUMBNAIL_GAP = 1;
36
- const STRIP_BORDER_PADDING = 4;
37
20
  /**
38
- * Calculate optimal thumbnail count and timestamps for the strip
39
- * Groups thumbnails by scrub segment ID for efficient caching
21
+ * Get identifiers for cache key generation.
22
+ * Returns rootId (for cache isolation), elementId (for element-specific caching), and epoch (for content versioning).
40
23
  */
41
- function calculateThumbnailLayout(stripWidth, thumbnailWidth, startTimeMs, endTimeMs, scrubSegmentDurationMs) {
42
- if (stripWidth <= 0 || thumbnailWidth <= 0 || endTimeMs <= startTimeMs) return {
43
- count: 0,
44
- segments: []
45
- };
46
- const thumbnailPitch = thumbnailWidth + THUMBNAIL_GAP;
47
- const baseFitCount = Math.floor(stripWidth / thumbnailPitch);
48
- const count = Math.max(1, baseFitCount + 1);
49
- const timestamps = [];
50
- const timeRange = endTimeMs - startTimeMs;
51
- for (let i = 0; i < count; i++) {
52
- const timeMs = count === 1 ? (startTimeMs + endTimeMs) / 2 : startTimeMs + i * timeRange / (count - 1);
53
- timestamps.push(timeMs);
54
- }
55
- const segmentMap = /* @__PURE__ */ new Map();
56
- for (const timeMs of timestamps) {
57
- const segmentId = scrubSegmentDurationMs ? Math.floor(timeMs / scrubSegmentDurationMs) : 0;
58
- if (!segmentMap.has(segmentId)) segmentMap.set(segmentId, []);
59
- segmentMap.get(segmentId).push({ timeMs });
60
- }
24
+ function getCacheIdentifiers(element) {
25
+ const rootTemporal = findRootTemporal(element);
26
+ const rootTimegroup = rootTemporal && isEFTimegroup(rootTemporal) ? rootTemporal : null;
27
+ const rootId = rootTimegroup?.id || "default";
28
+ const epoch = rootTimegroup?.contentEpoch ?? 0;
61
29
  return {
62
- count,
63
- segments: Array.from(segmentMap.entries()).sort(([a], [b]) => a - b).map(([segmentId, thumbnails]) => ({
64
- segmentId,
65
- thumbnails
66
- }))
30
+ rootId,
31
+ elementId: isEFVideo(element) ? element.src || element.id || "video" : element.id || "timegroup",
32
+ epoch
67
33
  };
68
34
  }
35
+ /** Padding in pixels for virtual rendering (render extra thumbnails beyond viewport) */
36
+ const VIRTUAL_RENDER_PADDING_PX = 200;
37
+ /** Default gap between thumbnails */
38
+ const DEFAULT_GAP = 4;
39
+ /** Default aspect ratio if unknown */
40
+ const DEFAULT_ASPECT_RATIO = 16 / 9;
41
+ /** Max canvas width for thumbnail captures */
42
+ const MAX_CAPTURE_WIDTH = 480;
43
+ /** Thumbnails to capture per batch */
44
+ const BATCH_SIZE = 10;
69
45
  let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
70
46
  constructor(..._args) {
71
47
  super(..._args);
72
48
  this.canvasRef = createRef();
73
- this._targetController = new TargetController(this);
74
- this._targetElement = null;
75
49
  this.target = "";
76
- this.thumbnailWidth = 80;
50
+ this.thumbnailWidth = 0;
51
+ this.gap = DEFAULT_GAP;
77
52
  this.useIntrinsicDuration = false;
78
- this._stripWidth = 0;
79
- this._stripHeight = 48;
80
- this._thumbnailUpdateInProgress = false;
81
- this._pendingThumbnailUpdate = false;
82
- this.thumbnailLayoutTask = new Task(this, {
83
- autoRun: false,
84
- task: async ([stripWidth, thumbnailWidth, targetElement, startTimeMs, endTimeMs, useIntrinsicDuration, mediaEngine]) => {
85
- if (stripWidth <= 0 || !targetElement) return {
86
- count: 0,
87
- segments: []
88
- };
89
- if (!mediaEngine) {
90
- if (targetElement.mediaEngineTask) {
91
- await targetElement.mediaEngineTask.taskComplete;
92
- const readyMediaEngine = targetElement.mediaEngineTask.value;
93
- if (!readyMediaEngine) return {
94
- count: 0,
95
- segments: []
96
- };
97
- return this.calculateLayoutWithMediaEngine(stripWidth, thumbnailWidth, targetElement, startTimeMs, endTimeMs, useIntrinsicDuration, readyMediaEngine);
98
- }
99
- return {
100
- count: 0,
101
- segments: []
102
- };
103
- }
104
- return this.calculateLayoutWithMediaEngine(stripWidth, thumbnailWidth, targetElement, startTimeMs, endTimeMs, useIntrinsicDuration, mediaEngine);
105
- },
106
- args: () => [
107
- this.stripWidth,
108
- this.thumbnailWidth,
109
- this.targetElement,
110
- this.startTimeMs,
111
- this.endTimeMs,
112
- this.useIntrinsicDuration,
113
- this.targetElement?.mediaEngineTask?.value
114
- ]
115
- });
116
- this.thumbnailRenderTask = new Task(this, {
117
- autoRun: false,
118
- task: async ([layout, targetElement, thumbnailWidth]) => {
119
- if (!layout || !targetElement) return [];
120
- return this.renderThumbnails(layout, targetElement, thumbnailWidth);
121
- },
122
- args: () => [
123
- this.thumbnailLayoutTask.value || null,
124
- this.targetElement,
125
- this.thumbnailWidth
126
- ]
127
- });
53
+ this.pixelsPerMs = .1;
54
+ this._targetController = new TargetController(this);
55
+ this._targetElement = null;
56
+ this._width = 0;
57
+ this._height = 0;
58
+ this._scrollContainer = null;
59
+ this._currentScrollLeft = 0;
60
+ this._trackLeftOffset = 0;
61
+ this._thumbnailSlots = [];
62
+ this._captureInProgress = false;
63
+ this._renderRequested = false;
64
+ this._hasLoadedThumbnails = false;
65
+ this._lastLoadedEpoch = null;
66
+ this._lastLayoutParams = null;
67
+ this._onScroll = () => {
68
+ if (!this._scrollContainer) return;
69
+ this._currentScrollLeft = this._scrollContainer.scrollLeft;
70
+ this._drawCanvas();
71
+ if (!this._scrollFrame) this._scrollFrame = requestAnimationFrame(() => {
72
+ this._scrollFrame = void 0;
73
+ this._loadVisibleThumbnails();
74
+ });
75
+ };
128
76
  }
129
77
  static {
130
78
  this.styles = [css`
131
79
  :host {
132
80
  display: block;
133
81
  position: relative;
134
- width: 100%;
135
- height: 48px; /* Default filmstrip height */
136
- background: #2a2a2a;
137
- border: 2px solid #333;
138
- border-radius: 6px;
82
+ background: #1a1a2e;
139
83
  overflow: hidden;
140
- box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
84
+ width: 100%;
85
+ height: 100%;
141
86
  }
142
87
  canvas {
143
88
  display: block;
89
+ /* Absolute positioning - we manually position at visible region */
144
90
  position: absolute;
145
91
  top: 0;
146
- left: 0;
147
- image-rendering: pixelated; /* Keep thumbnails crisp */
148
- /* Width and height set programmatically to prevent CSS scaling */
149
- }
150
- .loading-overlay {
151
- position: absolute;
152
- top: 0;
153
- left: 0;
154
- right: 0;
155
- bottom: 0;
156
- background: rgba(42, 42, 42, 0.9);
157
- display: flex;
158
- align-items: center;
159
- justify-content: center;
160
- font-size: 11px;
161
- color: #ccc;
162
- font-weight: 500;
92
+ /* Left and width set programmatically based on visible portion */
93
+ height: 100%;
94
+ image-rendering: auto;
163
95
  }
164
96
  `];
165
97
  }
@@ -169,20 +101,166 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
169
101
  set targetElement(value) {
170
102
  const oldValue = this._targetElement;
171
103
  this._targetElement = value;
172
- this._videoPropertyObserver?.disconnect();
173
- if (value && value !== oldValue) {
174
- this._videoPropertyObserver = new MutationObserver((mutations) => {
175
- let shouldUpdate = false;
176
- for (const mutation of mutations) if (mutation.type === "attributes" && mutation.attributeName) {
177
- const attr = mutation.attributeName;
178
- if (attr === "trimstart" || attr === "trimend" || attr === "sourcein" || attr === "sourceout" || attr === "src") {
179
- shouldUpdate = true;
180
- break;
181
- }
104
+ this._mutationObserver?.disconnect();
105
+ if (value !== oldValue) {
106
+ this._hasLoadedThumbnails = false;
107
+ this._lastLoadedEpoch = null;
108
+ this._lastLayoutParams = null;
109
+ }
110
+ if (value && value !== oldValue) this._setupTargetObserver(value);
111
+ this.requestUpdate("targetElement", oldValue);
112
+ }
113
+ connectedCallback() {
114
+ super.connectedCallback();
115
+ this._resizeObserver = new ResizeObserver((entries) => {
116
+ for (const entry of entries) {
117
+ const box = entry.borderBoxSize?.[0];
118
+ this._width = box?.inlineSize ?? entry.contentRect.width;
119
+ this._height = box?.blockSize ?? entry.contentRect.height;
120
+ this._calculateTrackOffset();
121
+ this._scheduleRender();
122
+ }
123
+ });
124
+ this._resizeObserver.observe(this);
125
+ this.updateComplete.then(() => {
126
+ this._findScrollContainer();
127
+ this._scheduleRender();
128
+ });
129
+ }
130
+ disconnectedCallback() {
131
+ super.disconnectedCallback();
132
+ this._resizeObserver?.disconnect();
133
+ this._mutationObserver?.disconnect();
134
+ this._detachScrollListener();
135
+ if (this._scrollFrame) cancelAnimationFrame(this._scrollFrame);
136
+ }
137
+ updated(changedProperties) {
138
+ super.updated(changedProperties);
139
+ if (changedProperties.has("thumbnailWidth") || changedProperties.has("gap") || changedProperties.has("startTimeMs") || changedProperties.has("endTimeMs") || changedProperties.has("useIntrinsicDuration") || changedProperties.has("pixelsPerMs") || changedProperties.has("targetElement")) this._scheduleRender();
140
+ if (changedProperties.has("_timelineState")) this._onContextScroll();
141
+ }
142
+ _findScrollContainer() {
143
+ let node = this.parentNode;
144
+ while (node) {
145
+ if (node instanceof HTMLElement) {
146
+ const style = getComputedStyle(node);
147
+ if (style.overflowX === "auto" || style.overflowX === "scroll") {
148
+ this._scrollContainer = node;
149
+ this._calculateTrackOffset();
150
+ this._attachScrollListener();
151
+ return;
182
152
  }
183
- if (shouldUpdate) this.runThumbnailUpdate();
184
- });
185
- this._videoPropertyObserver.observe(value, {
153
+ }
154
+ if (node.parentNode) node = node.parentNode;
155
+ else if (node instanceof ShadowRoot) node = node.host;
156
+ else break;
157
+ }
158
+ }
159
+ /**
160
+ * Calculate the horizontal offset from scroll container's left edge to this element's track.
161
+ * This accounts for sticky labels or other elements that precede the track area.
162
+ *
163
+ * We look for our specific timeline elements (ef-timeline-row) and measure their label width.
164
+ */
165
+ _calculateTrackOffset() {
166
+ if (!this._scrollContainer) {
167
+ this._trackLeftOffset = 0;
168
+ return;
169
+ }
170
+ const timelineRow = this._findTimelineRow();
171
+ if (timelineRow) {
172
+ const labelWidth = this._getTimelineRowLabelWidth(timelineRow);
173
+ if (labelWidth > 0) {
174
+ this._trackLeftOffset = labelWidth;
175
+ return;
176
+ }
177
+ }
178
+ this._trackLeftOffset = 0;
179
+ }
180
+ /**
181
+ * Find the ef-timeline-row ancestor by walking up through shadow DOM boundaries.
182
+ */
183
+ _findTimelineRow() {
184
+ let node = this;
185
+ while (node) {
186
+ if (node instanceof Element && node.tagName.toLowerCase() === "ef-timeline-row") return node;
187
+ const parentNode = node.parentNode;
188
+ if (parentNode instanceof ShadowRoot) node = parentNode.host;
189
+ else node = parentNode;
190
+ }
191
+ return null;
192
+ }
193
+ /**
194
+ * Get the label width from an ef-timeline-row element.
195
+ * Queries the shadow root for .row-label and returns its width.
196
+ */
197
+ _getTimelineRowLabelWidth(timelineRow) {
198
+ const shadowRoot = timelineRow.shadowRoot;
199
+ if (!shadowRoot) return 0;
200
+ const rowLabel = shadowRoot.querySelector(".row-label");
201
+ if (!rowLabel) return 0;
202
+ return rowLabel.getBoundingClientRect().width;
203
+ }
204
+ /**
205
+ * Get this strip's absolute position in the timeline (pixels from timeline origin).
206
+ * Uses the target element's startTimeMs to determine position.
207
+ */
208
+ _getStripTimelinePosition() {
209
+ const target = this._targetElement;
210
+ if (!target) return 0;
211
+ if (isEFVideo(target)) return (target.startTimeMs ?? 0) * (this._timelineState?.pixelsPerMs ?? .1);
212
+ return 0;
213
+ }
214
+ _attachScrollListener() {
215
+ if (!this._scrollContainer) return;
216
+ this._scrollContainer.addEventListener("scroll", this._onScroll, { passive: true });
217
+ this._currentScrollLeft = this._scrollContainer.scrollLeft;
218
+ }
219
+ _detachScrollListener() {
220
+ if (this._scrollContainer) {
221
+ this._scrollContainer.removeEventListener("scroll", this._onScroll);
222
+ this._scrollContainer = null;
223
+ }
224
+ }
225
+ _onContextScroll() {
226
+ if (!this._timelineState || this._scrollContainer) return;
227
+ this._currentScrollLeft = this._timelineState.viewportScrollLeft;
228
+ this._drawCanvas();
229
+ }
230
+ get _viewportWidth() {
231
+ if (this._timelineState?.viewportWidth) return this._timelineState.viewportWidth;
232
+ if (this._scrollContainer) return this._scrollContainer.clientWidth - this._trackLeftOffset;
233
+ return this._width;
234
+ }
235
+ /**
236
+ * Watch for async content loading from child media elements.
237
+ * When media finishes loading, increment the epoch to invalidate cached thumbnails.
238
+ */
239
+ _watchChildContentLoading(target) {
240
+ const mediaElements = target.querySelectorAll("ef-video, ef-image, ef-audio");
241
+ for (const el of mediaElements) {
242
+ const mediaEngine = el.mediaEngineTask;
243
+ if (mediaEngine?.taskComplete) mediaEngine.taskComplete.then(() => {
244
+ if (this._targetElement === target) {
245
+ target.incrementContentEpoch();
246
+ this._lastLayoutParams = null;
247
+ this._scheduleRender();
248
+ }
249
+ }).catch(() => {});
250
+ const fetchTask = el.fetchImage;
251
+ if (fetchTask?.taskComplete) fetchTask.taskComplete.then(() => {
252
+ if (this._targetElement === target) {
253
+ target.incrementContentEpoch();
254
+ this._lastLayoutParams = null;
255
+ this._scheduleRender();
256
+ }
257
+ }).catch(() => {});
258
+ }
259
+ }
260
+ _setupTargetObserver(target) {
261
+ if (isEFVideo(target)) {
262
+ this._mutationObserver = new MutationObserver(() => this._scheduleRender());
263
+ this._mutationObserver.observe(target, {
186
264
  attributes: true,
187
265
  attributeFilter: [
188
266
  "trimstart",
@@ -192,271 +270,434 @@ let EFThumbnailStrip = class EFThumbnailStrip$1 extends LitElement {
192
270
  "src"
193
271
  ]
194
272
  });
195
- if (value.mediaEngineTask) value.mediaEngineTask.taskComplete.then(() => {
196
- if (this._stripWidth > 0) this.thumbnailLayoutTask.run();
197
- }).catch(() => {});
273
+ target.updateComplete.then(() => {
274
+ if (this._targetElement !== target) return;
275
+ target.mediaEngineTask?.taskComplete.then(() => {
276
+ if (this._targetElement !== target) return;
277
+ this._scheduleRender();
278
+ });
279
+ });
280
+ } else if (isEFTimegroup(target)) {
281
+ this._mutationObserver = new MutationObserver((mutations) => {
282
+ if (mutations.some((mutation) => {
283
+ if (mutation.type === "childList") return mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0;
284
+ if (mutation.type === "attributes") {
285
+ const attrName = mutation.attributeName;
286
+ if (attrName === "currenttime" || attrName === "current-time" || attrName === "playing" || attrName === "loop") return false;
287
+ return attrName === "src" || attrName === "asset-id" || attrName === "style" || attrName === "transform";
288
+ }
289
+ return false;
290
+ })) {
291
+ const epochBefore = target.contentEpoch;
292
+ target.incrementContentEpoch();
293
+ if (target.contentEpoch !== epochBefore) {
294
+ this._lastLayoutParams = null;
295
+ if (mutations.some((m) => m.addedNodes.length > 0)) this._watchChildContentLoading(target);
296
+ this._scheduleRender();
297
+ }
298
+ }
299
+ });
300
+ this._mutationObserver.observe(target, {
301
+ childList: true,
302
+ subtree: true,
303
+ attributes: true,
304
+ attributeFilter: [
305
+ "src",
306
+ "asset-id",
307
+ "style",
308
+ "transform"
309
+ ]
310
+ });
311
+ this._watchChildContentLoading(target);
312
+ if (target.durationMs === 0) {
313
+ const checkDuration = () => {
314
+ if (this._targetElement !== target) return;
315
+ if (target.durationMs > 0) this._scheduleRender();
316
+ else requestAnimationFrame(checkDuration);
317
+ };
318
+ requestAnimationFrame(checkDuration);
319
+ }
198
320
  }
199
- this.requestUpdate("targetElement", oldValue);
200
321
  }
201
- set stripWidth(value) {
202
- if (this._thumbnailLayoutTask) {
203
- this._pendingStripWidth = value;
204
- return;
205
- }
206
- this._stripWidth = value;
207
- if (value > 0) this._thumbnailLayoutTask = this.thumbnailLayoutTask.run().then(async () => {
208
- await this.thumbnailLayoutTask.taskComplete;
209
- const layout = this.thumbnailLayoutTask.value;
210
- return layout ? this.runThumbnailRenderTask(layout) : [];
211
- }).finally(() => {
212
- this._thumbnailLayoutTask = void 0;
213
- if (this._pendingStripWidth) {
214
- this.stripWidth = this._pendingStripWidth;
215
- this._pendingStripWidth = void 0;
216
- }
322
+ _scheduleRender() {
323
+ if (this._renderRequested) return;
324
+ this._renderRequested = true;
325
+ requestAnimationFrame(() => {
326
+ this._renderRequested = false;
327
+ this._calculateLayout();
328
+ this._checkCache();
329
+ this._drawCanvas();
330
+ this._loadVisibleThumbnails();
331
+ this._checkAndDispatchReady();
217
332
  });
218
333
  }
219
- get stripWidth() {
220
- return this._stripWidth;
221
- }
222
334
  /**
223
- * Run thumbnail render task directly with provided layout (bypasses task args dependency)
335
+ * Check if thumbnails are ready and dispatch event if not already done.
224
336
  */
225
- async runThumbnailRenderTask(layout) {
226
- if (!layout || !this.targetElement || layout.count === 0) return [];
227
- return this.renderThumbnails(layout, this.targetElement, this.thumbnailWidth);
228
- }
229
- updated(changedProperties) {
230
- super.updated(changedProperties);
231
- if (this._stripWidth === 0) {
232
- const width = this.clientWidth;
233
- if (width > 0) this.stripWidth = width;
337
+ _checkAndDispatchReady() {
338
+ if (this._hasLoadedThumbnails) return;
339
+ const hasLayout = this._thumbnailSlots.length > 0;
340
+ const hasAnyCached = this._thumbnailSlots.some((s) => s.status === "cached");
341
+ const hasPending = this._thumbnailSlots.some((s) => s.status === "pending");
342
+ if (hasLayout && (hasAnyCached || !hasPending)) {
343
+ this._hasLoadedThumbnails = true;
344
+ this.dispatchEvent(new CustomEvent("thumbnails-ready", { bubbles: true }));
234
345
  }
235
- if (changedProperties.has("thumbnailWidth") || changedProperties.has("startTimeMs") || changedProperties.has("endTimeMs") || changedProperties.has("useIntrinsicDuration")) this.runThumbnailUpdate();
236
346
  }
237
347
  /**
238
- * Run thumbnail update with responsive debouncing (based on EFTimegroup currentTime pattern)
348
+ * Calculate thumbnail layout based on current dimensions and time range.
349
+ * Only recreates slots if layout parameters have actually changed.
239
350
  */
240
- runThumbnailUpdate() {
241
- if (this._thumbnailUpdateInProgress) {
242
- this._pendingThumbnailUpdate = true;
351
+ _calculateLayout() {
352
+ if (this._width <= 0 || this._height <= 0 || !this._targetElement) {
353
+ this._thumbnailSlots = [];
354
+ this._lastLayoutParams = null;
243
355
  return;
244
356
  }
245
- this._thumbnailUpdateInProgress = true;
246
- this.thumbnailLayoutTask.run().then(async () => {
247
- await this.thumbnailLayoutTask.taskComplete;
248
- const layout = this.thumbnailLayoutTask.value;
249
- if (layout) await this.runThumbnailRenderTask(layout);
250
- }).catch(() => {}).finally(() => {
251
- this._thumbnailUpdateInProgress = false;
252
- if (this._pendingThumbnailUpdate) {
253
- this._pendingThumbnailUpdate = false;
254
- this.runThumbnailUpdate();
255
- }
256
- });
357
+ const timeRange = this._getTimeRange();
358
+ if (timeRange.endMs <= timeRange.startMs) {
359
+ this._thumbnailSlots = [];
360
+ this._lastLayoutParams = null;
361
+ return;
362
+ }
363
+ const thumbWidth = this._getEffectiveThumbnailWidth();
364
+ const gap = this.gap;
365
+ const currentParams = {
366
+ width: this._width,
367
+ height: this._height,
368
+ startTimeMs: timeRange.startMs,
369
+ endTimeMs: timeRange.endMs,
370
+ thumbWidth,
371
+ gap
372
+ };
373
+ if (this._lastLayoutParams && this._lastLayoutParams.width === currentParams.width && this._lastLayoutParams.height === currentParams.height && this._lastLayoutParams.startTimeMs === currentParams.startTimeMs && this._lastLayoutParams.endTimeMs === currentParams.endTimeMs && this._lastLayoutParams.thumbWidth === currentParams.thumbWidth && this._lastLayoutParams.gap === currentParams.gap) return;
374
+ this._lastLayoutParams = currentParams;
375
+ const count = Math.max(1, Math.floor((this._width + gap) / (thumbWidth + gap)));
376
+ const pitch = count > 1 ? (this._width - thumbWidth) / (count - 1) : 0;
377
+ const slots = [];
378
+ const duration = timeRange.endMs - timeRange.startMs;
379
+ for (let i = 0; i < count; i++) {
380
+ const timeMs = count === 1 ? (timeRange.startMs + timeRange.endMs) / 2 : timeRange.startMs + i * duration / (count - 1);
381
+ slots.push({
382
+ timeMs,
383
+ x: Math.round(i * pitch),
384
+ width: thumbWidth,
385
+ status: "pending"
386
+ });
387
+ }
388
+ this._thumbnailSlots = slots;
257
389
  }
258
390
  /**
259
- * Calculate layout with a ready media engine
391
+ * Get effective time range for thumbnails.
260
392
  */
261
- calculateLayoutWithMediaEngine(stripWidth, thumbnailWidth, targetElement, startTimeMs, endTimeMs, useIntrinsicDuration, mediaEngine) {
262
- if (useIntrinsicDuration) {
263
- const effectiveStartMs$1 = startTimeMs ?? 0;
264
- const effectiveEndMs$1 = endTimeMs ?? targetElement.intrinsicDurationMs ?? 0;
265
- return this.generateLayoutFromTimeRange(stripWidth, thumbnailWidth, effectiveStartMs$1, effectiveEndMs$1, mediaEngine);
393
+ _getTimeRange() {
394
+ const target = this._targetElement;
395
+ if (!target) return {
396
+ startMs: 0,
397
+ endMs: 0
398
+ };
399
+ if (isEFVideo(target)) {
400
+ if (this.useIntrinsicDuration) return {
401
+ startMs: this.startTimeMs ?? 0,
402
+ endMs: this.endTimeMs ?? target.intrinsicDurationMs ?? 0
403
+ };
404
+ const sourceStart = target.sourceStartMs ?? 0;
405
+ const trimmedDuration = target.durationMs ?? 0;
406
+ return {
407
+ startMs: this.startTimeMs !== void 0 ? sourceStart + this.startTimeMs : sourceStart,
408
+ endMs: this.endTimeMs !== void 0 ? sourceStart + this.endTimeMs : sourceStart + trimmedDuration
409
+ };
266
410
  }
267
- const sourceStart = targetElement.sourceStartMs ?? 0;
268
- const trimmedDuration = targetElement.durationMs ?? 0;
269
- const effectiveStartMs = startTimeMs !== void 0 ? sourceStart + startTimeMs : sourceStart;
270
- const effectiveEndMs = endTimeMs !== void 0 ? sourceStart + endTimeMs : sourceStart + trimmedDuration;
271
- return this.generateLayoutFromTimeRange(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, mediaEngine);
411
+ return {
412
+ startMs: this.startTimeMs ?? 0,
413
+ endMs: this.endTimeMs && this.endTimeMs > 0 ? this.endTimeMs : target.durationMs ?? 0
414
+ };
272
415
  }
273
416
  /**
274
- * Generate layout from calculated time range
417
+ * Calculate effective thumbnail width (auto or specified).
275
418
  */
276
- generateLayoutFromTimeRange(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, mediaEngine) {
277
- return calculateThumbnailLayout(stripWidth, thumbnailWidth, effectiveStartMs, effectiveEndMs, mediaEngine && typeof mediaEngine.getScrubVideoRendition === "function" ? mediaEngine.getScrubVideoRendition()?.segmentDurationMs : void 0);
419
+ _getEffectiveThumbnailWidth() {
420
+ if (this.thumbnailWidth > 0) return this.thumbnailWidth;
421
+ const target = this._targetElement;
422
+ let aspectRatio = DEFAULT_ASPECT_RATIO;
423
+ if (isEFVideo(target)) aspectRatio = (target.videoWidth || 1920) / (target.videoHeight || 1080);
424
+ else if (isEFTimegroup(target)) aspectRatio = (target.offsetWidth || 1920) / (target.offsetHeight || 1080);
425
+ return Math.round(this._height * aspectRatio);
278
426
  }
279
427
  /**
280
- * Render thumbnails with provided layout (main rendering logic)
428
+ * Check cache for existing thumbnails.
281
429
  */
282
- async renderThumbnails(layout, targetElement, thumbnailWidth) {
283
- if (!layout || !targetElement || layout.count === 0) return [];
284
- const videoSrc = targetElement.src;
285
- const availableHeight = this._stripHeight - STRIP_BORDER_PADDING;
286
- const allThumbnails = [];
287
- let thumbnailIndex = 0;
288
- for (const segment of layout.segments) for (const thumbnail of segment.thumbnails) {
289
- const cacheKey = getThumbnailCacheKey(videoSrc, thumbnail.timeMs);
290
- let imageData = thumbnailImageCache.get(cacheKey);
291
- let status = "exact-hit";
292
- let nearHitKey;
293
- if (!imageData) {
294
- const timeMinus = Math.max(0, thumbnail.timeMs - 5e3);
295
- const timePlus = thumbnail.timeMs + 5e3;
296
- const rangeStartKey = `${videoSrc}:${timeMinus}`;
297
- const rangeEndKey = `${videoSrc}:${timePlus}`;
298
- const sameVideoHits = thumbnailImageCache.findRange(rangeStartKey, rangeEndKey).filter((hit) => hit.key.startsWith(`${videoSrc}:`));
299
- if (sameVideoHits.length > 0) {
300
- const nearestHit = sameVideoHits.reduce((closest, current) => {
301
- const currentParts = current.key.split(":");
302
- const closestParts = closest.key.split(":");
303
- const currentTime = Number.parseFloat(currentParts[currentParts.length - 1] || "0");
304
- const closestTime = Number.parseFloat(closestParts[closestParts.length - 1] || "0");
305
- return Math.abs(currentTime - thumbnail.timeMs) < Math.abs(closestTime - thumbnail.timeMs) ? current : closest;
306
- });
307
- imageData = nearestHit.value;
308
- status = "near-hit";
309
- nearHitKey = nearestHit.key;
310
- } else status = "missing";
311
- }
312
- const x = thumbnailIndex * (thumbnailWidth + THUMBNAIL_GAP);
313
- allThumbnails.push({
314
- timeMs: thumbnail.timeMs,
315
- segmentId: segment.segmentId,
316
- x,
317
- width: thumbnailWidth,
318
- height: availableHeight,
319
- status,
320
- imageData,
321
- nearHitKey
322
- });
323
- thumbnailIndex++;
430
+ _checkCache() {
431
+ if (!this._targetElement) return;
432
+ const { rootId, elementId, epoch } = getCacheIdentifiers(this._targetElement);
433
+ for (const slot of this._thumbnailSlots) {
434
+ const key = getCacheKey(rootId, elementId, slot.timeMs, epoch);
435
+ if (sessionThumbnailCache.has(key)) {
436
+ slot.imageData = sessionThumbnailCache.get(key);
437
+ slot.status = "cached";
438
+ } else slot.status = "pending";
324
439
  }
325
- await this.drawThumbnails(allThumbnails);
326
- await this.loadMissingThumbnails(allThumbnails, targetElement);
327
- return allThumbnails;
328
- }
329
- connectedCallback() {
330
- super.connectedCallback();
331
- this.resizeObserver = new ResizeObserver((entries) => {
332
- for (const entry of entries) {
333
- const width = entry.borderBoxSize && entry.borderBoxSize.length > 0 ? entry.borderBoxSize[0]?.inlineSize : entry.contentRect.width;
334
- this._stripHeight = (entry.borderBoxSize && entry.borderBoxSize.length > 0 ? entry.borderBoxSize[0]?.blockSize : entry.contentRect.height) ?? 0;
335
- this.stripWidth = width ?? 0;
336
- }
337
- });
338
- this.resizeObserver.observe(this);
339
- this.updateComplete.then(() => {
340
- if (this._stripWidth === 0) {
341
- const width = this.clientWidth;
342
- if (width > 0) this.stripWidth = width ?? 0;
343
- }
344
- });
345
- }
346
- disconnectedCallback() {
347
- super.disconnectedCallback();
348
- this.resizeObserver?.disconnect();
349
- this.resizeObserver = void 0;
350
- this._videoPropertyObserver?.disconnect();
351
- this._videoPropertyObserver = void 0;
352
440
  }
353
441
  /**
354
- * Draw thumbnails to the canvas with cache hits and placeholders
442
+ * Draw the canvas with current thumbnail state.
443
+ * Canvas is absolutely positioned at the visible portion of the strip.
444
+ * Uses virtual rendering - only draws thumbnails in the visible region.
355
445
  */
356
- async drawThumbnails(thumbnails) {
446
+ _drawCanvas() {
357
447
  const canvas = this.canvasRef.value;
358
448
  if (!canvas) return;
359
- const ctx = canvas.getContext("2d");
449
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
360
450
  if (!ctx) return;
451
+ const stripWidth = this._width;
452
+ const height = this._height;
453
+ if (stripWidth <= 0 || height <= 0) return;
361
454
  const dpr = window.devicePixelRatio || 1;
362
- canvas.width = this._stripWidth * dpr;
363
- canvas.height = this._stripHeight * dpr;
364
- canvas.style.width = `${this._stripWidth}px`;
365
- canvas.style.height = `${this._stripHeight}px`;
366
- ctx.scale(dpr, dpr);
367
- ctx.fillStyle = "#2a2a2a";
368
- ctx.fillRect(0, 0, this._stripWidth, this._stripHeight);
369
- for (const thumb of thumbnails) if (thumb.imageData) {
370
- const tempCanvas = document.createElement("canvas");
371
- tempCanvas.width = thumb.imageData.width;
372
- tempCanvas.height = thumb.imageData.height;
373
- const tempCtx = tempCanvas.getContext("2d");
374
- if (!tempCtx) continue;
375
- tempCtx.putImageData(thumb.imageData, 0, 0);
376
- const sourceAspect = thumb.imageData.width / thumb.imageData.height;
377
- const containerAspect = thumb.width / thumb.height;
378
- let drawWidth;
379
- let drawHeight;
380
- let drawX;
381
- let drawY;
382
- if (sourceAspect > containerAspect) {
383
- drawWidth = thumb.width;
384
- drawHeight = Math.round(thumb.width / sourceAspect);
385
- drawX = thumb.x;
386
- drawY = Math.round((this._stripHeight - drawHeight) / 2);
387
- } else {
388
- drawWidth = Math.round(thumb.height * sourceAspect);
389
- drawHeight = thumb.height;
390
- drawX = thumb.x + Math.round((thumb.width - drawWidth) / 2);
391
- drawY = Math.round((this._stripHeight - drawHeight) / 2);
392
- }
393
- ctx.drawImage(tempCanvas, drawX, drawY, drawWidth, drawHeight);
394
- if (thumb.status === "near-hit") {
395
- ctx.fillStyle = "rgba(255, 165, 0, 0.3)";
396
- ctx.fillRect(thumb.x, 0, thumb.width, 2);
455
+ const scrollLeft = this._currentScrollLeft;
456
+ const viewportWidth = this._viewportWidth;
457
+ const stripStartPx = this._getStripTimelinePosition();
458
+ const stripEndPx = stripStartPx + stripWidth;
459
+ const visibleLeftPx = scrollLeft - VIRTUAL_RENDER_PADDING_PX;
460
+ const visibleRightPx = scrollLeft + viewportWidth + VIRTUAL_RENDER_PADDING_PX;
461
+ if (stripEndPx < visibleLeftPx || stripStartPx > visibleRightPx) {
462
+ canvas.style.display = "none";
463
+ return;
464
+ }
465
+ canvas.style.display = "block";
466
+ const visibleStartInStrip = Math.max(0, visibleLeftPx - stripStartPx);
467
+ const visibleEndInStrip = Math.min(stripWidth, visibleRightPx - stripStartPx);
468
+ const visibleWidthPx = visibleEndInStrip - visibleStartInStrip;
469
+ if (visibleWidthPx <= 0) {
470
+ canvas.style.display = "none";
471
+ return;
472
+ }
473
+ const targetWidth = Math.ceil(visibleWidthPx * dpr);
474
+ const targetHeight = Math.ceil(height * dpr);
475
+ if (canvas.width !== targetWidth || canvas.height !== targetHeight) {
476
+ canvas.width = targetWidth;
477
+ canvas.height = targetHeight;
478
+ }
479
+ canvas.style.left = `${visibleStartInStrip}px`;
480
+ canvas.style.width = `${visibleWidthPx}px`;
481
+ canvas.style.height = `${height}px`;
482
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
483
+ ctx.fillStyle = "#1a1a2e";
484
+ ctx.fillRect(0, 0, visibleWidthPx, height);
485
+ for (const slot of this._thumbnailSlots) {
486
+ if (slot.x + slot.width < visibleStartInStrip || slot.x > visibleEndInStrip) continue;
487
+ const drawX = slot.x - visibleStartInStrip;
488
+ if (drawX + slot.width < 0 || drawX > visibleWidthPx) continue;
489
+ if (slot.imageData) this._drawThumbnailImage(ctx, slot.imageData, drawX, slot.width, height);
490
+ else {
491
+ ctx.fillStyle = slot.status === "loading" ? "#2d2d50" : "#2d2d44";
492
+ ctx.fillRect(drawX, 0, slot.width, height);
493
+ if (slot.status === "loading") {
494
+ ctx.fillStyle = "rgba(59, 130, 246, 0.3)";
495
+ ctx.fillRect(drawX, 0, slot.width, 2);
496
+ }
397
497
  }
498
+ }
499
+ }
500
+ /**
501
+ * Draw a thumbnail image with cover mode scaling.
502
+ */
503
+ _drawThumbnailImage(ctx, imageData, x, width, height) {
504
+ const tempCanvas = document.createElement("canvas");
505
+ tempCanvas.width = imageData.width;
506
+ tempCanvas.height = imageData.height;
507
+ const tempCtx = tempCanvas.getContext("2d");
508
+ if (!tempCtx) return;
509
+ tempCtx.putImageData(imageData, 0, 0);
510
+ const srcAspect = imageData.width / imageData.height;
511
+ const dstAspect = width / height;
512
+ let srcX = 0, srcY = 0, srcW = imageData.width, srcH = imageData.height;
513
+ if (srcAspect > dstAspect) {
514
+ srcW = imageData.height * dstAspect;
515
+ srcX = (imageData.width - srcW) / 2;
398
516
  } else {
399
- const placeholderY = Math.round((this._stripHeight - thumb.height) / 2);
400
- ctx.fillStyle = "#404040";
401
- ctx.fillRect(thumb.x, placeholderY, thumb.width, thumb.height);
402
- ctx.strokeStyle = "#666";
403
- ctx.lineWidth = 1;
404
- ctx.setLineDash([2, 2]);
405
- ctx.strokeRect(thumb.x, placeholderY, thumb.width, thumb.height);
406
- ctx.setLineDash([]);
517
+ srcH = imageData.width / dstAspect;
518
+ srcY = (imageData.height - srcH) / 2;
519
+ }
520
+ ctx.drawImage(tempCanvas, srcX, srcY, srcW, srcH, x, 0, width, height);
521
+ }
522
+ /**
523
+ * Load thumbnails that are visible in the current viewport.
524
+ * Skips loading if the epoch hasn't changed since last load.
525
+ */
526
+ async _loadVisibleThumbnails() {
527
+ if (this._captureInProgress || !this._targetElement) return;
528
+ if (isEFTimegroup(this._targetElement)) {
529
+ const currentEpoch = this._targetElement.contentEpoch;
530
+ if (this._lastLoadedEpoch !== null && this._lastLoadedEpoch === currentEpoch) {
531
+ if (!this._thumbnailSlots.some((s) => s.status === "pending")) return;
532
+ }
533
+ }
534
+ const viewportWidth = this._viewportWidth;
535
+ const scrollOffset = this._currentScrollLeft;
536
+ const stripWidth = this._width;
537
+ const stripStartPx = this._getStripTimelinePosition();
538
+ const visibleLeftPx = scrollOffset - VIRTUAL_RENDER_PADDING_PX;
539
+ const visibleRightPx = scrollOffset + viewportWidth + VIRTUAL_RENDER_PADDING_PX;
540
+ const visibleStartInStrip = Math.max(0, visibleLeftPx - stripStartPx);
541
+ const visibleEndInStrip = Math.min(stripWidth, visibleRightPx - stripStartPx);
542
+ const pending = this._thumbnailSlots.filter((slot) => {
543
+ if (slot.status !== "pending") return false;
544
+ return slot.x + slot.width >= visibleStartInStrip && slot.x <= visibleEndInStrip;
545
+ });
546
+ if (pending.length === 0) return;
547
+ this._captureInProgress = true;
548
+ for (const slot of pending) slot.status = "loading";
549
+ this._drawCanvas();
550
+ try {
551
+ if (isEFTimegroup(this._targetElement)) await this._captureTimegroupThumbnails(pending);
552
+ else if (isEFVideo(this._targetElement)) await this._captureVideoThumbnails(pending);
553
+ } catch (error) {
554
+ console.warn("Failed to capture thumbnails:", error);
555
+ for (const slot of pending) if (slot.status === "loading") slot.status = "pending";
556
+ } finally {
557
+ this._captureInProgress = false;
558
+ this._drawCanvas();
559
+ if (isEFTimegroup(this._targetElement)) this._lastLoadedEpoch = this._targetElement.contentEpoch;
560
+ if (this._thumbnailSlots.some((s) => s.status === "cached") && !this._hasLoadedThumbnails) {
561
+ this._hasLoadedThumbnails = true;
562
+ this.dispatchEvent(new CustomEvent("thumbnails-ready", { bubbles: true }));
563
+ }
564
+ }
565
+ }
566
+ /**
567
+ * Capture thumbnails from a timegroup target.
568
+ */
569
+ async _captureTimegroupThumbnails(slots) {
570
+ const target = this._targetElement;
571
+ const { rootId, elementId, epoch } = getCacheIdentifiers(target);
572
+ const timegroupWidth = target.offsetWidth || 1920;
573
+ const timegroupHeight = target.offsetHeight || 1080;
574
+ const scale = Math.min(1, this._height / timegroupHeight, MAX_CAPTURE_WIDTH / timegroupWidth);
575
+ for (let i = 0; i < slots.length; i += BATCH_SIZE) {
576
+ const batch = slots.slice(i, i + BATCH_SIZE);
577
+ const timestamps = batch.map((s) => s.timeMs);
578
+ try {
579
+ const canvases = await target.captureBatch(timestamps, {
580
+ scale,
581
+ contentReadyMode: "immediate"
582
+ });
583
+ for (let j = 0; j < batch.length; j++) {
584
+ const slot = batch[j];
585
+ const canvas = canvases[j];
586
+ if (canvas) {
587
+ const imageData = this._canvasToImageData(canvas);
588
+ if (imageData) {
589
+ const key = getCacheKey(rootId, elementId, slot.timeMs, epoch);
590
+ sessionThumbnailCache.set(key, imageData, slot.timeMs, elementId);
591
+ slot.imageData = imageData;
592
+ slot.status = "cached";
593
+ }
594
+ }
595
+ }
596
+ this._drawCanvas();
597
+ if (i + BATCH_SIZE < slots.length) await new Promise((r) => requestAnimationFrame(r));
598
+ } catch (error) {
599
+ console.warn("Batch capture failed:", error);
600
+ }
407
601
  }
408
602
  }
409
603
  /**
410
- * Load missing thumbnails using MediaEngine batch extraction
604
+ * Capture thumbnails from a video target using MediaEngine.
411
605
  */
412
- async loadMissingThumbnails(thumbnails, targetElement) {
413
- const mediaEngine = targetElement.mediaEngineTask?.value;
606
+ async _captureVideoThumbnails(slots) {
607
+ const target = this._targetElement;
608
+ const { rootId, elementId, epoch } = getCacheIdentifiers(target);
609
+ if (target.mediaEngineTask) await target.mediaEngineTask.taskComplete;
610
+ const mediaEngine = target.mediaEngineTask?.value;
414
611
  if (!mediaEngine) return;
415
- const missingThumbnails = thumbnails.filter((t) => t.status === "missing" || t.status === "near-hit");
416
- if (missingThumbnails.length === 0) return;
417
- for (const thumb of missingThumbnails) thumb.status = "loading";
418
- const timestamps = missingThumbnails.map((t) => t.timeMs);
419
- const thumbnailResults = await mediaEngine.extractThumbnails(timestamps);
420
- for (let i = 0; i < missingThumbnails.length; i++) {
421
- const thumb = missingThumbnails[i];
422
- const thumbnailResult = thumbnailResults[i];
423
- if (thumb && thumbnailResult) {
424
- const imageData = this.canvasToImageData(thumbnailResult.thumbnail);
425
- if (imageData) {
426
- const cacheKey = getThumbnailCacheKey(targetElement.src, thumb.timeMs);
427
- thumbnailImageCache.set(cacheKey, imageData);
428
- thumb.imageData = imageData;
429
- thumb.status = "exact-hit";
612
+ const videoRendition = mediaEngine.getVideoRendition();
613
+ const scrubRendition = mediaEngine.getScrubVideoRendition();
614
+ if (!videoRendition && !scrubRendition) return;
615
+ const timestamps = slots.map((s) => s.timeMs);
616
+ const abortController = new AbortController();
617
+ try {
618
+ const results = await mediaEngine.extractThumbnails(timestamps, abortController.signal);
619
+ for (let i = 0; i < slots.length; i++) {
620
+ const slot = slots[i];
621
+ const result = results[i];
622
+ if (result?.thumbnail) {
623
+ const imageData = this._canvasToImageData(result.thumbnail);
624
+ if (imageData) {
625
+ const key = getCacheKey(rootId, elementId, slot.timeMs, epoch);
626
+ sessionThumbnailCache.set(key, imageData, slot.timeMs, elementId);
627
+ slot.imageData = imageData;
628
+ slot.status = "cached";
629
+ }
430
630
  }
431
631
  }
632
+ } catch (error) {
633
+ abortController.abort();
634
+ console.warn("Video thumbnail extraction failed:", error);
432
635
  }
433
- await this.drawThumbnails(thumbnails);
434
636
  }
435
637
  /**
436
- * Convert Canvas to ImageData for caching
638
+ * Convert canvas to ImageData.
437
639
  */
438
- canvasToImageData(canvas) {
439
- const ctx = canvas.getContext("2d");
640
+ _canvasToImageData(canvas) {
641
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
440
642
  if (!ctx) return null;
441
643
  return ctx.getImageData(0, 0, canvas.width, canvas.height);
442
644
  }
645
+ /**
646
+ * Returns a promise that resolves when thumbnails are ready.
647
+ * Resolves immediately if thumbnails are already loaded.
648
+ */
649
+ whenReady() {
650
+ if (this._hasLoadedThumbnails) return Promise.resolve();
651
+ return new Promise((resolve) => {
652
+ this.addEventListener("thumbnails-ready", () => resolve(), { once: true });
653
+ });
654
+ }
655
+ /**
656
+ * Check if thumbnails have been loaded.
657
+ */
658
+ get isReady() {
659
+ return this._hasLoadedThumbnails;
660
+ }
661
+ /**
662
+ * Invalidate cached thumbnails for this element within a time range.
663
+ * Call this when content changes at specific times.
664
+ */
665
+ invalidateTimeRange(startTimeMs, endTimeMs) {
666
+ if (!this._targetElement) return;
667
+ const { rootId, elementId } = getCacheIdentifiers(this._targetElement);
668
+ sessionThumbnailCache.invalidateTimeRange(rootId, elementId, startTimeMs, endTimeMs);
669
+ for (const slot of this._thumbnailSlots) if (slot.timeMs >= startTimeMs && slot.timeMs <= endTimeMs) {
670
+ slot.imageData = void 0;
671
+ slot.status = "pending";
672
+ }
673
+ this._scheduleRender();
674
+ }
675
+ /**
676
+ * Invalidate all cached thumbnails for this element.
677
+ */
678
+ invalidateAll() {
679
+ if (!this._targetElement) return;
680
+ const { rootId, elementId } = getCacheIdentifiers(this._targetElement);
681
+ sessionThumbnailCache.invalidateElement(rootId, elementId);
682
+ for (const slot of this._thumbnailSlots) {
683
+ slot.imageData = void 0;
684
+ slot.status = "pending";
685
+ }
686
+ this._scheduleRender();
687
+ }
443
688
  render() {
444
- return html`
445
- <canvas ${ref(this.canvasRef)}></canvas>
446
- ${this.thumbnailRenderTask.render({
447
- pending: () => html``,
448
- complete: () => html``,
449
- error: (e) => html`<div class="error">Error loading thumbnails: ${e}</div>`
450
- })}
451
- `;
689
+ return html`<canvas ${ref(this.canvasRef)}></canvas>`;
452
690
  }
453
691
  };
454
- __decorate([state()], EFThumbnailStrip.prototype, "targetElement", null);
455
692
  __decorate([property({ type: String })], EFThumbnailStrip.prototype, "target", void 0);
456
693
  __decorate([property({
457
694
  type: Number,
458
695
  attribute: "thumbnail-width"
459
696
  })], EFThumbnailStrip.prototype, "thumbnailWidth", void 0);
697
+ __decorate([property({
698
+ type: Number,
699
+ attribute: "gap"
700
+ })], EFThumbnailStrip.prototype, "gap", void 0);
460
701
  __decorate([property({
461
702
  type: Number,
462
703
  attribute: "start-time-ms"
@@ -470,14 +711,19 @@ __decorate([property({
470
711
  attribute: "use-intrinsic-duration",
471
712
  reflect: true,
472
713
  converter: {
473
- fromAttribute: (value) => {
474
- if (value === null) return false;
475
- return value === "true";
476
- },
714
+ fromAttribute: (value) => value === "true",
477
715
  toAttribute: (value) => value ? "true" : null
478
716
  }
479
717
  })], EFThumbnailStrip.prototype, "useIntrinsicDuration", void 0);
480
- __decorate([state()], EFThumbnailStrip.prototype, "stripWidth", null);
718
+ __decorate([property({
719
+ type: Number,
720
+ attribute: "pixels-per-ms"
721
+ })], EFThumbnailStrip.prototype, "pixelsPerMs", void 0);
722
+ __decorate([consume({
723
+ context: timelineStateContext,
724
+ subscribe: true
725
+ }), state()], EFThumbnailStrip.prototype, "_timelineState", void 0);
726
+ __decorate([state()], EFThumbnailStrip.prototype, "targetElement", null);
481
727
  EFThumbnailStrip = __decorate([customElement("ef-thumbnail-strip")], EFThumbnailStrip);
482
728
 
483
729
  //#endregion