@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
@@ -7,113 +7,37 @@ import { MainVideoInputCache } from "./MainVideoInputCache.js";
7
7
  import { Task } from "@lit/task";
8
8
 
9
9
  //#region src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts
10
- const scrubInputCache = new ScrubInputCache();
10
+ new ScrubInputCache();
11
11
  const mainVideoInputCache = new MainVideoInputCache();
12
12
  const makeUnifiedVideoSeekTask = (host) => {
13
13
  return new Task(host, {
14
14
  autoRun: false,
15
15
  args: () => [host.desiredSeekTimeMs],
16
16
  onError: (error) => {
17
- console.error("unifiedVideoSeekTask error", error);
17
+ if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"))) return;
18
+ if (error instanceof Error && error.message !== "Video rendition unavailable after checking videoRendition exists" && !error.message.includes("No valid media source") && !error.message.includes("Sample not found for time")) console.error("unifiedVideoSeekTask error", error);
18
19
  },
19
20
  onComplete: (_value) => {},
20
21
  task: async ([desiredSeekTimeMs], { signal }) => {
21
22
  const mediaEngine = await getLatestMediaEngine(host, signal);
22
- if (!mediaEngine || signal.aborted) return void 0;
23
+ if (!mediaEngine) return void 0;
24
+ signal?.throwIfAborted();
23
25
  const mainRendition = mediaEngine.videoRendition;
24
26
  if (mainRendition) {
25
27
  const mainSegmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, mainRendition);
26
28
  if (mainSegmentId !== void 0 && mediaEngine.isSegmentCached(mainSegmentId, mainRendition)) {
27
29
  const result$1 = await getMainVideoSample(host, mediaEngine, desiredSeekTimeMs, signal);
28
- if (signal.aborted) return;
30
+ signal?.throwIfAborted();
29
31
  return result$1;
30
32
  }
31
33
  }
32
- const scrubSample = await tryGetScrubSample(mediaEngine, desiredSeekTimeMs, signal);
33
- if (scrubSample || signal.aborted) {
34
- if (signal.aborted) return;
35
- if (scrubSample) startMainQualityUpgrade(host, mediaEngine, desiredSeekTimeMs, signal).catch(() => {});
36
- return scrubSample;
37
- }
38
34
  const result = await getMainVideoSample(host, mediaEngine, desiredSeekTimeMs, signal);
39
- if (signal.aborted) return;
35
+ signal?.throwIfAborted();
40
36
  return result;
41
37
  }
42
38
  });
43
39
  };
44
40
  /**
45
- * Try to get scrub sample from cache (instant if available)
46
- */
47
- async function tryGetScrubSample(mediaEngine, desiredSeekTimeMs, signal) {
48
- return withSpan("video.tryGetScrubSample", {
49
- desiredSeekTimeMs,
50
- src: mediaEngine.src || "unknown"
51
- }, void 0, async (span) => {
52
- try {
53
- let scrubRendition;
54
- if (typeof mediaEngine.getScrubVideoRendition === "function") scrubRendition = mediaEngine.getScrubVideoRendition();
55
- else if ("data" in mediaEngine && mediaEngine.data?.videoRenditions) scrubRendition = mediaEngine.data.videoRenditions.find((r) => r.id === "scrub");
56
- if (!scrubRendition) {
57
- span.setAttribute("result", "no-scrub-rendition");
58
- return;
59
- }
60
- const scrubRenditionWithSrc = {
61
- ...scrubRendition,
62
- src: mediaEngine.src
63
- };
64
- const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, scrubRenditionWithSrc);
65
- if (segmentId === void 0) {
66
- span.setAttribute("result", "no-segment-id");
67
- return;
68
- }
69
- const isCached = mediaEngine.isSegmentCached(segmentId, scrubRenditionWithSrc);
70
- span.setAttribute("isCached", isCached);
71
- if (!isCached) {
72
- span.setAttribute("result", "not-cached");
73
- return;
74
- }
75
- const scrubInput = await scrubInputCache.getOrCreateInput(segmentId, async () => {
76
- const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal), mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc)]);
77
- if (!initSegment || !mediaSegment || signal.aborted) return void 0;
78
- const { BufferedSeekingInput: BufferedSeekingInput$1 } = await import("../BufferedSeekingInput.js");
79
- const { EFMedia: EFMedia$1 } = await import("../../EFMedia.js");
80
- return new BufferedSeekingInput$1(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
81
- videoBufferSize: EFMedia$1.VIDEO_SAMPLE_BUFFER_SIZE,
82
- audioBufferSize: EFMedia$1.AUDIO_SAMPLE_BUFFER_SIZE,
83
- startTimeOffsetMs: scrubRendition.startTimeOffsetMs
84
- });
85
- });
86
- if (!scrubInput) {
87
- span.setAttribute("result", "no-scrub-input");
88
- return;
89
- }
90
- if (signal.aborted) {
91
- span.setAttribute("result", "aborted-after-scrub-input");
92
- return;
93
- }
94
- const videoTrack = await scrubInput.getFirstVideoTrack();
95
- if (!videoTrack) {
96
- span.setAttribute("result", "no-video-track");
97
- return;
98
- }
99
- if (signal.aborted) {
100
- span.setAttribute("result", "aborted-after-scrub-track");
101
- return;
102
- }
103
- const sample = await scrubInput.seek(videoTrack.id, desiredSeekTimeMs);
104
- span.setAttribute("result", sample ? "success" : "no-sample");
105
- return sample;
106
- } catch (_error) {
107
- if (signal.aborted) {
108
- span.setAttribute("result", "aborted");
109
- return;
110
- }
111
- span.setAttribute("result", "error");
112
- return;
113
- }
114
- });
115
- }
116
- /**
117
41
  * Get main video sample (slower path with fetching)
118
42
  */
119
43
  async function getMainVideoSample(_host, mediaEngine, desiredSeekTimeMs, signal) {
@@ -123,7 +47,10 @@ async function getMainVideoSample(_host, mediaEngine, desiredSeekTimeMs, signal)
123
47
  }, void 0, async (span) => {
124
48
  try {
125
49
  const videoRendition = mediaEngine.getVideoRendition();
126
- if (!videoRendition) throw new Error("Video rendition unavailable after checking videoRendition exists");
50
+ if (!videoRendition) {
51
+ span.setAttribute("result", "no-video-rendition");
52
+ return;
53
+ }
127
54
  const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, videoRendition);
128
55
  if (segmentId === void 0) {
129
56
  span.setAttribute("result", "no-segment-id");
@@ -131,38 +58,44 @@ async function getMainVideoSample(_host, mediaEngine, desiredSeekTimeMs, signal)
131
58
  }
132
59
  span.setAttribute("segmentId", segmentId);
133
60
  const mainInput = await mainVideoInputCache.getOrCreateInput(mediaEngine.src, segmentId, videoRendition.id, async () => {
134
- const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(videoRendition, signal), mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal)]);
61
+ let initSegment;
62
+ let mediaSegment;
63
+ try {
64
+ [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(videoRendition, signal), mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal)]);
65
+ } catch (error) {
66
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
67
+ if (error instanceof Error && (error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch") || error.message.includes("File not found") || error.message.includes("Media segment not found") || error.message.includes("Init segment not found") || error.message.includes("Track not found"))) return;
68
+ throw error;
69
+ }
135
70
  if (!initSegment || !mediaSegment) return;
136
- signal.throwIfAborted();
137
- const startTimeOffsetMs = videoRendition?.startTimeOffsetMs;
138
- return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
71
+ signal?.throwIfAborted();
72
+ signal?.throwIfAborted();
73
+ const combinedBlob = new Blob([initSegment, mediaSegment]);
74
+ signal?.throwIfAborted();
75
+ const arrayBuffer = await combinedBlob.arrayBuffer();
76
+ signal?.throwIfAborted();
77
+ return new BufferedSeekingInput(arrayBuffer, {
139
78
  videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
140
79
  audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
141
- startTimeOffsetMs
80
+ startTimeOffsetMs: videoRendition.startTimeOffsetMs
142
81
  });
143
82
  });
144
83
  if (!mainInput) {
145
84
  span.setAttribute("result", "no-segments");
146
85
  return;
147
86
  }
148
- if (signal.aborted) {
149
- span.setAttribute("result", "aborted-after-input");
150
- return;
151
- }
87
+ signal?.throwIfAborted();
152
88
  const videoTrack = await mainInput.getFirstVideoTrack();
153
89
  if (!videoTrack) {
154
90
  span.setAttribute("result", "no-video-track");
155
91
  return;
156
92
  }
157
- if (signal.aborted) {
158
- span.setAttribute("result", "aborted-after-track");
159
- return;
160
- }
93
+ signal?.throwIfAborted();
161
94
  const sample = await mainInput.seek(videoTrack.id, desiredSeekTimeMs);
162
95
  span.setAttribute("result", sample ? "success" : "no-sample");
163
96
  return sample;
164
97
  } catch (error) {
165
- if (signal.aborted) {
98
+ if (signal?.aborted) {
166
99
  span.setAttribute("result", "aborted");
167
100
  return;
168
101
  }
@@ -170,22 +103,6 @@ async function getMainVideoSample(_host, mediaEngine, desiredSeekTimeMs, signal)
170
103
  }
171
104
  });
172
105
  }
173
- /**
174
- * Start background upgrade to main quality (non-blocking)
175
- */
176
- async function startMainQualityUpgrade(host, mediaEngine, targetSeekTimeMs, signal) {
177
- await new Promise((resolve) => setTimeout(resolve, 50));
178
- if (signal.aborted || host.desiredSeekTimeMs !== targetSeekTimeMs) return;
179
- const mainSample = await getMainVideoSample(host, mediaEngine, targetSeekTimeMs, signal);
180
- if (mainSample && !signal.aborted && host.desiredSeekTimeMs === targetSeekTimeMs) {
181
- const videoFrame = mainSample.toVideoFrame();
182
- try {
183
- host.displayFrame(videoFrame, targetSeekTimeMs);
184
- } finally {
185
- videoFrame.close();
186
- }
187
- }
188
- }
189
106
 
190
107
  //#endregion
191
108
  export { makeUnifiedVideoSeekTask };
@@ -1 +1 @@
1
- {"version":3,"file":"makeUnifiedVideoSeekTask.js","names":["result","scrubRendition: VideoRendition | undefined","BufferedSeekingInput","EFMedia"],"sources":["../../../../src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { VideoSample } from \"mediabunny\";\nimport { withSpan } from \"../../../otel/tracingHelpers.js\";\nimport type { VideoRendition } from \"../../../transcoding/types\";\nimport { EFMedia } from \"../../EFMedia.js\";\nimport type { EFVideo } from \"../../EFVideo\";\nimport { BufferedSeekingInput } from \"../BufferedSeekingInput.js\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\nimport { MainVideoInputCache } from \"./MainVideoInputCache\";\nimport { ScrubInputCache } from \"./ScrubInputCache\";\n\ntype UnifiedVideoSeekTask = Task<readonly [number], VideoSample | undefined>;\n\n// Shared cache for scrub inputs\nconst scrubInputCache = new ScrubInputCache();\n\n// Shared cache for main video inputs\nconst mainVideoInputCache = new MainVideoInputCache();\n\nexport const makeUnifiedVideoSeekTask = (\n host: EFVideo,\n): UnifiedVideoSeekTask => {\n return new Task(host, {\n autoRun: false,\n args: () => [host.desiredSeekTimeMs] as const,\n onError: (error) => {\n console.error(\"unifiedVideoSeekTask error\", error);\n },\n onComplete: (_value) => {},\n task: async ([desiredSeekTimeMs], { signal }) => {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n if (!mediaEngine || signal.aborted) return undefined;\n\n // FIRST: Check if main quality content is already cached\n const mainRendition = mediaEngine.videoRendition;\n if (mainRendition) {\n const mainSegmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n mainRendition,\n );\n if (\n mainSegmentId !== undefined &&\n mediaEngine.isSegmentCached(mainSegmentId, mainRendition)\n ) {\n const result = await getMainVideoSample(\n host,\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n\n if (signal.aborted) {\n return undefined;\n }\n\n return result;\n }\n }\n\n // SECOND: Main not cached, try scrub path (instant if cached)\n const scrubSample = await tryGetScrubSample(\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n\n if (scrubSample || signal.aborted) {\n if (signal.aborted) {\n return undefined;\n }\n\n // If scrub succeeded, start background main quality upgrade (non-blocking)\n if (scrubSample) {\n startMainQualityUpgrade(\n host,\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n ).catch(() => {\n // Main upgrade failed - scrub already succeeded, that's fine\n });\n }\n\n return scrubSample;\n }\n\n // THIRD: Neither are cached, fetch main video path as final fallback\n const result = await getMainVideoSample(\n host,\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n\n if (signal.aborted) {\n return undefined;\n }\n\n return result;\n },\n });\n};\n\n/**\n * Try to get scrub sample from cache (instant if available)\n */\nasync function tryGetScrubSample(\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal,\n): Promise<VideoSample | undefined> {\n return withSpan(\n \"video.tryGetScrubSample\",\n {\n desiredSeekTimeMs,\n src: mediaEngine.src || \"unknown\",\n },\n undefined,\n async (span) => {\n try {\n // Get scrub rendition\n let scrubRendition: VideoRendition | undefined;\n\n // Check if media engine has a getScrubVideoRendition method (AssetMediaEngine, etc.)\n if (typeof mediaEngine.getScrubVideoRendition === \"function\") {\n scrubRendition = mediaEngine.getScrubVideoRendition();\n } else if (\"data\" in mediaEngine && mediaEngine.data?.videoRenditions) {\n // Fallback to data structure for other engines\n scrubRendition = mediaEngine.data.videoRenditions.find(\n (r: any) => r.id === \"scrub\",\n );\n }\n\n if (!scrubRendition) {\n span.setAttribute(\"result\", \"no-scrub-rendition\");\n return undefined;\n }\n\n const scrubRenditionWithSrc = {\n ...scrubRendition,\n src: mediaEngine.src,\n };\n\n // Check if scrub segment is cached\n const segmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n scrubRenditionWithSrc,\n );\n if (segmentId === undefined) {\n span.setAttribute(\"result\", \"no-segment-id\");\n return undefined;\n }\n\n const isCached = mediaEngine.isSegmentCached(\n segmentId,\n scrubRenditionWithSrc,\n );\n span.setAttribute(\"isCached\", isCached);\n if (!isCached) {\n span.setAttribute(\"result\", \"not-cached\");\n return undefined; // Not cached - let main video handle it\n }\n\n // Get cached scrub input and seek within it\n const scrubInput = await scrubInputCache.getOrCreateInput(\n segmentId,\n async () => {\n const [initSegment, mediaSegment] = await Promise.all([\n mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal),\n mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc),\n ]);\n\n if (!initSegment || !mediaSegment || signal.aborted)\n return undefined;\n\n const { BufferedSeekingInput } = await import(\n \"../BufferedSeekingInput.js\"\n );\n const { EFMedia } = await import(\"../../EFMedia.js\");\n\n return new BufferedSeekingInput(\n await new Blob([initSegment, mediaSegment]).arrayBuffer(),\n {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n },\n );\n },\n );\n\n if (!scrubInput) {\n span.setAttribute(\"result\", \"no-scrub-input\");\n return undefined;\n }\n\n if (signal.aborted) {\n span.setAttribute(\"result\", \"aborted-after-scrub-input\");\n return undefined;\n }\n\n const videoTrack = await scrubInput.getFirstVideoTrack();\n if (!videoTrack) {\n span.setAttribute(\"result\", \"no-video-track\");\n return undefined;\n }\n\n if (signal.aborted) {\n span.setAttribute(\"result\", \"aborted-after-scrub-track\");\n return undefined;\n }\n\n const sample = (await scrubInput.seek(\n videoTrack.id,\n desiredSeekTimeMs,\n )) as unknown as VideoSample | undefined;\n\n span.setAttribute(\"result\", sample ? \"success\" : \"no-sample\");\n return sample;\n } catch (_error) {\n if (signal.aborted) {\n span.setAttribute(\"result\", \"aborted\");\n return undefined;\n }\n span.setAttribute(\"result\", \"error\");\n return undefined; // Scrub failed - let main video handle it\n }\n },\n );\n}\n\n/**\n * Get main video sample (slower path with fetching)\n */\nasync function getMainVideoSample(\n _host: EFVideo,\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal,\n): Promise<VideoSample | undefined> {\n return withSpan(\n \"video.getMainVideoSample\",\n {\n desiredSeekTimeMs,\n src: mediaEngine.src || \"unknown\",\n },\n undefined,\n async (span) => {\n try {\n // Use existing main video task chain\n const videoRendition = mediaEngine.getVideoRendition();\n if (!videoRendition) {\n throw new Error(\n \"Video rendition unavailable after checking videoRendition exists\",\n );\n }\n\n const segmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n videoRendition,\n );\n if (segmentId === undefined) {\n span.setAttribute(\"result\", \"no-segment-id\");\n return undefined;\n }\n\n span.setAttribute(\"segmentId\", segmentId);\n\n // Get cached main video input or create new one\n const mainInput = await mainVideoInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n videoRendition.id,\n async () => {\n // Fetch main video segment (will be cached at mediaEngine level)\n const [initSegment, mediaSegment] = await Promise.all([\n mediaEngine.fetchInitSegment(videoRendition, signal),\n mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal),\n ]);\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n signal.throwIfAborted();\n\n const startTimeOffsetMs = videoRendition?.startTimeOffsetMs;\n\n return new BufferedSeekingInput(\n await new Blob([initSegment, mediaSegment]).arrayBuffer(),\n {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs,\n },\n );\n },\n );\n\n if (!mainInput) {\n span.setAttribute(\"result\", \"no-segments\");\n return undefined;\n }\n\n if (signal.aborted) {\n span.setAttribute(\"result\", \"aborted-after-input\");\n return undefined;\n }\n\n const videoTrack = await mainInput.getFirstVideoTrack();\n if (!videoTrack) {\n span.setAttribute(\"result\", \"no-video-track\");\n return undefined;\n }\n\n if (signal.aborted) {\n span.setAttribute(\"result\", \"aborted-after-track\");\n return undefined;\n }\n\n const sample = (await mainInput.seek(\n videoTrack.id,\n desiredSeekTimeMs,\n )) as unknown as VideoSample | undefined;\n\n span.setAttribute(\"result\", sample ? \"success\" : \"no-sample\");\n return sample;\n } catch (error) {\n if (signal.aborted) {\n span.setAttribute(\"result\", \"aborted\");\n return undefined;\n }\n throw error;\n }\n },\n );\n}\n\n/**\n * Start background upgrade to main quality (non-blocking)\n */\nasync function startMainQualityUpgrade(\n host: EFVideo,\n mediaEngine: any,\n targetSeekTimeMs: number,\n signal: AbortSignal,\n): Promise<void> {\n // Small delay to let scrub content display first\n await new Promise((resolve) => setTimeout(resolve, 50));\n if (signal.aborted || host.desiredSeekTimeMs !== targetSeekTimeMs) return;\n\n // Get main quality sample and upgrade display\n const mainSample = await getMainVideoSample(\n host,\n mediaEngine,\n targetSeekTimeMs,\n signal,\n );\n if (\n mainSample &&\n !signal.aborted &&\n host.desiredSeekTimeMs === targetSeekTimeMs\n ) {\n const videoFrame = mainSample.toVideoFrame();\n try {\n host.displayFrame(videoFrame, targetSeekTimeMs);\n } finally {\n videoFrame.close();\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"makeUnifiedVideoSeekTask.js","names":["result","initSegment: ArrayBuffer | undefined","mediaSegment: ArrayBuffer | undefined"],"sources":["../../../../src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { VideoSample } from \"mediabunny\";\nimport { withSpan } from \"../../../otel/tracingHelpers.js\";\nimport type { VideoRendition } from \"../../../transcoding/types\";\nimport { EFMedia } from \"../../EFMedia.js\";\nimport type { EFVideo } from \"../../EFVideo\";\nimport { BufferedSeekingInput } from \"../BufferedSeekingInput.js\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\nimport { MainVideoInputCache } from \"./MainVideoInputCache\";\nimport { ScrubInputCache } from \"./ScrubInputCache\";\n\ntype UnifiedVideoSeekTask = Task<readonly [number], VideoSample | undefined>;\n\n// Shared cache for scrub inputs\nconst scrubInputCache = new ScrubInputCache();\n\n// Shared cache for main video inputs\nconst mainVideoInputCache = new MainVideoInputCache();\n\nexport const makeUnifiedVideoSeekTask = (\n host: EFVideo,\n): UnifiedVideoSeekTask => {\n return new Task(host, {\n autoRun: false,\n args: () => [host.desiredSeekTimeMs] as const,\n onError: (error) => {\n // Don't log AbortErrors - these are expected when tasks are cancelled\n if (\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message?.includes(\"signal is aborted\") ||\n error.message?.includes(\"The user aborted a request\")\n ))\n ) {\n return;\n }\n // Only log unexpected errors - expected conditions handled gracefully\n if (\n error instanceof Error &&\n error.message !== \"Video rendition unavailable after checking videoRendition exists\" &&\n !error.message.includes(\"No valid media source\") &&\n !error.message.includes(\"Sample not found for time\") // Seeking beyond video duration\n ) {\n console.error(\"unifiedVideoSeekTask error\", error);\n }\n },\n onComplete: (_value) => {},\n task: async ([desiredSeekTimeMs], { signal }) => {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n if (!mediaEngine) return undefined;\n signal?.throwIfAborted();\n\n // FIRST: Check if main quality content is already cached\n const mainRendition = mediaEngine.videoRendition;\n if (mainRendition) {\n const mainSegmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n mainRendition,\n );\n if (\n mainSegmentId !== undefined &&\n mediaEngine.isSegmentCached(mainSegmentId, mainRendition)\n ) {\n const result = await getMainVideoSample(\n host,\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n\n signal?.throwIfAborted();\n\n return result;\n }\n }\n\n // SECOND: Scrub track disabled for thumbnail generation\n // Testing showed scrub track is actually SLOWER than main video for this use case\n // because the entire 42MB file needs to be parsed vs efficient segment-based seeking.\n // The scrub track is designed for interactive scrubbing preview, not batch thumbnail gen.\n\n // THIRD: Fetch main video path\n const result = await getMainVideoSample(\n host,\n mediaEngine,\n desiredSeekTimeMs,\n signal,\n );\n\n signal?.throwIfAborted();\n\n return result;\n },\n });\n};\n\n/**\n * Try to get scrub sample from cache (instant if available)\n */\nasync function tryGetScrubSample(\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal,\n): Promise<VideoSample | undefined> {\n return withSpan(\n \"video.tryGetScrubSample\",\n {\n desiredSeekTimeMs,\n src: mediaEngine.src || \"unknown\",\n },\n undefined,\n async (span) => {\n try {\n // Get scrub rendition\n let scrubRendition: VideoRendition | undefined;\n\n // Check if media engine has a getScrubVideoRendition method (AssetMediaEngine, etc.)\n if (typeof mediaEngine.getScrubVideoRendition === \"function\") {\n scrubRendition = mediaEngine.getScrubVideoRendition();\n } else if (\"data\" in mediaEngine && mediaEngine.data?.videoRenditions) {\n // Fallback to data structure for other engines\n scrubRendition = mediaEngine.data.videoRenditions.find(\n (r: any) => r.id === \"scrub\",\n );\n }\n\n if (!scrubRendition) {\n span.setAttribute(\"result\", \"no-scrub-rendition\");\n return undefined;\n }\n\n const scrubRenditionWithSrc = {\n ...scrubRendition,\n src: mediaEngine.src,\n };\n\n // Check if scrub segment is cached\n const segmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n scrubRenditionWithSrc,\n );\n if (segmentId === undefined) {\n span.setAttribute(\"result\", \"no-segment-id\");\n return undefined;\n }\n\n const isCached = mediaEngine.isSegmentCached(\n segmentId,\n scrubRenditionWithSrc,\n );\n span.setAttribute(\"isCached\", isCached);\n if (!isCached) {\n span.setAttribute(\"result\", \"not-cached\");\n return undefined; // Not cached - let main video handle it\n }\n\n // For single-file scrub tracks (AssetMediaEngine trackId -1), use URL-based caching\n // This ensures all segments share the same BufferedSeekingInput instance\n // Use JIT format URL for consistency\n let scrubUrl: string | undefined;\n if (scrubRendition.trackId === -1) {\n // Check if mediaEngine has urlGenerator (AssetMediaEngine)\n if (mediaEngine.urlGenerator && typeof mediaEngine.urlGenerator.baseUrl === \"function\") {\n // Get baseUrl, fallback to current origin if empty\n let baseUrl = mediaEngine.urlGenerator.baseUrl();\n if (!baseUrl && typeof window !== \"undefined\") {\n baseUrl = window.location.origin;\n }\n // Get source URL\n const sourceUrl = mediaEngine.src.startsWith(\"http://\") || mediaEngine.src.startsWith(\"https://\")\n ? mediaEngine.src\n : `${baseUrl}/${mediaEngine.src.startsWith(\"/\") ? mediaEngine.src.slice(1) : mediaEngine.src}`;\n scrubUrl = `${baseUrl}/api/v1/transcode/scrub/init.m4s?url=${encodeURIComponent(sourceUrl)}`;\n } else {\n // Fallback if no urlGenerator (shouldn't happen, but for safety)\n // Use production API format for local files\n let normalizedSrc = mediaEngine.src.startsWith(\"/\")\n ? mediaEngine.src.slice(1)\n : mediaEngine.src;\n normalizedSrc = normalizedSrc.replace(/^\\/+/, \"\");\n // Use the local isobmff API format\n scrubUrl = `/api/v1/isobmff_files/local/track?src=${encodeURIComponent(normalizedSrc)}&trackId=-1`;\n }\n }\n\n // Get cached scrub input and seek within it\n const scrubInput = await scrubInputCache.getOrCreateInput(\n segmentId,\n async () => {\n // Try to fetch segments, but return undefined if they fail with expected errors\n let initSegment: ArrayBuffer | undefined;\n let mediaSegment: ArrayBuffer | undefined;\n \n try {\n [initSegment, mediaSegment] = await Promise.all([\n mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal),\n mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc, signal),\n ]);\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // If fetch fails with expected errors (401, missing segments, etc.), return undefined\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n error.message.includes(\"Failed to fetch\") ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"Media segment not found\") ||\n error.message.includes(\"Init segment not found\") ||\n error.message.includes(\"Track not found\"))\n ) {\n return undefined;\n }\n // Re-throw unexpected errors\n throw error;\n }\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n signal?.throwIfAborted();\n\n const { BufferedSeekingInput } =\n await import(\"../BufferedSeekingInput.js\");\n const { EFMedia } = await import(\"../../EFMedia.js\");\n\n // Create combined blob - this is expensive, check abort before/after\n signal?.throwIfAborted();\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n signal?.throwIfAborted();\n \n const arrayBuffer = await combinedBlob.arrayBuffer();\n signal?.throwIfAborted();\n\n return new BufferedSeekingInput(\n arrayBuffer,\n {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n },\n );\n },\n scrubUrl,\n );\n\n if (!scrubInput) {\n span.setAttribute(\"result\", \"no-scrub-input\");\n return undefined;\n }\n\n signal?.throwIfAborted();\n\n const videoTrack = await scrubInput.getFirstVideoTrack();\n if (!videoTrack) {\n span.setAttribute(\"result\", \"no-video-track\");\n return undefined;\n }\n\n signal?.throwIfAborted();\n\n const sample = (await scrubInput.seek(\n videoTrack.id,\n desiredSeekTimeMs,\n )) as unknown as VideoSample | undefined;\n\n span.setAttribute(\"result\", sample ? \"success\" : \"no-sample\");\n return sample;\n } catch (_error) {\n if (signal?.aborted) {\n span.setAttribute(\"result\", \"aborted\");\n return undefined;\n }\n span.setAttribute(\"result\", \"error\");\n return undefined; // Scrub failed - let main video handle it\n }\n },\n );\n}\n\n/**\n * Get main video sample (slower path with fetching)\n */\nasync function getMainVideoSample(\n _host: EFVideo,\n mediaEngine: any,\n desiredSeekTimeMs: number,\n signal: AbortSignal,\n): Promise<VideoSample | undefined> {\n return withSpan(\n \"video.getMainVideoSample\",\n {\n desiredSeekTimeMs,\n src: mediaEngine.src || \"unknown\",\n },\n undefined,\n async (span) => {\n try {\n // Use existing main video task chain\n const videoRendition = mediaEngine.getVideoRendition();\n if (!videoRendition) {\n // Video rendition not available - return undefined gracefully instead of throwing\n span.setAttribute(\"result\", \"no-video-rendition\");\n return undefined;\n }\n\n const segmentId = mediaEngine.computeSegmentId(\n desiredSeekTimeMs,\n videoRendition,\n );\n if (segmentId === undefined) {\n span.setAttribute(\"result\", \"no-segment-id\");\n return undefined;\n }\n\n span.setAttribute(\"segmentId\", segmentId);\n\n // Get cached main video input or create new one\n const mainInput = await mainVideoInputCache.getOrCreateInput(\n mediaEngine.src,\n segmentId,\n videoRendition.id,\n async () => {\n // Fetch main video segment (will be cached at mediaEngine level)\n // Try to fetch segments, but return undefined if they fail with expected errors\n let initSegment: ArrayBuffer | undefined;\n let mediaSegment: ArrayBuffer | undefined;\n \n try {\n [initSegment, mediaSegment] = await Promise.all([\n mediaEngine.fetchInitSegment(videoRendition, signal),\n mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal),\n ]);\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // If fetch fails with expected errors (401, missing segments, etc.), return undefined\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n error.message.includes(\"Failed to fetch\") ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"Media segment not found\") ||\n error.message.includes(\"Init segment not found\") ||\n error.message.includes(\"Track not found\"))\n ) {\n return undefined;\n }\n // Re-throw unexpected errors\n throw error;\n }\n\n if (!initSegment || !mediaSegment) {\n return undefined;\n }\n signal?.throwIfAborted();\n\n // Create combined blob - this is expensive, check abort before/after\n signal?.throwIfAborted();\n const combinedBlob = new Blob([initSegment, mediaSegment]);\n signal?.throwIfAborted();\n \n const arrayBuffer = await combinedBlob.arrayBuffer();\n signal?.throwIfAborted();\n\n return new BufferedSeekingInput(\n arrayBuffer,\n {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs: videoRendition.startTimeOffsetMs,\n },\n );\n },\n );\n\n if (!mainInput) {\n span.setAttribute(\"result\", \"no-segments\");\n return undefined;\n }\n\n signal?.throwIfAborted();\n\n const videoTrack = await mainInput.getFirstVideoTrack();\n if (!videoTrack) {\n span.setAttribute(\"result\", \"no-video-track\");\n return undefined;\n }\n\n signal?.throwIfAborted();\n\n const sample = (await mainInput.seek(\n videoTrack.id,\n desiredSeekTimeMs,\n )) as unknown as VideoSample | undefined;\n\n span.setAttribute(\"result\", sample ? \"success\" : \"no-sample\");\n return sample;\n } catch (error) {\n if (signal?.aborted) {\n span.setAttribute(\"result\", \"aborted\");\n return undefined;\n }\n throw error;\n }\n },\n );\n}\n\n/**\n * Start background upgrade to main quality (non-blocking)\n */\nasync function startMainQualityUpgrade(\n host: EFVideo,\n mediaEngine: any,\n targetSeekTimeMs: number,\n signal: AbortSignal,\n): Promise<void> {\n // Small delay to let scrub content display first\n await new Promise((resolve) => setTimeout(resolve, 50));\n if (signal?.aborted || host.desiredSeekTimeMs !== targetSeekTimeMs) return;\n\n // Get main quality sample and upgrade display\n const mainSample = await getMainVideoSample(\n host,\n mediaEngine,\n targetSeekTimeMs,\n signal,\n );\n if (\n mainSample &&\n !signal?.aborted &&\n host.desiredSeekTimeMs === targetSeekTimeMs\n ) {\n const videoFrame = mainSample.toVideoFrame();\n try {\n host.displayFrame(videoFrame, targetSeekTimeMs);\n } finally {\n videoFrame.close();\n }\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
2
2
  import { EF_RENDERING } from "../../../EF_RENDERING.js";
3
+ import { AssetMediaEngine } from "../AssetMediaEngine.js";
3
4
  import { manageMediaBuffer } from "../shared/BufferUtils.js";
4
5
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
5
6
  import { Task } from "@lit/task";
@@ -12,10 +13,14 @@ const makeVideoBufferTask = (host) => {
12
13
  activeRequests: /* @__PURE__ */ new Set(),
13
14
  requestQueue: []
14
15
  };
15
- return new Task(host, {
16
+ let task;
17
+ task = new Task(host, {
16
18
  autoRun: EF_INTERACTIVE,
17
19
  args: () => [host.desiredSeekTimeMs],
18
20
  onError: (error) => {
21
+ task.taskComplete.catch(() => {});
22
+ if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"))) return;
23
+ if (error instanceof Error && (error.message === "No valid media source" || error.message.includes("File not found") || error.message.includes("is not valid JSON") || error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch"))) return;
19
24
  console.error("videoBufferTask error", error);
20
25
  },
21
26
  onComplete: (value) => {
@@ -23,7 +28,16 @@ const makeVideoBufferTask = (host) => {
23
28
  },
24
29
  task: async ([seekTimeMs], { signal }) => {
25
30
  if (EF_RENDERING()) return currentState;
26
- const engineConfig = (await getLatestMediaEngine(host, signal)).getBufferConfig();
31
+ if (host.mediaEngineTask.error) return currentState;
32
+ let mediaEngine;
33
+ try {
34
+ mediaEngine = await getLatestMediaEngine(host, signal);
35
+ } catch (error) {
36
+ if (error instanceof Error && error.message === "No valid media source") return currentState;
37
+ throw error;
38
+ }
39
+ if (!mediaEngine) return currentState;
40
+ const engineConfig = mediaEngine.getBufferConfig();
27
41
  const currentConfig = {
28
42
  bufferDurationMs: engineConfig.videoBufferDurationMs,
29
43
  maxParallelFetches: engineConfig.maxVideoBufferFetches,
@@ -37,23 +51,45 @@ const makeVideoBufferTask = (host) => {
37
51
  } : void 0;
38
52
  return manageMediaBuffer(seekTimeMs, currentConfig, currentState, host.intrinsicDurationMs || 1e4, signal, {
39
53
  computeSegmentId: async (timeMs, rendition) => {
40
- return (await getLatestMediaEngine(host, signal)).computeSegmentId(timeMs, rendition);
54
+ const mediaEngine$1 = await getLatestMediaEngine(host, signal);
55
+ if (!mediaEngine$1) return void 0;
56
+ return mediaEngine$1.computeSegmentId(timeMs, rendition);
41
57
  },
42
58
  prefetchSegment: async (segmentId, rendition) => {
43
- await (await getLatestMediaEngine(host, signal)).fetchMediaSegment(segmentId, rendition);
59
+ try {
60
+ const mediaEngine$1 = await getLatestMediaEngine(host, signal);
61
+ if (!mediaEngine$1) return;
62
+ if (mediaEngine$1 instanceof AssetMediaEngine && rendition.trackId !== -1) {
63
+ const trackData = mediaEngine$1.data?.[rendition.trackId];
64
+ if (!trackData?.segments || segmentId >= trackData.segments.length) return;
65
+ }
66
+ await mediaEngine$1.fetchMediaSegment(segmentId, rendition, signal);
67
+ } catch (error) {
68
+ if (error instanceof Error && (error.message.includes("Media segment not found") || error.message.includes("Track not found") || error.message.includes("Failed to fetch") || error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("File not found"))) return;
69
+ throw error;
70
+ }
44
71
  },
45
72
  isSegmentCached: (segmentId, rendition) => {
46
- const mediaEngine = host.mediaEngineTask.value;
47
- if (!mediaEngine) return false;
48
- return mediaEngine.isSegmentCached(segmentId, rendition);
73
+ const mediaEngine$1 = host.mediaEngineTask.value;
74
+ if (!mediaEngine$1) return false;
75
+ return mediaEngine$1.isSegmentCached(segmentId, rendition);
49
76
  },
50
77
  getRendition: async () => {
51
- return (await getLatestMediaEngine(host, signal)).getVideoRendition();
78
+ try {
79
+ const mediaEngine$1 = await getLatestMediaEngine(host, signal);
80
+ if (!mediaEngine$1) throw new Error("Video rendition not available");
81
+ return mediaEngine$1.getVideoRendition();
82
+ } catch (error) {
83
+ if (error instanceof Error && error.message === "No valid media source") throw new Error("Video rendition not available");
84
+ throw error;
85
+ }
52
86
  },
53
87
  logError: console.error
54
88
  }, timelineContext);
55
89
  }
56
90
  });
91
+ task.taskComplete.catch(() => {});
92
+ return task;
57
93
  };
58
94
 
59
95
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"makeVideoBufferTask.js","names":["currentState: VideoBufferState","currentConfig: VideoBufferConfig"],"sources":["../../../../src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\n\nimport { EF_INTERACTIVE } from \"../../../EF_INTERACTIVE\";\nimport { EF_RENDERING } from \"../../../EF_RENDERING\";\nimport type { VideoRendition } from \"../../../transcoding/types\";\nimport type { EFVideo } from \"../../EFVideo\";\nimport {\n type MediaBufferConfig,\n type MediaBufferState,\n manageMediaBuffer,\n} from \"../shared/BufferUtils\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\n/**\n * Configuration for video buffering - extends the generic interface\n */\nexport interface VideoBufferConfig extends MediaBufferConfig {}\n\n/**\n * State of the video buffer - uses the generic interface\n */\nexport interface VideoBufferState extends MediaBufferState {}\n\ntype VideoBufferTask = Task<readonly [number], VideoBufferState>;\nexport const makeVideoBufferTask = (host: EFVideo): VideoBufferTask => {\n let currentState: VideoBufferState = {\n currentSeekTimeMs: 0,\n requestedSegments: new Set(),\n activeRequests: new Set(),\n requestQueue: [],\n };\n\n return new Task(host, {\n autoRun: EF_INTERACTIVE, // Make lazy - only run when element becomes timeline-active\n args: () => [host.desiredSeekTimeMs] as const,\n onError: (error) => {\n console.error(\"videoBufferTask error\", error);\n },\n onComplete: (value) => {\n currentState = value;\n },\n task: async ([seekTimeMs], { signal }) => {\n // Skip buffering entirely in rendering mode\n if (EF_RENDERING()) {\n return currentState; // Return existing state without any buffering activity\n }\n\n // Get media engine to potentially override buffer configuration\n const mediaEngine = await getLatestMediaEngine(host, signal);\n\n // Use media engine's buffer config, falling back to host properties\n const engineConfig = mediaEngine.getBufferConfig();\n const bufferDurationMs = engineConfig.videoBufferDurationMs;\n const maxParallelFetches = engineConfig.maxVideoBufferFetches;\n\n const currentConfig: VideoBufferConfig = {\n bufferDurationMs,\n maxParallelFetches,\n enableBuffering: host.enableVideoBuffering,\n bufferThresholdMs: engineConfig.bufferThresholdMs,\n };\n\n // Timeline context for priority-based buffering\n const timelineContext =\n host.rootTimegroup?.currentTimeMs !== undefined\n ? {\n elementStartMs: host.startTimeMs,\n elementEndMs: host.endTimeMs,\n playheadMs: host.rootTimegroup.currentTimeMs,\n }\n : undefined;\n\n return manageMediaBuffer<VideoRendition>(\n seekTimeMs,\n currentConfig,\n currentState,\n (host as any).intrinsicDurationMs || 10000,\n signal,\n {\n computeSegmentId: async (timeMs, rendition) => {\n // Use media engine's computeSegmentId\n const mediaEngine = await getLatestMediaEngine(host, signal);\n return mediaEngine.computeSegmentId(timeMs, rendition);\n },\n prefetchSegment: async (segmentId, rendition) => {\n // Trigger prefetch through BaseMediaEngine - let it handle caching\n const mediaEngine = await getLatestMediaEngine(host, signal);\n await mediaEngine.fetchMediaSegment(segmentId, rendition);\n // Don't return data - just ensure it's cached in BaseMediaEngine\n },\n isSegmentCached: (segmentId, rendition) => {\n // Check if segment is already cached in BaseMediaEngine\n const mediaEngine = host.mediaEngineTask.value;\n if (!mediaEngine) return false;\n\n return mediaEngine.isSegmentCached(segmentId, rendition);\n },\n getRendition: async () => {\n // Get real video rendition from media engine\n const mediaEngine = await getLatestMediaEngine(host, signal);\n return mediaEngine.getVideoRendition();\n },\n logError: console.error,\n },\n timelineContext,\n );\n },\n });\n};\n"],"mappings":";;;;;;;AAwBA,MAAa,uBAAuB,SAAmC;CACrE,IAAIA,eAAiC;EACnC,mBAAmB;EACnB,mCAAmB,IAAI,KAAK;EAC5B,gCAAgB,IAAI,KAAK;EACzB,cAAc,EAAE;EACjB;AAED,QAAO,IAAI,KAAK,MAAM;EACpB,SAAS;EACT,YAAY,CAAC,KAAK,kBAAkB;EACpC,UAAU,UAAU;AAClB,WAAQ,MAAM,yBAAyB,MAAM;;EAE/C,aAAa,UAAU;AACrB,kBAAe;;EAEjB,MAAM,OAAO,CAAC,aAAa,EAAE,aAAa;AAExC,OAAI,cAAc,CAChB,QAAO;GAOT,MAAM,gBAHc,MAAM,qBAAqB,MAAM,OAAO,EAG3B,iBAAiB;GAIlD,MAAMC,gBAAmC;IACvC,kBAJuB,aAAa;IAKpC,oBAJyB,aAAa;IAKtC,iBAAiB,KAAK;IACtB,mBAAmB,aAAa;IACjC;GAGD,MAAM,kBACJ,KAAK,eAAe,kBAAkB,SAClC;IACE,gBAAgB,KAAK;IACrB,cAAc,KAAK;IACnB,YAAY,KAAK,cAAc;IAChC,GACD;AAEN,UAAO,kBACL,YACA,eACA,cACC,KAAa,uBAAuB,KACrC,QACA;IACE,kBAAkB,OAAO,QAAQ,cAAc;AAG7C,aADoB,MAAM,qBAAqB,MAAM,OAAO,EACzC,iBAAiB,QAAQ,UAAU;;IAExD,iBAAiB,OAAO,WAAW,cAAc;AAG/C,YADoB,MAAM,qBAAqB,MAAM,OAAO,EAC1C,kBAAkB,WAAW,UAAU;;IAG3D,kBAAkB,WAAW,cAAc;KAEzC,MAAM,cAAc,KAAK,gBAAgB;AACzC,SAAI,CAAC,YAAa,QAAO;AAEzB,YAAO,YAAY,gBAAgB,WAAW,UAAU;;IAE1D,cAAc,YAAY;AAGxB,aADoB,MAAM,qBAAqB,MAAM,OAAO,EACzC,mBAAmB;;IAExC,UAAU,QAAQ;IACnB,EACD,gBACD;;EAEJ,CAAC"}
1
+ {"version":3,"file":"makeVideoBufferTask.js","names":["currentState: VideoBufferState","task: VideoBufferTask","currentConfig: VideoBufferConfig","mediaEngine"],"sources":["../../../../src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\n\nimport { EF_INTERACTIVE } from \"../../../EF_INTERACTIVE\";\nimport { EF_RENDERING } from \"../../../EF_RENDERING\";\nimport type { VideoRendition } from \"../../../transcoding/types\";\nimport type { EFVideo } from \"../../EFVideo\";\nimport { AssetMediaEngine } from \"../AssetMediaEngine\";\nimport {\n type MediaBufferConfig,\n type MediaBufferState,\n manageMediaBuffer,\n} from \"../shared/BufferUtils\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\n/**\n * Configuration for video buffering - extends the generic interface\n */\nexport interface VideoBufferConfig extends MediaBufferConfig {}\n\n/**\n * State of the video buffer - uses the generic interface\n */\nexport interface VideoBufferState extends MediaBufferState {}\n\ntype VideoBufferTask = Task<readonly [number], VideoBufferState>;\nexport const makeVideoBufferTask = (host: EFVideo): VideoBufferTask => {\n let currentState: VideoBufferState = {\n currentSeekTimeMs: 0,\n requestedSegments: new Set(),\n activeRequests: new Set(),\n requestQueue: [],\n };\n\n // Capture task reference for use in onError\n let task: VideoBufferTask;\n\n task = new Task(host, {\n autoRun: EF_INTERACTIVE, // Make lazy - only run when element becomes timeline-active\n args: () => [host.desiredSeekTimeMs] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete in onError.\n // This is called BEFORE reject(), so the handler is attached in time.\n task.taskComplete.catch(() => {});\n \n // Don't log AbortErrors - these are expected when tasks are cancelled\n if (\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message?.includes(\"signal is aborted\") ||\n error.message?.includes(\"The user aborted a request\")\n ))\n ) {\n return;\n }\n // Don't log errors when there's no valid media source, file not found, or auth errors - these are expected\n if (error instanceof Error && (\n error.message === \"No valid media source\" ||\n error.message.includes(\"File not found\") ||\n error.message.includes(\"is not valid JSON\") ||\n error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n error.message.includes(\"Failed to fetch\")\n )) {\n return;\n }\n console.error(\"videoBufferTask error\", error);\n },\n onComplete: (value) => {\n currentState = value;\n },\n task: async ([seekTimeMs], { signal }) => {\n // Skip buffering entirely in rendering mode\n if (EF_RENDERING()) {\n return currentState; // Return existing state without any buffering activity\n }\n\n // Check if media engine task has errored (no valid source) before attempting to use it\n if (host.mediaEngineTask.error) {\n return currentState;\n }\n\n // Get media engine to potentially override buffer configuration\n let mediaEngine;\n try {\n mediaEngine = await getLatestMediaEngine(host, signal);\n } catch (error) {\n // If media engine task failed (no valid source), return current state silently\n if (error instanceof Error && error.message === \"No valid media source\") {\n return currentState;\n }\n // Re-throw unexpected errors\n throw error;\n }\n\n // Return existing state if no valid media engine (no valid source)\n if (!mediaEngine) {\n return currentState;\n }\n\n // Use media engine's buffer config, falling back to host properties\n const engineConfig = mediaEngine.getBufferConfig();\n const bufferDurationMs = engineConfig.videoBufferDurationMs;\n const maxParallelFetches = engineConfig.maxVideoBufferFetches;\n\n const currentConfig: VideoBufferConfig = {\n bufferDurationMs,\n maxParallelFetches,\n enableBuffering: host.enableVideoBuffering,\n bufferThresholdMs: engineConfig.bufferThresholdMs,\n };\n\n // Timeline context for priority-based buffering\n const timelineContext =\n host.rootTimegroup?.currentTimeMs !== undefined\n ? {\n elementStartMs: host.startTimeMs,\n elementEndMs: host.endTimeMs,\n playheadMs: host.rootTimegroup.currentTimeMs,\n }\n : undefined;\n\n return manageMediaBuffer<VideoRendition>(\n seekTimeMs,\n currentConfig,\n currentState,\n (host as any).intrinsicDurationMs || 10000,\n signal,\n {\n computeSegmentId: async (timeMs, rendition) => {\n // Use media engine's computeSegmentId\n const mediaEngine = await getLatestMediaEngine(host, signal);\n if (!mediaEngine) return undefined;\n return mediaEngine.computeSegmentId(timeMs, rendition);\n },\n prefetchSegment: async (segmentId, rendition) => {\n // Trigger prefetch through BaseMediaEngine - let it handle caching\n try {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n if (!mediaEngine) return;\n \n // Check if the segment exists in AssetMediaEngine data before prefetching\n // Scrub track uses trackId -1, which is handled specially, so skip check for that\n if (mediaEngine instanceof AssetMediaEngine && rendition.trackId !== -1) {\n // @ts-expect-error - data is protected but we need to check segment existence\n const trackData = (mediaEngine as any).data?.[rendition.trackId];\n if (!trackData?.segments || segmentId >= trackData.segments.length) {\n // Segment doesn't exist in the data - don't prefetch\n return;\n }\n }\n \n await mediaEngine.fetchMediaSegment(segmentId, rendition, signal);\n } catch (error) {\n // If segment doesn't exist or fetch fails (401, etc.), skip prefetch silently\n if (\n error instanceof Error &&\n (error.message.includes(\"Media segment not found\") ||\n error.message.includes(\"Track not found\") ||\n error.message.includes(\"Failed to fetch\") ||\n error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n error.message.includes(\"File not found\"))\n ) {\n return;\n }\n throw error;\n }\n // Don't return data - just ensure it's cached in BaseMediaEngine\n },\n isSegmentCached: (segmentId, rendition) => {\n // Check if segment is already cached in BaseMediaEngine\n const mediaEngine = host.mediaEngineTask.value;\n if (!mediaEngine) return false;\n\n return mediaEngine.isSegmentCached(segmentId, rendition);\n },\n getRendition: async () => {\n // Get real video rendition from media engine\n try {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n if (!mediaEngine) {\n throw new Error(\"Video rendition not available\");\n }\n return mediaEngine.getVideoRendition();\n } catch (error) {\n // If media engine task failed (no valid source), throw error for getRendition\n if (error instanceof Error && error.message === \"No valid media source\") {\n throw new Error(\"Video rendition not available\");\n }\n throw error;\n }\n },\n logError: console.error,\n },\n timelineContext,\n );\n },\n });\n\n // CRITICAL: Attach .catch() handler IMMEDIATELY to prevent unhandled rejections.\n // This must be done synchronously after task creation, before any updates can trigger run().\n // When hostUpdate() triggers _performTask() -> run(), the rejection needs to already have a handler.\n task.taskComplete.catch(() => {});\n\n return task;\n};\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { TemporalMixinInterface } from "./EFTemporal.js";
2
1
  import { EFFramegen } from "../EF_FRAMEGEN.js";
3
2
  import { EFSourceMixinInterface } from "./EFSourceMixin.js";
3
+ import { TemporalMixinInterface } from "./EFTemporal.js";
4
4
  import { FetchMixinInterface } from "./FetchMixin.js";
5
5
  import { BufferedSeekingInput } from "./EFMedia/BufferedSeekingInput.js";
6
6
  import { InputTask } from "./EFMedia/shared/MediaTaskUtils.js";
@@ -9,7 +9,7 @@ import { AudioBufferState } from "./EFMedia/audioTasks/makeAudioBufferTask.js";
9
9
  import { ControllableInterface } from "../gui/Controllable.js";
10
10
  import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
11
11
  import * as _lit_task0 from "@lit/task";
12
- import * as lit6 from "lit";
12
+ import * as lit2 from "lit";
13
13
  import { LitElement, PropertyValueMap } from "lit";
14
14
  import * as mediabunny0 from "mediabunny";
15
15
 
@@ -22,8 +22,19 @@ declare class EFMedia extends EFMedia_base {
22
22
  get efContext(): ControllableInterface | null;
23
23
  static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
24
24
  static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
25
+ /**
26
+ * Which tracks this media element requires.
27
+ * Subclasses can override to specify their needs:
28
+ * - "audio" - Only needs audio track (e.g., EFAudio)
29
+ * - "video" - Only needs video track
30
+ * - "both" - Needs both tracks (default for backwards compatibility)
31
+ *
32
+ * This is used during media engine creation to skip validation
33
+ * of tracks that won't be used, avoiding unnecessary network requests.
34
+ */
35
+ get requiredTracks(): "audio" | "video" | "both";
25
36
  static get observedAttributes(): string[];
26
- static styles: lit6.CSSResult[];
37
+ static styles: lit2.CSSResult[];
27
38
  /**
28
39
  * Duration in milliseconds for audio buffering ahead of current time
29
40
  * @domAttribute "audio-buffer-duration"
@@ -64,7 +75,7 @@ declare class EFMedia extends EFMedia_base {
64
75
  * @domAttribute "interpolate-frequencies"
65
76
  */
66
77
  interpolateFrequencies: boolean;
67
- get FREQ_WEIGHTS(): Float32Array;
78
+ get FREQ_WEIGHTS(): Float32Array<ArrayBufferLike>;
68
79
  get shouldInterpolateFrequencies(): boolean;
69
80
  get urlGenerator(): UrlGenerator;
70
81
  mediaEngineTask: _lit_task0.Task<readonly [string, string | null], MediaEngine>;
@@ -74,8 +85,8 @@ declare class EFMedia extends EFMedia_base {
74
85
  audioInputTask: InputTask;
75
86
  audioSeekTask: _lit_task0.Task<readonly [number, BufferedSeekingInput | undefined], mediabunny0.VideoSample | undefined>;
76
87
  audioBufferTask: _lit_task0.Task<readonly [number], AudioBufferState>;
77
- byteTimeDomainTask: _lit_task0.Task<readonly [number, number, number, number, boolean], Uint8Array | null>;
78
- frequencyDataTask: _lit_task0.Task<readonly [number, number, number, number, boolean], Uint8Array | null>;
88
+ byteTimeDomainTask: _lit_task0.Task<readonly [number], Uint8Array<ArrayBufferLike> | null>;
89
+ frequencyDataTask: _lit_task0.Task<readonly [number], Uint8Array<ArrayBufferLike> | null>;
79
90
  /**
80
91
  * The unique identifier for the media asset.
81
92
  * This property can be set programmatically or via the "asset-id" attribute.
@@ -99,7 +110,7 @@ declare class EFMedia extends EFMedia_base {
99
110
  * Wait for media engine to load and determine duration
100
111
  * Ensures media is ready for playback
101
112
  */
102
- waitForMediaDurations(): Promise<void>;
113
+ waitForMediaDurations(signal?: AbortSignal): Promise<void>;
103
114
  /**
104
115
  * Returns media elements for playback audio rendering
105
116
  * For standalone media, returns [this]; for timegroups, returns all descendants
@@ -62,6 +62,19 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
62
62
  static {
63
63
  this.AUDIO_SAMPLE_BUFFER_SIZE = 120;
64
64
  }
65
+ /**
66
+ * Which tracks this media element requires.
67
+ * Subclasses can override to specify their needs:
68
+ * - "audio" - Only needs audio track (e.g., EFAudio)
69
+ * - "video" - Only needs video track
70
+ * - "both" - Needs both tracks (default for backwards compatibility)
71
+ *
72
+ * This is used during media engine creation to skip validation
73
+ * of tracks that won't be used, avoiding unnecessary network requests.
74
+ */
75
+ get requiredTracks() {
76
+ return "both";
77
+ }
65
78
  static get observedAttributes() {
66
79
  return [
67
80
  ...super.observedAttributes || [],
@@ -153,7 +166,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
153
166
  * Now powered by clean, testable utility functions
154
167
  * Returns undefined if no audio rendition is available
155
168
  */
156
- async fetchAudioSpanningTime(fromMs, toMs, signal = new AbortController().signal) {
169
+ async fetchAudioSpanningTime(fromMs, toMs, signal) {
157
170
  return withSpan("media.fetchAudioSpanningTime", {
158
171
  elementId: this.id || "unknown",
159
172
  tagName: this.tagName.toLowerCase(),
@@ -169,9 +182,16 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
169
182
  * Wait for media engine to load and determine duration
170
183
  * Ensures media is ready for playback
171
184
  */
172
- async waitForMediaDurations() {
185
+ async waitForMediaDurations(signal) {
173
186
  if (this.mediaEngineTask.value) return;
174
- await this.mediaEngineTask.run();
187
+ try {
188
+ await this.mediaEngineTask.taskComplete;
189
+ } catch (error) {
190
+ const isAbortError = error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message.includes("signal is aborted") || error.message.includes("The user aborted a request"));
191
+ if (signal?.aborted) throw error;
192
+ if (isAbortError) return;
193
+ throw error;
194
+ }
175
195
  }
176
196
  /**
177
197
  * Returns media elements for playback audio rendering
@@ -1 +1 @@
1
- {"version":3,"file":"EFMedia.js","names":[],"sources":["../../src/elements/EFMedia.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport type { ControllableInterface } from \"../gui/Controllable.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { withSpan } from \"../otel/tracingHelpers.js\";\nimport type { AudioSpan } from \"../transcoding/types/index.ts\";\nimport { UrlGenerator } from \"../transcoding/utils/UrlGenerator.ts\";\nimport { makeAudioBufferTask } from \"./EFMedia/audioTasks/makeAudioBufferTask.ts\";\nimport { makeAudioFrequencyAnalysisTask } from \"./EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts\";\nimport { makeAudioInitSegmentFetchTask } from \"./EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts\";\nimport { makeAudioInputTask } from \"./EFMedia/audioTasks/makeAudioInputTask.ts\";\nimport { makeAudioSeekTask } from \"./EFMedia/audioTasks/makeAudioSeekTask.ts\";\nimport { makeAudioSegmentFetchTask } from \"./EFMedia/audioTasks/makeAudioSegmentFetchTask.ts\";\nimport { makeAudioSegmentIdTask } from \"./EFMedia/audioTasks/makeAudioSegmentIdTask.ts\";\nimport { makeAudioTimeDomainAnalysisTask } from \"./EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts\";\nimport { fetchAudioSpanningTime } from \"./EFMedia/shared/AudioSpanUtils.ts\";\nimport { makeMediaEngineTask } from \"./EFMedia/tasks/makeMediaEngineTask.ts\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { EFTemporal } from \"./EFTemporal.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.ts\";\n\n// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts\ndeclare global {\n var EF_FRAMEGEN: import(\"../EF_FRAMEGEN.js\").EFFramegen;\n}\n\nconst freqWeightsCache = new Map<number, Float32Array>();\n\nexport class IgnorableError extends Error {}\n\nexport const deepGetMediaElements = (\n element: Element,\n medias: EFMedia[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFMedia) {\n medias.push(child);\n } else {\n deepGetMediaElements(child, medias);\n }\n }\n return medias;\n};\n\nexport class EFMedia extends EFTargetable(\n EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {\n assetType: \"isobmff_files\",\n }),\n) {\n @provide({ context: efContext })\n get efContext(): ControllableInterface | null {\n return this.rootTimegroup ?? this;\n }\n\n // Sample buffer size configuration\n static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;\n static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;\n\n static get observedAttributes() {\n // biome-ignore lint/complexity/noThisInStatic: We need to access super\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mute\",\n \"fft-size\",\n \"fft-decay\",\n \"fft-gain\",\n \"interpolate-frequencies\",\n \"asset-id\",\n \"audio-buffer-duration\",\n \"max-audio-buffer-fetches\",\n \"enable-audio-buffering\",\n \"sourcein\",\n \"sourceout\",\n ];\n }\n\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n `,\n ];\n\n /**\n * Duration in milliseconds for audio buffering ahead of current time\n * @domAttribute \"audio-buffer-duration\"\n */\n @property({ type: Number, attribute: \"audio-buffer-duration\" })\n audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent audio segment fetches for buffering\n * @domAttribute \"max-audio-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-audio-buffer-fetches\" })\n maxAudioBufferFetches = 2;\n\n /**\n * Enable/disable audio buffering system\n * @domAttribute \"enable-audio-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-audio-buffering\" })\n enableAudioBuffering = true;\n\n /**\n * Mute/unmute the media element\n * @domAttribute \"mute\"\n */\n @property({\n type: Boolean,\n attribute: \"mute\",\n reflect: true,\n })\n mute = false;\n\n /**\n * FFT size for frequency analysis\n * @domAttribute \"fft-size\"\n */\n @property({ type: Number, attribute: \"fft-size\", reflect: true })\n fftSize = 128;\n\n /**\n * FFT decay rate for frequency analysis\n * @domAttribute \"fft-decay\"\n */\n @property({ type: Number, attribute: \"fft-decay\", reflect: true })\n fftDecay = 8;\n\n /**\n * FFT gain for frequency analysis\n * @domAttribute \"fft-gain\"\n */\n @property({ type: Number, attribute: \"fft-gain\", reflect: true })\n fftGain = 3.0;\n\n /**\n * Enable/disable frequency interpolation\n * @domAttribute \"interpolate-frequencies\"\n */\n @property({\n type: Boolean,\n attribute: \"interpolate-frequencies\",\n reflect: true,\n })\n interpolateFrequencies = false;\n\n // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value\n get FREQ_WEIGHTS() {\n if (freqWeightsCache.has(this.fftSize)) {\n // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above\n return freqWeightsCache.get(this.fftSize)!;\n }\n\n const weights = new Float32Array(this.fftSize / 2).map((_, i) => {\n const frequency = (i * 48000) / this.fftSize;\n if (frequency < 60) return 0.3;\n if (frequency < 250) return 0.4;\n if (frequency < 500) return 0.6;\n if (frequency < 2000) return 0.8;\n if (frequency < 4000) return 1.2;\n if (frequency < 8000) return 1.6;\n return 2.0;\n });\n\n freqWeightsCache.set(this.fftSize, weights);\n return weights;\n }\n\n // Helper getter for backwards compatibility\n get shouldInterpolateFrequencies() {\n return this.interpolateFrequencies;\n }\n\n get urlGenerator() {\n return new UrlGenerator(() => this.apiHost ?? \"\");\n }\n\n mediaEngineTask = makeMediaEngineTask(this);\n\n audioSegmentIdTask = makeAudioSegmentIdTask(this);\n audioInitSegmentFetchTask = makeAudioInitSegmentFetchTask(this);\n audioSegmentFetchTask = makeAudioSegmentFetchTask(this);\n audioInputTask = makeAudioInputTask(this);\n audioSeekTask = makeAudioSeekTask(this);\n\n audioBufferTask = makeAudioBufferTask(this);\n\n // Audio analysis tasks for frequency and time domain analysis\n byteTimeDomainTask = makeAudioTimeDomainAnalysisTask(this);\n frequencyDataTask = makeAudioFrequencyAnalysisTask(this);\n\n /**\n * The unique identifier for the media asset.\n * This property can be set programmatically or via the \"asset-id\" attribute.\n * @domAttribute \"asset-id\"\n */\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n assetId: string | null = null;\n\n get intrinsicDurationMs() {\n return this.mediaEngineTask.value?.durationMs ?? 0;\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property\n const newCurrentSourceTimeMs = this.currentSourceTimeMs;\n if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {\n this.executeSeek(newCurrentSourceTimeMs);\n }\n\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.executeSeek(this.currentSourceTimeMs);\n }\n\n // Check if trim/source properties changed that affect duration\n const durationAffectingProps = [\n \"_trimStartMs\",\n \"_trimEndMs\",\n \"_sourceInMs\",\n \"_sourceOutMs\",\n ];\n\n const hasDurationChange = durationAffectingProps.some((prop) =>\n changedProperties.has(prop),\n );\n\n if (hasDurationChange) {\n // Notify parent timegroup to recalculate its duration (same pattern as EFCaptions)\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n\n // Also find and directly notify any context provider (ContextMixin)\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n parent.dispatchEvent(\n new CustomEvent(\"child-duration-changed\", {\n detail: { source: this },\n }),\n );\n break;\n }\n parent = parent.parentNode;\n }\n }\n }\n }\n\n get hasOwnDuration() {\n return true;\n }\n\n @state()\n private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading\n\n get desiredSeekTimeMs() {\n return this._desiredSeekTimeMs;\n }\n\n set desiredSeekTimeMs(value: number) {\n if (this._desiredSeekTimeMs !== value) {\n this._desiredSeekTimeMs = value;\n }\n }\n\n protected async executeSeek(seekToMs: number) {\n // The seekToMs parameter should be the timeline-relative media time\n // calculated from currentSourceTimeMs which includes timeline positioning\n this.desiredSeekTimeMs = seekToMs;\n }\n\n /**\n * Main integration method for EFTimegroup audio playback\n * Now powered by clean, testable utility functions\n * Returns undefined if no audio rendition is available\n */\n async fetchAudioSpanningTime(\n fromMs: number,\n toMs: number,\n signal: AbortSignal = new AbortController().signal,\n ): Promise<AudioSpan | undefined> {\n return withSpan(\n \"media.fetchAudioSpanningTime\",\n {\n elementId: this.id || \"unknown\",\n tagName: this.tagName.toLowerCase(),\n fromMs,\n toMs,\n durationMs: toMs - fromMs,\n src: this.src || \"none\",\n },\n undefined,\n async () => {\n return fetchAudioSpanningTime(this, fromMs, toMs, signal);\n },\n );\n }\n\n /**\n * Wait for media engine to load and determine duration\n * Ensures media is ready for playback\n */\n async waitForMediaDurations(): Promise<void> {\n if (this.mediaEngineTask.value) {\n return;\n }\n await this.mediaEngineTask.run();\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return [this];\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"EFMedia.js","names":[],"sources":["../../src/elements/EFMedia.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport { css, LitElement, type PropertyValueMap } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport type { ControllableInterface } from \"../gui/Controllable.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { withSpan } from \"../otel/tracingHelpers.js\";\nimport type { AudioSpan } from \"../transcoding/types/index.ts\";\nimport { UrlGenerator } from \"../transcoding/utils/UrlGenerator.ts\";\nimport { makeAudioBufferTask } from \"./EFMedia/audioTasks/makeAudioBufferTask.ts\";\nimport { makeAudioFrequencyAnalysisTask } from \"./EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts\";\nimport { makeAudioInitSegmentFetchTask } from \"./EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts\";\nimport { makeAudioInputTask } from \"./EFMedia/audioTasks/makeAudioInputTask.ts\";\nimport { makeAudioSeekTask } from \"./EFMedia/audioTasks/makeAudioSeekTask.ts\";\nimport { makeAudioSegmentFetchTask } from \"./EFMedia/audioTasks/makeAudioSegmentFetchTask.ts\";\nimport { makeAudioSegmentIdTask } from \"./EFMedia/audioTasks/makeAudioSegmentIdTask.ts\";\nimport { makeAudioTimeDomainAnalysisTask } from \"./EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts\";\nimport { fetchAudioSpanningTime } from \"./EFMedia/shared/AudioSpanUtils.ts\";\nimport { makeMediaEngineTask } from \"./EFMedia/tasks/makeMediaEngineTask.ts\";\nimport { EFSourceMixin } from \"./EFSourceMixin.js\";\nimport { FetchMixin } from \"./FetchMixin.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.ts\";\n\n// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts\ndeclare global {\n var EF_FRAMEGEN: import(\"../EF_FRAMEGEN.js\").EFFramegen;\n}\n\nconst freqWeightsCache = new Map<number, Float32Array>();\n\nexport class IgnorableError extends Error {}\n\nexport const deepGetMediaElements = (\n element: Element,\n medias: EFMedia[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFMedia) {\n medias.push(child);\n } else {\n deepGetMediaElements(child, medias);\n }\n }\n return medias;\n};\n\n// Import EFTemporal - use a function wrapper to defer evaluation until class definition\n// This breaks the circular dependency: EFTimegroup -> EFMedia -> EFTemporal\nimport { EFTemporal } from \"./EFTemporal.js\";\n\nexport class EFMedia extends EFTargetable(\n EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {\n assetType: \"isobmff_files\",\n }),\n) {\n @provide({ context: efContext })\n get efContext(): ControllableInterface | null {\n return this.rootTimegroup ?? this;\n }\n\n // Sample buffer size configuration\n static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;\n static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;\n\n /**\n * Which tracks this media element requires.\n * Subclasses can override to specify their needs:\n * - \"audio\" - Only needs audio track (e.g., EFAudio)\n * - \"video\" - Only needs video track\n * - \"both\" - Needs both tracks (default for backwards compatibility)\n * \n * This is used during media engine creation to skip validation\n * of tracks that won't be used, avoiding unnecessary network requests.\n */\n get requiredTracks(): \"audio\" | \"video\" | \"both\" {\n return \"both\";\n }\n\n static get observedAttributes() {\n // biome-ignore lint/complexity/noThisInStatic: We need to access super\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mute\",\n \"fft-size\",\n \"fft-decay\",\n \"fft-gain\",\n \"interpolate-frequencies\",\n \"asset-id\",\n \"audio-buffer-duration\",\n \"max-audio-buffer-fetches\",\n \"enable-audio-buffering\",\n \"sourcein\",\n \"sourceout\",\n ];\n }\n\n static styles = [\n css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n `,\n ];\n\n /**\n * Duration in milliseconds for audio buffering ahead of current time\n * @domAttribute \"audio-buffer-duration\"\n */\n @property({ type: Number, attribute: \"audio-buffer-duration\" })\n audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding\n\n /**\n * Maximum number of concurrent audio segment fetches for buffering\n * @domAttribute \"max-audio-buffer-fetches\"\n */\n @property({ type: Number, attribute: \"max-audio-buffer-fetches\" })\n maxAudioBufferFetches = 2;\n\n /**\n * Enable/disable audio buffering system\n * @domAttribute \"enable-audio-buffering\"\n */\n @property({ type: Boolean, attribute: \"enable-audio-buffering\" })\n enableAudioBuffering = true;\n\n /**\n * Mute/unmute the media element\n * @domAttribute \"mute\"\n */\n @property({\n type: Boolean,\n attribute: \"mute\",\n reflect: true,\n })\n mute = false;\n\n /**\n * FFT size for frequency analysis\n * @domAttribute \"fft-size\"\n */\n @property({ type: Number, attribute: \"fft-size\", reflect: true })\n fftSize = 128;\n\n /**\n * FFT decay rate for frequency analysis\n * @domAttribute \"fft-decay\"\n */\n @property({ type: Number, attribute: \"fft-decay\", reflect: true })\n fftDecay = 8;\n\n /**\n * FFT gain for frequency analysis\n * @domAttribute \"fft-gain\"\n */\n @property({ type: Number, attribute: \"fft-gain\", reflect: true })\n fftGain = 3.0;\n\n /**\n * Enable/disable frequency interpolation\n * @domAttribute \"interpolate-frequencies\"\n */\n @property({\n type: Boolean,\n attribute: \"interpolate-frequencies\",\n reflect: true,\n })\n interpolateFrequencies = false;\n\n // Update FREQ_WEIGHTS to use the instance fftSize instead of a static value\n get FREQ_WEIGHTS() {\n if (freqWeightsCache.has(this.fftSize)) {\n // biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above\n return freqWeightsCache.get(this.fftSize)!;\n }\n\n const weights = new Float32Array(this.fftSize / 2).map((_, i) => {\n const frequency = (i * 48000) / this.fftSize;\n if (frequency < 60) return 0.3;\n if (frequency < 250) return 0.4;\n if (frequency < 500) return 0.6;\n if (frequency < 2000) return 0.8;\n if (frequency < 4000) return 1.2;\n if (frequency < 8000) return 1.6;\n return 2.0;\n });\n\n freqWeightsCache.set(this.fftSize, weights);\n return weights;\n }\n\n // Helper getter for backwards compatibility\n get shouldInterpolateFrequencies() {\n return this.interpolateFrequencies;\n }\n\n get urlGenerator() {\n return new UrlGenerator(() => this.apiHost ?? \"\");\n }\n\n mediaEngineTask = makeMediaEngineTask(this);\n\n audioSegmentIdTask = makeAudioSegmentIdTask(this);\n audioInitSegmentFetchTask = makeAudioInitSegmentFetchTask(this);\n audioSegmentFetchTask = makeAudioSegmentFetchTask(this);\n audioInputTask = makeAudioInputTask(this);\n audioSeekTask = makeAudioSeekTask(this);\n\n audioBufferTask = makeAudioBufferTask(this);\n\n // Audio analysis tasks for frequency and time domain analysis\n byteTimeDomainTask = makeAudioTimeDomainAnalysisTask(this);\n frequencyDataTask = makeAudioFrequencyAnalysisTask(this);\n\n /**\n * The unique identifier for the media asset.\n * This property can be set programmatically or via the \"asset-id\" attribute.\n * @domAttribute \"asset-id\"\n */\n @property({ type: String, attribute: \"asset-id\", reflect: true })\n assetId: string | null = null;\n\n get intrinsicDurationMs() {\n return this.mediaEngineTask.value?.durationMs ?? 0;\n }\n\n protected updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Check if our timeline position has actually changed, even if ownCurrentTimeMs isn't tracked as a property\n const newCurrentSourceTimeMs = this.currentSourceTimeMs;\n if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) {\n this.executeSeek(newCurrentSourceTimeMs);\n }\n\n if (changedProperties.has(\"ownCurrentTimeMs\")) {\n this.executeSeek(this.currentSourceTimeMs);\n }\n\n // Check if trim/source properties changed that affect duration\n const durationAffectingProps = [\n \"_trimStartMs\",\n \"_trimEndMs\",\n \"_sourceInMs\",\n \"_sourceOutMs\",\n ];\n\n const hasDurationChange = durationAffectingProps.some((prop) =>\n changedProperties.has(prop),\n );\n\n if (hasDurationChange) {\n // Notify parent timegroup to recalculate its duration (same pattern as EFCaptions)\n if (this.parentTimegroup) {\n this.parentTimegroup.requestUpdate(\"durationMs\");\n this.parentTimegroup.requestUpdate(\"currentTime\");\n\n // Also find and directly notify any context provider (ContextMixin)\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n parent.dispatchEvent(\n new CustomEvent(\"child-duration-changed\", {\n detail: { source: this },\n }),\n );\n break;\n }\n parent = parent.parentNode;\n }\n }\n }\n }\n\n get hasOwnDuration() {\n return true;\n }\n\n @state()\n private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading\n\n get desiredSeekTimeMs() {\n return this._desiredSeekTimeMs;\n }\n\n set desiredSeekTimeMs(value: number) {\n if (this._desiredSeekTimeMs !== value) {\n this._desiredSeekTimeMs = value;\n }\n }\n\n protected async executeSeek(seekToMs: number) {\n // The seekToMs parameter should be the timeline-relative media time\n // calculated from currentSourceTimeMs which includes timeline positioning\n this.desiredSeekTimeMs = seekToMs;\n }\n\n /**\n * Main integration method for EFTimegroup audio playback\n * Now powered by clean, testable utility functions\n * Returns undefined if no audio rendition is available\n */\n async fetchAudioSpanningTime(\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n ): Promise<AudioSpan | undefined> {\n return withSpan(\n \"media.fetchAudioSpanningTime\",\n {\n elementId: this.id || \"unknown\",\n tagName: this.tagName.toLowerCase(),\n fromMs,\n toMs,\n durationMs: toMs - fromMs,\n src: this.src || \"none\",\n },\n undefined,\n async () => {\n return fetchAudioSpanningTime(this, fromMs, toMs, signal);\n },\n );\n }\n\n /**\n * Wait for media engine to load and determine duration\n * Ensures media is ready for playback\n */\n async waitForMediaDurations(signal?: AbortSignal): Promise<void> {\n if (this.mediaEngineTask.value) {\n return;\n }\n \n // Use taskComplete instead of run() to avoid throwing errors\n // taskComplete resolves when the task completes successfully or rejects on error\n // This allows us to handle AbortError without it being logged as unhandled\n try {\n await this.mediaEngineTask.taskComplete;\n } catch (error) {\n // Don't throw AbortError - these are intentional cancellations when element is disconnected\n const isAbortError = \n error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message.includes(\"signal is aborted\") ||\n error.message.includes(\"The user aborted a request\")\n );\n \n // If explicitly aborted via signal, throw to propagate cancellation\n if (signal?.aborted) {\n throw error;\n }\n \n // For task abort (element disconnected), silently return\n // This is expected behavior when element is removed from DOM\n if (isAbortError) {\n return;\n }\n \n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n */\n getMediaElements(): EFMedia[] {\n return [this];\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n */\n async renderAudio(fromMs: number, toMs: number): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs);\n }\n}\n"],"mappings":"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"}