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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +5 -0
  2. package/dist/EF_FRAMEGEN.js +20 -4
  3. package/dist/EF_FRAMEGEN.js.map +1 -1
  4. package/dist/EF_INTERACTIVE.js.map +1 -1
  5. package/dist/_virtual/rolldown_runtime.js +27 -0
  6. package/dist/canvas/EFCanvas.d.ts +311 -0
  7. package/dist/canvas/EFCanvas.js +1089 -0
  8. package/dist/canvas/EFCanvas.js.map +1 -0
  9. package/dist/canvas/EFCanvasItem.d.ts +55 -0
  10. package/dist/canvas/EFCanvasItem.js +72 -0
  11. package/dist/canvas/EFCanvasItem.js.map +1 -0
  12. package/dist/canvas/api/CanvasAPI.d.ts +115 -0
  13. package/dist/canvas/api/CanvasAPI.js +182 -0
  14. package/dist/canvas/api/CanvasAPI.js.map +1 -0
  15. package/dist/canvas/api/types.d.ts +42 -0
  16. package/dist/canvas/coordinateTransform.js +90 -0
  17. package/dist/canvas/coordinateTransform.js.map +1 -0
  18. package/dist/canvas/getElementBounds.js +40 -0
  19. package/dist/canvas/getElementBounds.js.map +1 -0
  20. package/dist/canvas/overlays/SelectionOverlay.js +265 -0
  21. package/dist/canvas/overlays/SelectionOverlay.js.map +1 -0
  22. package/dist/canvas/overlays/overlayState.js +153 -0
  23. package/dist/canvas/overlays/overlayState.js.map +1 -0
  24. package/dist/canvas/selection/SelectionController.js +105 -0
  25. package/dist/canvas/selection/SelectionController.js.map +1 -0
  26. package/dist/canvas/selection/SelectionModel.d.ts +98 -0
  27. package/dist/canvas/selection/SelectionModel.js +229 -0
  28. package/dist/canvas/selection/SelectionModel.js.map +1 -0
  29. package/dist/canvas/selection/selectionContext.d.ts +31 -0
  30. package/dist/canvas/selection/selectionContext.js +12 -0
  31. package/dist/canvas/selection/selectionContext.js.map +1 -0
  32. package/dist/elements/ContainerInfo.d.ts +29 -0
  33. package/dist/elements/ContainerInfo.js +30 -0
  34. package/dist/elements/ContainerInfo.js.map +1 -0
  35. package/dist/elements/EFAudio.d.ts +13 -3
  36. package/dist/elements/EFAudio.js +64 -10
  37. package/dist/elements/EFAudio.js.map +1 -1
  38. package/dist/elements/EFCaptions.d.ts +18 -16
  39. package/dist/elements/EFCaptions.js +110 -19
  40. package/dist/elements/EFCaptions.js.map +1 -1
  41. package/dist/elements/EFImage.d.ts +16 -6
  42. package/dist/elements/EFImage.js +79 -9
  43. package/dist/elements/EFImage.js.map +1 -1
  44. package/dist/elements/EFMedia/AssetIdMediaEngine.js +51 -4
  45. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -1
  46. package/dist/elements/EFMedia/AssetMediaEngine.js +125 -52
  47. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  48. package/dist/elements/EFMedia/BaseMediaEngine.js +24 -6
  49. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  50. package/dist/elements/EFMedia/JitMediaEngine.js +12 -8
  51. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  52. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +46 -7
  53. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -1
  54. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +98 -73
  55. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -1
  56. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +28 -5
  57. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -1
  58. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +18 -6
  59. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -1
  60. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +8 -2
  61. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -1
  62. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +31 -6
  63. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -1
  64. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +28 -5
  65. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -1
  66. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +97 -72
  67. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -1
  68. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -1
  69. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  70. package/dist/elements/EFMedia/shared/BufferUtils.js +1 -1
  71. package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -1
  72. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +25 -14
  73. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
  74. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +47 -16
  75. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -1
  76. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +37 -19
  77. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
  78. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +65 -21
  79. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  80. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +8 -3
  81. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -1
  82. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +32 -9
  83. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -1
  84. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +33 -10
  85. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -1
  86. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +23 -8
  87. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -1
  88. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +34 -10
  89. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -1
  90. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +31 -8
  91. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -1
  92. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +31 -114
  93. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -1
  94. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +44 -8
  95. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -1
  96. package/dist/elements/EFMedia.d.ts +18 -7
  97. package/dist/elements/EFMedia.js +23 -3
  98. package/dist/elements/EFMedia.js.map +1 -1
  99. package/dist/elements/EFPanZoom.d.ts +96 -0
  100. package/dist/elements/EFPanZoom.js +290 -0
  101. package/dist/elements/EFPanZoom.js.map +1 -0
  102. package/dist/elements/EFSourceMixin.js +7 -6
  103. package/dist/elements/EFSourceMixin.js.map +1 -1
  104. package/dist/elements/EFSurface.d.ts +6 -6
  105. package/dist/elements/EFSurface.js +7 -2
  106. package/dist/elements/EFSurface.js.map +1 -1
  107. package/dist/elements/EFTemporal.d.ts +2 -1
  108. package/dist/elements/EFTemporal.js +192 -71
  109. package/dist/elements/EFTemporal.js.map +1 -1
  110. package/dist/elements/EFText.d.ts +5 -4
  111. package/dist/elements/EFText.js +102 -13
  112. package/dist/elements/EFText.js.map +1 -1
  113. package/dist/elements/EFTextSegment.d.ts +32 -6
  114. package/dist/elements/EFTextSegment.js +53 -15
  115. package/dist/elements/EFTextSegment.js.map +1 -1
  116. package/dist/elements/EFThumbnailStrip.d.ts +129 -56
  117. package/dist/elements/EFThumbnailStrip.js +605 -359
  118. package/dist/elements/EFThumbnailStrip.js.map +1 -1
  119. package/dist/elements/EFTimegroup.d.ts +233 -25
  120. package/dist/elements/EFTimegroup.js +865 -144
  121. package/dist/elements/EFTimegroup.js.map +1 -1
  122. package/dist/elements/EFVideo.d.ts +42 -5
  123. package/dist/elements/EFVideo.js +165 -11
  124. package/dist/elements/EFVideo.js.map +1 -1
  125. package/dist/elements/EFWaveform.d.ts +6 -6
  126. package/dist/elements/EFWaveform.js +2 -1
  127. package/dist/elements/EFWaveform.js.map +1 -1
  128. package/dist/elements/ElementPositionInfo.d.ts +35 -0
  129. package/dist/elements/ElementPositionInfo.js +49 -0
  130. package/dist/elements/ElementPositionInfo.js.map +1 -0
  131. package/dist/elements/FetchMixin.js +16 -1
  132. package/dist/elements/FetchMixin.js.map +1 -1
  133. package/dist/elements/SessionThumbnailCache.js +154 -0
  134. package/dist/elements/SessionThumbnailCache.js.map +1 -0
  135. package/dist/elements/TargetController.js +3 -1
  136. package/dist/elements/TargetController.js.map +1 -1
  137. package/dist/elements/TimegroupController.js +9 -3
  138. package/dist/elements/TimegroupController.js.map +1 -1
  139. package/dist/elements/findRootTemporal.js +30 -0
  140. package/dist/elements/findRootTemporal.js.map +1 -0
  141. package/dist/elements/renderTemporalAudio.js +18 -5
  142. package/dist/elements/renderTemporalAudio.js.map +1 -1
  143. package/dist/elements/updateAnimations.js +171 -28
  144. package/dist/elements/updateAnimations.js.map +1 -1
  145. package/dist/getRenderInfo.d.ts +2 -2
  146. package/dist/gui/ContextMixin.js +4 -2
  147. package/dist/gui/ContextMixin.js.map +1 -1
  148. package/dist/gui/Controllable.js +74 -1
  149. package/dist/gui/Controllable.js.map +1 -1
  150. package/dist/gui/EFActiveRootTemporal.d.ts +50 -0
  151. package/dist/gui/EFActiveRootTemporal.js +94 -0
  152. package/dist/gui/EFActiveRootTemporal.js.map +1 -0
  153. package/dist/gui/EFConfiguration.d.ts +7 -1
  154. package/dist/gui/EFConfiguration.js.map +1 -1
  155. package/dist/gui/EFControls.d.ts +2 -2
  156. package/dist/gui/EFControls.js +109 -13
  157. package/dist/gui/EFControls.js.map +1 -1
  158. package/dist/gui/EFDial.d.ts +4 -4
  159. package/dist/gui/EFFilmstrip.d.ts +11 -214
  160. package/dist/gui/EFFilmstrip.js +53 -1152
  161. package/dist/gui/EFFilmstrip.js.map +1 -1
  162. package/dist/gui/EFFitScale.d.ts +3 -3
  163. package/dist/gui/EFFitScale.js +39 -12
  164. package/dist/gui/EFFitScale.js.map +1 -1
  165. package/dist/gui/EFFocusOverlay.d.ts +4 -4
  166. package/dist/gui/EFOverlayItem.d.ts +48 -0
  167. package/dist/gui/EFOverlayItem.js +97 -0
  168. package/dist/gui/EFOverlayItem.js.map +1 -0
  169. package/dist/gui/EFOverlayLayer.d.ts +70 -0
  170. package/dist/gui/EFOverlayLayer.js +104 -0
  171. package/dist/gui/EFOverlayLayer.js.map +1 -0
  172. package/dist/gui/EFPause.d.ts +4 -4
  173. package/dist/gui/EFPlay.d.ts +4 -4
  174. package/dist/gui/EFResizableBox.d.ts +12 -16
  175. package/dist/gui/EFResizableBox.js +109 -451
  176. package/dist/gui/EFResizableBox.js.map +1 -1
  177. package/dist/gui/EFScrubber.d.ts +30 -5
  178. package/dist/gui/EFScrubber.js +224 -31
  179. package/dist/gui/EFScrubber.js.map +1 -1
  180. package/dist/gui/EFTimeDisplay.d.ts +4 -4
  181. package/dist/gui/EFTimeDisplay.js +4 -1
  182. package/dist/gui/EFTimeDisplay.js.map +1 -1
  183. package/dist/gui/EFTimelineRuler.d.ts +71 -0
  184. package/dist/gui/EFTimelineRuler.js +320 -0
  185. package/dist/gui/EFTimelineRuler.js.map +1 -0
  186. package/dist/gui/EFToggleLoop.d.ts +4 -4
  187. package/dist/gui/EFTogglePlay.d.ts +4 -4
  188. package/dist/gui/EFTransformHandles.d.ts +91 -0
  189. package/dist/gui/EFTransformHandles.js +393 -0
  190. package/dist/gui/EFTransformHandles.js.map +1 -0
  191. package/dist/gui/EFWorkbench.d.ts +178 -0
  192. package/dist/gui/EFWorkbench.js +2067 -22
  193. package/dist/gui/EFWorkbench.js.map +1 -1
  194. package/dist/gui/FitScaleHelpers.d.ts +31 -0
  195. package/dist/gui/FitScaleHelpers.js +41 -0
  196. package/dist/gui/FitScaleHelpers.js.map +1 -0
  197. package/dist/gui/PlaybackController.d.ts +2 -1
  198. package/dist/gui/PlaybackController.js +46 -15
  199. package/dist/gui/PlaybackController.js.map +1 -1
  200. package/dist/gui/TWMixin.js +1 -1
  201. package/dist/gui/TWMixin.js.map +1 -1
  202. package/dist/gui/hierarchy/EFHierarchy.d.ts +65 -0
  203. package/dist/gui/hierarchy/EFHierarchy.js +338 -0
  204. package/dist/gui/hierarchy/EFHierarchy.js.map +1 -0
  205. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +118 -0
  206. package/dist/gui/hierarchy/EFHierarchyItem.js +551 -0
  207. package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -0
  208. package/dist/gui/hierarchy/hierarchyContext.d.ts +38 -0
  209. package/dist/gui/hierarchy/hierarchyContext.js +8 -0
  210. package/dist/gui/hierarchy/hierarchyContext.js.map +1 -0
  211. package/dist/gui/icons.js +34 -0
  212. package/dist/gui/icons.js.map +1 -0
  213. package/dist/gui/panZoomTransformContext.js +12 -0
  214. package/dist/gui/panZoomTransformContext.js.map +1 -0
  215. package/dist/gui/previewSettingsContext.js +12 -0
  216. package/dist/gui/previewSettingsContext.js.map +1 -0
  217. package/dist/gui/timeline/EFTimeline.d.ts +270 -0
  218. package/dist/gui/timeline/EFTimeline.js +1369 -0
  219. package/dist/gui/timeline/EFTimeline.js.map +1 -0
  220. package/dist/gui/timeline/EFTimelineRow.js +374 -0
  221. package/dist/gui/timeline/EFTimelineRow.js.map +1 -0
  222. package/dist/gui/timeline/TrimHandles.d.ts +36 -0
  223. package/dist/gui/timeline/TrimHandles.js +204 -0
  224. package/dist/gui/timeline/TrimHandles.js.map +1 -0
  225. package/dist/gui/timeline/flattenHierarchy.js +31 -0
  226. package/dist/gui/timeline/flattenHierarchy.js.map +1 -0
  227. package/dist/gui/timeline/timelineStateContext.d.ts +26 -0
  228. package/dist/gui/timeline/timelineStateContext.js +42 -0
  229. package/dist/gui/timeline/timelineStateContext.js.map +1 -0
  230. package/dist/gui/timeline/tracks/AudioTrack.js +264 -0
  231. package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -0
  232. package/dist/gui/timeline/tracks/CaptionsTrack.js +595 -0
  233. package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -0
  234. package/dist/gui/timeline/tracks/HTMLTrack.js +19 -0
  235. package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -0
  236. package/dist/gui/timeline/tracks/ImageTrack.js +53 -0
  237. package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -0
  238. package/dist/gui/timeline/tracks/TextTrack.js +250 -0
  239. package/dist/gui/timeline/tracks/TextTrack.js.map +1 -0
  240. package/dist/gui/timeline/tracks/TimegroupTrack.js +143 -0
  241. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -0
  242. package/dist/gui/timeline/tracks/TrackItem.js +269 -0
  243. package/dist/gui/timeline/tracks/TrackItem.js.map +1 -0
  244. package/dist/gui/timeline/tracks/VideoTrack.js +265 -0
  245. package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -0
  246. package/dist/gui/timeline/tracks/WaveformTrack.js +19 -0
  247. package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -0
  248. package/dist/gui/timeline/tracks/ensureTrackItemInit.js +1 -0
  249. package/dist/gui/timeline/tracks/preloadTracks.js +9 -0
  250. package/dist/gui/timeline/tracks/renderTrackChildren.js +119 -0
  251. package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -0
  252. package/dist/gui/timeline/tracks/waveformUtils.js +80 -0
  253. package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -0
  254. package/dist/gui/transformCalculations.js +217 -0
  255. package/dist/gui/transformCalculations.js.map +1 -0
  256. package/dist/gui/transformUtils.d.ts +37 -0
  257. package/dist/gui/transformUtils.js +77 -0
  258. package/dist/gui/transformUtils.js.map +1 -0
  259. package/dist/gui/tree/EFTree.d.ts +59 -0
  260. package/dist/gui/tree/EFTree.js +174 -0
  261. package/dist/gui/tree/EFTree.js.map +1 -0
  262. package/dist/gui/tree/EFTreeItem.d.ts +38 -0
  263. package/dist/gui/tree/EFTreeItem.js +146 -0
  264. package/dist/gui/tree/EFTreeItem.js.map +1 -0
  265. package/dist/gui/tree/treeContext.d.ts +60 -0
  266. package/dist/gui/tree/treeContext.js +23 -0
  267. package/dist/gui/tree/treeContext.js.map +1 -0
  268. package/dist/index.d.ts +32 -8
  269. package/dist/index.js +30 -6
  270. package/dist/index.js.map +1 -1
  271. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +688 -0
  272. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
  273. package/dist/node_modules/react/cjs/react.development.js +1521 -0
  274. package/dist/node_modules/react/cjs/react.development.js.map +1 -0
  275. package/dist/node_modules/react/index.js +13 -0
  276. package/dist/node_modules/react/index.js.map +1 -0
  277. package/dist/node_modules/react/jsx-runtime.js +13 -0
  278. package/dist/node_modules/react/jsx-runtime.js.map +1 -0
  279. package/dist/preview/AdaptiveResolutionTracker.js +228 -0
  280. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -0
  281. package/dist/preview/RenderProfiler.js +135 -0
  282. package/dist/preview/RenderProfiler.js.map +1 -0
  283. package/dist/preview/previewSettings.js +131 -0
  284. package/dist/preview/previewSettings.js.map +1 -0
  285. package/dist/preview/previewTypes.js +64 -0
  286. package/dist/preview/previewTypes.js.map +1 -0
  287. package/dist/preview/renderTimegroupPreview.js +656 -0
  288. package/dist/preview/renderTimegroupPreview.js.map +1 -0
  289. package/dist/preview/renderTimegroupToCanvas.d.ts +37 -0
  290. package/dist/preview/renderTimegroupToCanvas.js +833 -0
  291. package/dist/preview/renderTimegroupToCanvas.js.map +1 -0
  292. package/dist/preview/renderTimegroupToVideo.d.ts +39 -0
  293. package/dist/preview/renderTimegroupToVideo.js +274 -0
  294. package/dist/preview/renderTimegroupToVideo.js.map +1 -0
  295. package/dist/preview/renderers.js +16 -0
  296. package/dist/preview/renderers.js.map +1 -0
  297. package/dist/preview/statsTrackingStrategy.js +201 -0
  298. package/dist/preview/statsTrackingStrategy.js.map +1 -0
  299. package/dist/preview/thumbnailCacheSettings.js +52 -0
  300. package/dist/preview/thumbnailCacheSettings.js.map +1 -0
  301. package/dist/preview/workers/WorkerPool.js +178 -0
  302. package/dist/preview/workers/WorkerPool.js.map +1 -0
  303. package/dist/preview/workers/encoderWorkerInline.js +103 -0
  304. package/dist/preview/workers/encoderWorkerInline.js.map +1 -0
  305. package/dist/sandbox/PlaybackControls.js +10 -0
  306. package/dist/sandbox/PlaybackControls.js.map +1 -0
  307. package/dist/sandbox/ScenarioRunner.js +1 -0
  308. package/dist/sandbox/index.js +2 -0
  309. package/dist/style.css +71 -67
  310. package/dist/transcoding/types/index.d.ts +2 -1
  311. package/dist/transcoding/utils/UrlGenerator.d.ts +6 -1
  312. package/dist/transcoding/utils/UrlGenerator.js +12 -3
  313. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  314. package/dist/utils/LRUCache.js +1 -375
  315. package/dist/utils/LRUCache.js.map +1 -1
  316. package/dist/utils/frameTime.js +14 -0
  317. package/dist/utils/frameTime.js.map +1 -0
  318. package/package.json +3 -3
  319. package/test/profilingPlugin.ts +223 -0
  320. package/test/recordReplayProxyPlugin.js +22 -27
  321. package/test/thumbnail-performance-test.html +116 -0
  322. package/test/visualRegressionUtils.ts +286 -0
  323. package/types.json +1 -1
  324. package/dist/elements/TimegroupController.d.ts +0 -18
  325. package/dist/msToTimeCode.js +0 -17
  326. package/dist/msToTimeCode.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"makeAudioInputTask.js","names":[],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioInputTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { EFMedia } from \"../../EFMedia\";\nimport { BufferedSeekingInput } from \"../BufferedSeekingInput\";\nimport type { InputTask } from \"../shared/MediaTaskUtils\";\n\nexport const makeAudioInputTask = (host: EFMedia): InputTask => {\n return new Task<\n readonly [ArrayBuffer | undefined, ArrayBuffer | undefined],\n BufferedSeekingInput | undefined\n >(host, {\n args: () =>\n [\n host.audioInitSegmentFetchTask.value,\n host.audioSegmentFetchTask.value,\n ] as const,\n onError: (error) => {\n console.error(\"audioInputTask error\", error);\n },\n onComplete: (_value) => {},\n task: async (_, { signal }) => {\n const mediaEngine = await host.mediaEngineTask.taskComplete;\n if (signal.aborted) return undefined;\n\n const audioRendition = mediaEngine?.audioRendition;\n\n // Return undefined if no audio rendition available (video-only asset)\n if (!audioRendition) {\n return undefined;\n }\n\n const initSegment = await host.audioInitSegmentFetchTask.taskComplete;\n if (signal.aborted) return undefined;\n\n const segment = await host.audioSegmentFetchTask.taskComplete;\n if (signal.aborted) return undefined;\n\n if (!initSegment || !segment) {\n return undefined;\n }\n\n const startTimeOffsetMs = audioRendition.startTimeOffsetMs;\n\n const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();\n if (signal.aborted) return undefined;\n\n return new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs,\n });\n },\n });\n};\n"],"mappings":";;;;;AAKA,MAAa,sBAAsB,SAA6B;AAC9D,QAAO,IAAI,KAGT,MAAM;EACN,YACE,CACE,KAAK,0BAA0B,OAC/B,KAAK,sBAAsB,MAC5B;EACH,UAAU,UAAU;AAClB,WAAQ,MAAM,wBAAwB,MAAM;;EAE9C,aAAa,WAAW;EACxB,MAAM,OAAO,GAAG,EAAE,aAAa;GAC7B,MAAM,cAAc,MAAM,KAAK,gBAAgB;AAC/C,OAAI,OAAO,QAAS,QAAO;GAE3B,MAAM,iBAAiB,aAAa;AAGpC,OAAI,CAAC,eACH;GAGF,MAAM,cAAc,MAAM,KAAK,0BAA0B;AACzD,OAAI,OAAO,QAAS,QAAO;GAE3B,MAAM,UAAU,MAAM,KAAK,sBAAsB;AACjD,OAAI,OAAO,QAAS,QAAO;AAE3B,OAAI,CAAC,eAAe,CAAC,QACnB;GAGF,MAAM,oBAAoB,eAAe;GAEzC,MAAM,cAAc,MAAM,IAAI,KAAK,CAAC,aAAa,QAAQ,CAAC,CAAC,aAAa;AACxE,OAAI,OAAO,QAAS,QAAO;AAE3B,UAAO,IAAI,qBAAqB,aAAa;IAC3C,iBAAiB,QAAQ;IACzB,iBAAiB,QAAQ;IACzB;IACD,CAAC;;EAEL,CAAC"}
1
+ {"version":3,"file":"makeAudioInputTask.js","names":["task: InputTask"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioInputTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { EFMedia } from \"../../EFMedia\";\nimport { BufferedSeekingInput } from \"../BufferedSeekingInput\";\nimport type { InputTask } from \"../shared/MediaTaskUtils\";\n\nexport const makeAudioInputTask = (host: EFMedia): InputTask => {\n // Capture task reference for use in onError\n let task: InputTask;\n\n task = new Task<\n readonly [ArrayBuffer | undefined, ArrayBuffer | undefined],\n BufferedSeekingInput | undefined\n >(host, {\n args: () =>\n [\n host.audioInitSegmentFetchTask.value,\n host.audioSegmentFetchTask.value,\n ] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete BEFORE the promise is rejected.\n // This prevents unhandled rejection when hostUpdate() triggers _performTask() without awaiting.\n task.taskComplete.catch(() => {});\n \n // Don't log AbortError - these are intentional request cancellations\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 // Don't log errors when there's no valid media source, file not found, or fetch failures - these are expected\n if (isAbortError || (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(\"Failed to fetch\")\n ))) {\n return;\n }\n console.error(\"audioInputTask error\", error);\n },\n onComplete: (_value) => {},\n task: async (_, { signal }) => {\n // Check if media engine task has errored (no valid source) before attempting to use it\n if (host.mediaEngineTask.error) {\n return undefined;\n }\n \n let mediaEngine;\n try {\n mediaEngine = await host.mediaEngineTask.taskComplete;\n } catch (error) {\n // If media engine task failed (no valid source), return undefined silently\n if (error instanceof Error && error.message === \"No valid media source\") {\n return undefined;\n }\n // Re-throw unexpected errors\n throw error;\n }\n \n // Check for abort after awaiting media engine\n signal?.throwIfAborted();\n\n // If media engine task completed but returned undefined/null, no valid source\n if (!mediaEngine) {\n return undefined;\n }\n\n const audioRendition = mediaEngine?.audioRendition;\n\n // Return undefined if no audio rendition available (video-only asset)\n if (!audioRendition) {\n return undefined;\n }\n\n const initSegment = await host.audioInitSegmentFetchTask.taskComplete;\n \n // Check for abort after awaiting init segment\n signal?.throwIfAborted();\n\n const segment = await host.audioSegmentFetchTask.taskComplete;\n \n // Check for abort after awaiting segment\n signal?.throwIfAborted();\n\n if (!initSegment || !segment) {\n return undefined;\n }\n\n const startTimeOffsetMs = audioRendition.startTimeOffsetMs;\n\n const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();\n \n // Check for abort after expensive arrayBuffer operation\n signal?.throwIfAborted();\n\n return new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs,\n });\n },\n });\n\n return task;\n};\n"],"mappings":";;;;;AAKA,MAAa,sBAAsB,SAA6B;CAE9D,IAAIA;AAEJ,QAAO,IAAI,KAGT,MAAM;EACN,YACE,CACE,KAAK,0BAA0B,OAC/B,KAAK,sBAAsB,MAC5B;EACH,UAAU,UAAU;AAGlB,QAAK,aAAa,YAAY,GAAG;AAYjC,OARE,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UACf,MAAM,SAAS,gBACf,MAAM,QAAQ,SAAS,oBAAoB,IAC3C,MAAM,QAAQ,SAAS,6BAA6B,KAInC,iBAAiB,UACpC,MAAM,YAAY,2BAClB,MAAM,QAAQ,SAAS,iBAAiB,IACxC,MAAM,QAAQ,SAAS,oBAAoB,IAC3C,MAAM,QAAQ,SAAS,kBAAkB,EAEzC;AAEF,WAAQ,MAAM,wBAAwB,MAAM;;EAE9C,aAAa,WAAW;EACxB,MAAM,OAAO,GAAG,EAAE,aAAa;AAE7B,OAAI,KAAK,gBAAgB,MACvB;GAGF,IAAI;AACJ,OAAI;AACF,kBAAc,MAAM,KAAK,gBAAgB;YAClC,OAAO;AAEd,QAAI,iBAAiB,SAAS,MAAM,YAAY,wBAC9C;AAGF,UAAM;;AAIR,WAAQ,gBAAgB;AAGxB,OAAI,CAAC,YACH;GAGF,MAAM,iBAAiB,aAAa;AAGpC,OAAI,CAAC,eACH;GAGF,MAAM,cAAc,MAAM,KAAK,0BAA0B;AAGzD,WAAQ,gBAAgB;GAExB,MAAM,UAAU,MAAM,KAAK,sBAAsB;AAGjD,WAAQ,gBAAgB;AAExB,OAAI,CAAC,eAAe,CAAC,QACnB;GAGF,MAAM,oBAAoB,eAAe;GAEzC,MAAM,cAAc,MAAM,IAAI,KAAK,CAAC,aAAa,QAAQ,CAAC,CAAC,aAAa;AAGxE,WAAQ,gBAAgB;AAExB,UAAO,IAAI,qBAAqB,aAAa;IAC3C,iBAAiB,QAAQ;IACzB,iBAAiB,QAAQ;IACzB;IACD,CAAC;;EAEL,CAAC;AAEF,QAAO"}
@@ -3,9 +3,12 @@ import { Task } from "@lit/task";
3
3
 
4
4
  //#region src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts
5
5
  const makeAudioSeekTask = (host) => {
6
- return new Task(host, {
6
+ let task;
7
+ task = new Task(host, {
7
8
  args: () => [host.desiredSeekTimeMs, host.audioInputTask.value],
8
9
  onError: (error) => {
10
+ task.taskComplete.catch(() => {});
11
+ 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;
9
12
  if (error instanceof IgnorableError) {
10
13
  console.info("audioSeekTask aborted");
11
14
  return;
@@ -15,8 +18,11 @@ const makeAudioSeekTask = (host) => {
15
18
  else console.error("audioSeekTask unknown error", error);
16
19
  },
17
20
  onComplete: (_value) => {},
18
- task: async () => {}
21
+ task: async ([_desiredSeekTimeMs, _audioInput], { signal }) => {
22
+ signal?.throwIfAborted();
23
+ }
19
24
  });
25
+ return task;
20
26
  };
21
27
 
22
28
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"makeAudioSeekTask.js","names":[],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { VideoSample } from \"mediabunny\";\nimport { type EFMedia, IgnorableError } from \"../../EFMedia\";\nimport type { BufferedSeekingInput } from \"../BufferedSeekingInput\";\n\ntype AudioSeekTask = Task<\n readonly [number, BufferedSeekingInput | undefined],\n VideoSample | undefined\n>;\nexport const makeAudioSeekTask = (host: EFMedia): AudioSeekTask => {\n return new Task(host, {\n args: () => [host.desiredSeekTimeMs, host.audioInputTask.value] as const,\n onError: (error) => {\n if (error instanceof IgnorableError) {\n console.info(\"audioSeekTask aborted\");\n return;\n }\n if (error instanceof DOMException) {\n console.error(\n `audioSeekTask error: ${error.message} ${error.name} ${error.code}`,\n );\n } else if (error instanceof Error) {\n console.error(`audioSeekTask error ${error.name}: ${error.message}`);\n } else {\n console.error(\"audioSeekTask unknown error\", error);\n }\n },\n onComplete: (_value) => {},\n task: async (): Promise<VideoSample | undefined> => {\n return undefined;\n // TODO: validate that the audio seek task is not actually used to render any audio\n // CRITICAL FIX: Use the targetSeekTimeMs from args, not host.desiredSeekTimeMs\n // This ensures we use the same seek time that the segment loading tasks used\n\n // await host.audioSegmentIdTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // await host.audioSegmentFetchTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // await host.audioInitSegmentFetchTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // const audioInput = await host.audioInputTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // if (!audioInput) {\n // throw new Error(\"Audio input is not available\");\n // }\n // const audioTrack = await audioInput.getFirstAudioTrack();\n // if (!audioTrack) {\n // throw new Error(\"Audio track is not available\");\n // }\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // const sample = (await audioInput.seek(\n // audioTrack.id,\n // targetSeekTimeMs, // Use the captured value, not host.desiredSeekTimeMs\n // )) as unknown as VideoSample | undefined;\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // // If seek returned undefined, it was aborted - don't throw\n // if (sample === undefined && signal.aborted) {\n // return undefined;\n // }\n\n // // If we got undefined but weren't aborted, that's an actual error\n // if (sample === undefined) {\n // throw new Error(\"Audio seek failed to find sample\");\n // }\n\n // return sample;\n },\n });\n};\n"],"mappings":";;;;AASA,MAAa,qBAAqB,SAAiC;AACjE,QAAO,IAAI,KAAK,MAAM;EACpB,YAAY,CAAC,KAAK,mBAAmB,KAAK,eAAe,MAAM;EAC/D,UAAU,UAAU;AAClB,OAAI,iBAAiB,gBAAgB;AACnC,YAAQ,KAAK,wBAAwB;AACrC;;AAEF,OAAI,iBAAiB,aACnB,SAAQ,MACN,wBAAwB,MAAM,QAAQ,GAAG,MAAM,KAAK,GAAG,MAAM,OAC9D;YACQ,iBAAiB,MAC1B,SAAQ,MAAM,uBAAuB,MAAM,KAAK,IAAI,MAAM,UAAU;OAEpE,SAAQ,MAAM,+BAA+B,MAAM;;EAGvD,aAAa,WAAW;EACxB,MAAM,YAA8C;EA0CrD,CAAC"}
1
+ {"version":3,"file":"makeAudioSeekTask.js","names":["task: AudioSeekTask"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { VideoSample } from \"mediabunny\";\nimport { type EFMedia, IgnorableError } from \"../../EFMedia\";\nimport type { BufferedSeekingInput } from \"../BufferedSeekingInput\";\n\ntype AudioSeekTask = Task<\n readonly [number, BufferedSeekingInput | undefined],\n VideoSample | undefined\n>;\nexport const makeAudioSeekTask = (host: EFMedia): AudioSeekTask => {\n // Capture task reference for use in onError\n let task: AudioSeekTask;\n\n task = new Task(host, {\n args: () => [host.desiredSeekTimeMs, host.audioInputTask.value] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete BEFORE the promise is rejected.\n // This prevents unhandled rejection when hostUpdate() triggers _performTask() without awaiting.\n task.taskComplete.catch(() => {});\n \n // Don't log AbortErrors - these are expected when tasks are cancelled\n const isAbortError = \n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message?.includes(\"signal is aborted\") ||\n error.message?.includes(\"The user aborted a request\")\n ));\n \n if (isAbortError) {\n return;\n }\n \n if (error instanceof IgnorableError) {\n console.info(\"audioSeekTask aborted\");\n return;\n }\n if (error instanceof DOMException) {\n console.error(\n `audioSeekTask error: ${error.message} ${error.name} ${error.code}`,\n );\n } else if (error instanceof Error) {\n console.error(`audioSeekTask error ${error.name}: ${error.message}`);\n } else {\n console.error(\"audioSeekTask unknown error\", error);\n }\n },\n onComplete: (_value) => {},\n task: async ([_desiredSeekTimeMs, _audioInput], { signal }): Promise<VideoSample | undefined> => {\n // Check abort before starting work\n signal?.throwIfAborted();\n \n // VALIDATED: This task is NOT used for audio rendering.\n // It is only awaited in EFAudio.frameTask for synchronization purposes.\n // The actual audio rendering pipeline uses fetchAudioSpanningTime() which:\n // - Uses mediaEngineTask.taskComplete\n // - Uses audioInitSegmentFetchTask.taskComplete\n // - Uses audioSegmentFetchTask (via MediaEngine.fetchMediaSegment)\n // - Does NOT use audioSeekTask.value or result\n // \n // This task exists to ensure proper sequencing of audio-related tasks\n // but its return value is intentionally undefined and never used.\n return undefined;\n \n // Previous implementation (commented out) would have returned a VideoSample,\n // but that was never actually used in the rendering pipeline.\n // CRITICAL FIX: Use the targetSeekTimeMs from args, not host.desiredSeekTimeMs\n // This ensures we use the same seek time that the segment loading tasks used\n\n // await host.audioSegmentIdTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // await host.audioSegmentFetchTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // await host.audioInitSegmentFetchTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // const audioInput = await host.audioInputTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // if (!audioInput) {\n // throw new Error(\"Audio input is not available\");\n // }\n // const audioTrack = await audioInput.getFirstAudioTrack();\n // if (!audioTrack) {\n // throw new Error(\"Audio track is not available\");\n // }\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // const sample = (await audioInput.seek(\n // audioTrack.id,\n // targetSeekTimeMs, // Use the captured value, not host.desiredSeekTimeMs\n // )) as unknown as VideoSample | undefined;\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // // If seek returned undefined, it was aborted - don't throw\n // if (sample === undefined && signal.aborted) {\n // return undefined;\n // }\n\n // // If we got undefined but weren't aborted, that's an actual error\n // if (sample === undefined) {\n // throw new Error(\"Audio seek failed to find sample\");\n // }\n\n // return sample;\n },\n });\n\n return task;\n};\n"],"mappings":";;;;AASA,MAAa,qBAAqB,SAAiC;CAEjE,IAAIA;AAEJ,QAAO,IAAI,KAAK,MAAM;EACpB,YAAY,CAAC,KAAK,mBAAmB,KAAK,eAAe,MAAM;EAC/D,UAAU,UAAU;AAGlB,QAAK,aAAa,YAAY,GAAG;AAWjC,OAPG,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UAChB,MAAM,SAAS,gBACf,MAAM,SAAS,SAAS,oBAAoB,IAC5C,MAAM,SAAS,SAAS,6BAA6B,EAIvD;AAGF,OAAI,iBAAiB,gBAAgB;AACnC,YAAQ,KAAK,wBAAwB;AACrC;;AAEF,OAAI,iBAAiB,aACnB,SAAQ,MACN,wBAAwB,MAAM,QAAQ,GAAG,MAAM,KAAK,GAAG,MAAM,OAC9D;YACQ,iBAAiB,MAC1B,SAAQ,MAAM,uBAAuB,MAAM,KAAK,IAAI,MAAM,UAAU;OAEpE,SAAQ,MAAM,+BAA+B,MAAM;;EAGvD,aAAa,WAAW;EACxB,MAAM,OAAO,CAAC,oBAAoB,cAAc,EAAE,aAA+C;AAE/F,WAAQ,gBAAgB;;EAuD3B,CAAC;AAEF,QAAO"}
@@ -1,22 +1,47 @@
1
+ import { AssetMediaEngine } from "../AssetMediaEngine.js";
1
2
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
3
  import { Task } from "@lit/task";
3
4
 
4
5
  //#region src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts
5
6
  const makeAudioSegmentFetchTask = (host) => {
6
- return new Task(host, {
7
+ let task;
8
+ task = new Task(host, {
7
9
  args: () => [host.mediaEngineTask.value, host.audioSegmentIdTask.value],
8
10
  onError: (error) => {
11
+ task.taskComplete.catch(() => {});
12
+ 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")) || 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("Failed to fetch"))) return;
9
13
  console.error("audioSegmentFetchTask error", error);
10
14
  },
11
15
  onComplete: (_value) => {},
12
- task: async (_, { signal }) => {
13
- const mediaEngine = await getLatestMediaEngine(host, signal);
14
- const segmentId = await host.audioSegmentIdTask.taskComplete;
16
+ task: async ([mediaEngineValue], { signal }) => {
17
+ if (host.mediaEngineTask.error || !mediaEngineValue) return;
18
+ let mediaEngine;
19
+ try {
20
+ mediaEngine = await getLatestMediaEngine(host, signal);
21
+ } catch (error) {
22
+ if (error instanceof Error && error.message === "No valid media source") return;
23
+ throw error;
24
+ }
25
+ if (!mediaEngine) return;
15
26
  const audioRendition = mediaEngine.getAudioRendition();
16
- if (!audioRendition || segmentId === void 0) return;
17
- return mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);
27
+ if (!audioRendition) return;
28
+ const segmentId = await host.audioSegmentIdTask.taskComplete;
29
+ signal?.throwIfAborted();
30
+ if (segmentId === void 0) return;
31
+ if (mediaEngine instanceof AssetMediaEngine) {
32
+ const trackData = mediaEngine.data?.[audioRendition.trackId];
33
+ if (!trackData?.segments || segmentId >= trackData.segments.length) return;
34
+ }
35
+ try {
36
+ return await mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);
37
+ } catch (error) {
38
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
39
+ 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("File not found"))) return;
40
+ throw error;
41
+ }
18
42
  }
19
43
  });
44
+ return task;
20
45
  };
21
46
 
22
47
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"makeAudioSegmentFetchTask.js","names":[],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { MediaEngine } from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\nexport const makeAudioSegmentFetchTask = (\n host: EFMedia,\n): Task<\n readonly [MediaEngine | undefined, number | undefined],\n ArrayBuffer | undefined\n> => {\n return new Task(host, {\n args: () =>\n [host.mediaEngineTask.value, host.audioSegmentIdTask.value] as const,\n onError: (error) => {\n console.error(\"audioSegmentFetchTask error\", error);\n },\n onComplete: (_value) => {},\n task: async (_, { signal }) => {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n const segmentId = await host.audioSegmentIdTask.taskComplete;\n const audioRendition = mediaEngine.getAudioRendition();\n\n // Return undefined if no audio rendition or segment ID available (video-only asset)\n if (!audioRendition || segmentId === undefined) {\n return undefined;\n }\n\n return mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);\n },\n });\n};\n"],"mappings":";;;;AAKA,MAAa,6BACX,SAIG;AACH,QAAO,IAAI,KAAK,MAAM;EACpB,YACE,CAAC,KAAK,gBAAgB,OAAO,KAAK,mBAAmB,MAAM;EAC7D,UAAU,UAAU;AAClB,WAAQ,MAAM,+BAA+B,MAAM;;EAErD,aAAa,WAAW;EACxB,MAAM,OAAO,GAAG,EAAE,aAAa;GAC7B,MAAM,cAAc,MAAM,qBAAqB,MAAM,OAAO;GAC5D,MAAM,YAAY,MAAM,KAAK,mBAAmB;GAChD,MAAM,iBAAiB,YAAY,mBAAmB;AAGtD,OAAI,CAAC,kBAAkB,cAAc,OACnC;AAGF,UAAO,YAAY,kBAAkB,WAAW,gBAAgB,OAAO;;EAE1E,CAAC"}
1
+ {"version":3,"file":"makeAudioSegmentFetchTask.js","names":["task: AudioSegmentFetchTask"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { MediaEngine } from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\nimport { AssetMediaEngine } from \"../AssetMediaEngine\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\ntype AudioSegmentFetchTask = Task<\n readonly [MediaEngine | undefined, number | undefined],\n ArrayBuffer | undefined\n>;\n\nexport const makeAudioSegmentFetchTask = (\n host: EFMedia,\n): AudioSegmentFetchTask => {\n // Capture task reference for use in onError\n let task: AudioSegmentFetchTask;\n\n task = new Task(host, {\n args: () =>\n [host.mediaEngineTask.value, host.audioSegmentIdTask.value] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete BEFORE the promise is rejected.\n // This prevents unhandled rejection when hostUpdate() triggers _performTask() without awaiting.\n task.taskComplete.catch(() => {});\n \n // Don't log AbortError - these are intentional request cancellations\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 // Don't log errors when there's no valid media source, file not found, or fetch failures - these are expected\n if (isAbortError || (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(\"Failed to fetch\")\n ))) {\n return;\n }\n console.error(\"audioSegmentFetchTask error\", error);\n },\n onComplete: (_value) => {},\n task: async ([mediaEngineValue], { signal }) => {\n // Check if media engine task has errored (no valid source) before attempting to use it\n if (host.mediaEngineTask.error || !mediaEngineValue) {\n return undefined;\n }\n \n let mediaEngine;\n try {\n mediaEngine = await getLatestMediaEngine(host, signal);\n } catch (error) {\n // If media engine task failed (no valid source), return undefined silently\n if (error instanceof Error && error.message === \"No valid media source\") {\n return undefined;\n }\n // Re-throw unexpected errors\n throw error;\n }\n \n // Return undefined if no valid media engine (no valid source)\n if (!mediaEngine) {\n return undefined;\n }\n const audioRendition = mediaEngine.getAudioRendition();\n\n // Return undefined if no audio rendition available (video-only asset)\n // Check this BEFORE awaiting segmentId to avoid unnecessary work\n if (!audioRendition) {\n return undefined;\n }\n\n const segmentId = await host.audioSegmentIdTask.taskComplete;\n \n // Check for abort after awaiting segment ID\n signal?.throwIfAborted();\n\n // Return undefined if no segment ID available\n if (segmentId === undefined) {\n return undefined;\n }\n\n // Check if the segment exists in the media engine data before fetching\n // This prevents fetch errors when segments don't exist in AssetMediaEngine\n if (mediaEngine instanceof AssetMediaEngine) {\n // @ts-expect-error - data is protected but we need to check segment existence\n const trackData = (mediaEngine as any).data?.[audioRendition.trackId];\n if (!trackData?.segments || segmentId >= trackData.segments.length) {\n // Segment doesn't exist in the data - don't fetch\n return undefined;\n }\n }\n\n // Try to fetch the segment, but return undefined if it fails\n // This handles cases where the media engine has metadata but the file doesn't exist\n try {\n return await mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);\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 segment doesn't exist or fetch fails, return undefined gracefully\n // This prevents error propagation when files are missing (test environment)\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(\"File not found\"))\n ) {\n return undefined;\n }\n // Re-throw unexpected errors\n throw error;\n }\n },\n });\n\n return task;\n};\n"],"mappings":";;;;;AAWA,MAAa,6BACX,SAC0B;CAE1B,IAAIA;AAEJ,QAAO,IAAI,KAAK,MAAM;EACpB,YACE,CAAC,KAAK,gBAAgB,OAAO,KAAK,mBAAmB,MAAM;EAC7D,UAAU,UAAU;AAGlB,QAAK,aAAa,YAAY,GAAG;AAYjC,OARE,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UACf,MAAM,SAAS,gBACf,MAAM,QAAQ,SAAS,oBAAoB,IAC3C,MAAM,QAAQ,SAAS,6BAA6B,KAInC,iBAAiB,UACpC,MAAM,YAAY,2BAClB,MAAM,QAAQ,SAAS,iBAAiB,IACxC,MAAM,QAAQ,SAAS,oBAAoB,IAC3C,MAAM,QAAQ,SAAS,kBAAkB,EAEzC;AAEF,WAAQ,MAAM,+BAA+B,MAAM;;EAErD,aAAa,WAAW;EACxB,MAAM,OAAO,CAAC,mBAAmB,EAAE,aAAa;AAE9C,OAAI,KAAK,gBAAgB,SAAS,CAAC,iBACjC;GAGF,IAAI;AACJ,OAAI;AACF,kBAAc,MAAM,qBAAqB,MAAM,OAAO;YAC/C,OAAO;AAEd,QAAI,iBAAiB,SAAS,MAAM,YAAY,wBAC9C;AAGF,UAAM;;AAIR,OAAI,CAAC,YACH;GAEF,MAAM,iBAAiB,YAAY,mBAAmB;AAItD,OAAI,CAAC,eACH;GAGF,MAAM,YAAY,MAAM,KAAK,mBAAmB;AAGhD,WAAQ,gBAAgB;AAGxB,OAAI,cAAc,OAChB;AAKF,OAAI,uBAAuB,kBAAkB;IAE3C,MAAM,YAAa,YAAoB,OAAO,eAAe;AAC7D,QAAI,CAAC,WAAW,YAAY,aAAa,UAAU,SAAS,OAE1D;;AAMJ,OAAI;AACF,WAAO,MAAM,YAAY,kBAAkB,WAAW,gBAAgB,OAAO;YACtE,OAAO;AAEd,QAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAIR,QACE,iBAAiB,UAChB,MAAM,QAAQ,SAAS,0BAA0B,IAChD,MAAM,QAAQ,SAAS,kBAAkB,IACzC,MAAM,QAAQ,SAAS,kBAAkB,IACzC,MAAM,QAAQ,SAAS,iBAAiB,EAE1C;AAGF,UAAM;;;EAGX,CAAC;AAEF,QAAO"}
@@ -1,22 +1,45 @@
1
+ import { AssetMediaEngine } from "../AssetMediaEngine.js";
1
2
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
3
  import { Task } from "@lit/task";
3
4
 
4
5
  //#region src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts
5
6
  const makeAudioSegmentIdTask = (host) => {
6
- return new Task(host, {
7
+ let task;
8
+ task = new Task(host, {
7
9
  args: () => [host.mediaEngineTask.value, host.desiredSeekTimeMs],
8
10
  onError: (error) => {
11
+ task.taskComplete.catch(() => {});
12
+ 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;
13
+ if (error instanceof Error && (error.message === "No valid media source" || error.message.includes("File not found") || error.message.includes("is not valid JSON"))) return;
9
14
  console.error("audioSegmentIdTask error", error);
10
15
  },
11
16
  onComplete: (_value) => {},
12
- task: async ([, targetSeekTimeMs], { signal }) => {
13
- const mediaEngine = await getLatestMediaEngine(host, signal);
14
- signal.throwIfAborted();
17
+ task: async ([mediaEngineValue, targetSeekTimeMs], { signal }) => {
18
+ if (host.mediaEngineTask.error || !mediaEngineValue) return;
19
+ let mediaEngine;
20
+ try {
21
+ mediaEngine = await getLatestMediaEngine(host, signal);
22
+ } catch (error) {
23
+ if (error instanceof Error && error.message === "No valid media source") return;
24
+ throw error;
25
+ }
26
+ if (!mediaEngine) return;
27
+ signal?.throwIfAborted();
15
28
  const audioRendition = mediaEngine.getAudioRendition();
16
29
  if (!audioRendition) return;
17
- return mediaEngine.computeSegmentId(targetSeekTimeMs, audioRendition);
30
+ if (mediaEngine instanceof AssetMediaEngine) {
31
+ const trackData = mediaEngine.data?.[audioRendition.trackId];
32
+ if (!trackData || !trackData.segments || trackData.segments.length === 0) return;
33
+ }
34
+ try {
35
+ return mediaEngine.computeSegmentId(targetSeekTimeMs, audioRendition);
36
+ } catch (error) {
37
+ if (error instanceof Error && (error.message.includes("Track not found") || error.message.includes("Track ID is required"))) return;
38
+ throw error;
39
+ }
18
40
  }
19
41
  });
42
+ return task;
20
43
  };
21
44
 
22
45
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"makeAudioSegmentIdTask.js","names":[],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { MediaEngine } from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\nexport const makeAudioSegmentIdTask = (\n host: EFMedia,\n): Task<readonly [MediaEngine | undefined, number], number | undefined> => {\n return new Task(host, {\n args: () => [host.mediaEngineTask.value, host.desiredSeekTimeMs] as const,\n onError: (error) => {\n console.error(\"audioSegmentIdTask error\", error);\n },\n onComplete: (_value) => {},\n task: async ([, targetSeekTimeMs], { signal }) => {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n signal.throwIfAborted();\n\n const audioRendition = mediaEngine.getAudioRendition();\n\n // Return undefined if no audio rendition available (video-only asset)\n if (!audioRendition) {\n return undefined;\n }\n\n return mediaEngine.computeSegmentId(targetSeekTimeMs, audioRendition);\n },\n });\n};\n"],"mappings":";;;;AAKA,MAAa,0BACX,SACyE;AACzE,QAAO,IAAI,KAAK,MAAM;EACpB,YAAY,CAAC,KAAK,gBAAgB,OAAO,KAAK,kBAAkB;EAChE,UAAU,UAAU;AAClB,WAAQ,MAAM,4BAA4B,MAAM;;EAElD,aAAa,WAAW;EACxB,MAAM,OAAO,GAAG,mBAAmB,EAAE,aAAa;GAChD,MAAM,cAAc,MAAM,qBAAqB,MAAM,OAAO;AAC5D,UAAO,gBAAgB;GAEvB,MAAM,iBAAiB,YAAY,mBAAmB;AAGtD,OAAI,CAAC,eACH;AAGF,UAAO,YAAY,iBAAiB,kBAAkB,eAAe;;EAExE,CAAC"}
1
+ {"version":3,"file":"makeAudioSegmentIdTask.js","names":["task: AudioSegmentIdTask"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { MediaEngine } from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\nimport { AssetMediaEngine } from \"../AssetMediaEngine\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\ntype AudioSegmentIdTask = Task<readonly [MediaEngine | undefined, number], number | undefined>;\n\nexport const makeAudioSegmentIdTask = (\n host: EFMedia,\n): AudioSegmentIdTask => {\n // Capture task reference for use in onError\n let task: AudioSegmentIdTask;\n\n task = new Task(host, {\n args: () => [host.mediaEngineTask.value, host.desiredSeekTimeMs] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete BEFORE the promise is rejected.\n // This prevents unhandled rejection when hostUpdate() triggers _performTask() without awaiting.\n task.taskComplete.catch(() => {});\n \n // Don't log AbortErrors - these are expected when tasks are cancelled\n const isAbortError = \n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message?.includes(\"signal is aborted\") ||\n error.message?.includes(\"The user aborted a request\")\n ));\n \n if (isAbortError) {\n return;\n }\n \n // Don't log errors when there's no valid media source or file not found - 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 )) {\n return;\n }\n console.error(\"audioSegmentIdTask error\", error);\n },\n onComplete: (_value) => {},\n task: async ([mediaEngineValue, targetSeekTimeMs], { signal }) => {\n // Check if media engine task has errored (no valid source) before attempting to use it\n if (host.mediaEngineTask.error || !mediaEngineValue) {\n return undefined;\n }\n \n let mediaEngine;\n try {\n mediaEngine = await getLatestMediaEngine(host, signal);\n } catch (error) {\n // If media engine task failed (no valid source), return undefined silently\n if (error instanceof Error && error.message === \"No valid media source\") {\n return undefined;\n }\n // Re-throw unexpected errors\n throw error;\n }\n \n // Return undefined if no valid media engine (no valid source)\n if (!mediaEngine) {\n return undefined;\n }\n signal?.throwIfAborted();\n\n const audioRendition = mediaEngine.getAudioRendition();\n\n // Return undefined if no audio rendition available (video-only asset)\n if (!audioRendition) {\n return undefined;\n }\n\n // Check if the track exists in AssetMediaEngine data before computing segment ID\n // This prevents computing segment IDs for tracks that don't exist\n if (mediaEngine instanceof AssetMediaEngine) {\n // @ts-expect-error - data is protected but we need to check track existence\n const trackData = (mediaEngine as any).data?.[audioRendition.trackId];\n if (!trackData || !trackData.segments || trackData.segments.length === 0) {\n // Track doesn't exist or has no segments - don't compute segment ID\n return undefined;\n }\n }\n\n try {\n return mediaEngine.computeSegmentId(targetSeekTimeMs, audioRendition);\n } catch (error) {\n // If track doesn't exist or segment computation fails, return undefined gracefully\n if (\n error instanceof Error &&\n (error.message.includes(\"Track not found\") ||\n error.message.includes(\"Track ID is required\"))\n ) {\n return undefined;\n }\n // Re-throw unexpected errors\n throw error;\n }\n },\n });\n\n return task;\n};\n"],"mappings":";;;;;AAQA,MAAa,0BACX,SACuB;CAEvB,IAAIA;AAEJ,QAAO,IAAI,KAAK,MAAM;EACpB,YAAY,CAAC,KAAK,gBAAgB,OAAO,KAAK,kBAAkB;EAChE,UAAU,UAAU;AAGlB,QAAK,aAAa,YAAY,GAAG;AAWjC,OAPG,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UAChB,MAAM,SAAS,gBACf,MAAM,SAAS,SAAS,oBAAoB,IAC5C,MAAM,SAAS,SAAS,6BAA6B,EAIvD;AAIF,OAAI,iBAAiB,UACnB,MAAM,YAAY,2BAClB,MAAM,QAAQ,SAAS,iBAAiB,IACxC,MAAM,QAAQ,SAAS,oBAAoB,EAE3C;AAEF,WAAQ,MAAM,4BAA4B,MAAM;;EAElD,aAAa,WAAW;EACxB,MAAM,OAAO,CAAC,kBAAkB,mBAAmB,EAAE,aAAa;AAEhE,OAAI,KAAK,gBAAgB,SAAS,CAAC,iBACjC;GAGF,IAAI;AACJ,OAAI;AACF,kBAAc,MAAM,qBAAqB,MAAM,OAAO;YAC/C,OAAO;AAEd,QAAI,iBAAiB,SAAS,MAAM,YAAY,wBAC9C;AAGF,UAAM;;AAIR,OAAI,CAAC,YACH;AAEF,WAAQ,gBAAgB;GAExB,MAAM,iBAAiB,YAAY,mBAAmB;AAGtD,OAAI,CAAC,eACH;AAKF,OAAI,uBAAuB,kBAAkB;IAE3C,MAAM,YAAa,YAAoB,OAAO,eAAe;AAC7D,QAAI,CAAC,aAAa,CAAC,UAAU,YAAY,UAAU,SAAS,WAAW,EAErE;;AAIJ,OAAI;AACF,WAAO,YAAY,iBAAiB,kBAAkB,eAAe;YAC9D,OAAO;AAEd,QACE,iBAAiB,UAChB,MAAM,QAAQ,SAAS,kBAAkB,IACxC,MAAM,QAAQ,SAAS,uBAAuB,EAEhD;AAGF,UAAM;;;EAGX,CAAC;AAEF,QAAO"}
@@ -7,22 +7,20 @@ import { Task } from "@lit/task";
7
7
  const DECAY_WEIGHT = .8;
8
8
  function makeAudioTimeDomainAnalysisTask(element) {
9
9
  const cache = new LRUCache(1e3);
10
- return new Task(element, {
10
+ let task;
11
+ task = new Task(element, {
11
12
  autoRun: EF_INTERACTIVE,
12
13
  onError: (error) => {
14
+ task.taskComplete.catch(() => {});
13
15
  if (error instanceof IgnorableError) {
14
16
  console.info("byteTimeDomainTask skipped: no audio track");
15
17
  return;
16
18
  }
19
+ 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;
20
+ 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;
17
21
  console.error("byteTimeDomainTask error", error);
18
22
  },
19
- args: () => [
20
- element.currentSourceTimeMs,
21
- element.fftSize,
22
- element.fftDecay,
23
- element.fftGain,
24
- element.shouldInterpolateFrequencies
25
- ],
23
+ args: () => [element.currentSourceTimeMs],
26
24
  task: async (_, { signal }) => {
27
25
  if (element.currentSourceTimeMs < 0) return null;
28
26
  const currentTimeMs = element.currentSourceTimeMs;
@@ -36,78 +34,105 @@ function makeAudioTimeDomainAnalysisTask(element) {
36
34
  const preliminaryCacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${fromMs}:${currentTimeMs}`;
37
35
  const cachedData = cache.get(preliminaryCacheKey);
38
36
  if (cachedData) return cachedData;
39
- const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
40
- const audioSpan = await fetchAudioSpan(element, fromMs, toMs, signal);
41
- if (!audioSpan || !audioSpan.blob) {
42
- console.warn("Time domain analysis skipped: no audio data available");
43
- return null;
37
+ if (element.mediaEngineTask.error) return null;
38
+ let mediaEngine;
39
+ try {
40
+ mediaEngine = await element.mediaEngineTask.taskComplete;
41
+ } catch (error) {
42
+ if (error instanceof Error && error.message === "No valid media source") return null;
43
+ throw error;
44
44
  }
45
- const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
46
- const arrayBuffer = await audioSpan.blob.arrayBuffer();
47
- const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
48
- const startOffsetMs = audioSpan.startMs;
49
- const framesData = await Promise.all(Array.from({ length: element.fftDecay }, async (_$1, frameIndex) => {
50
- const frameOffset = frameIndex * (1e3 / 30);
51
- const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
52
- const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
53
- const cachedFrame = cache.get(cacheKey);
54
- if (cachedFrame) return cachedFrame;
55
- let audioContext;
45
+ signal?.throwIfAborted();
46
+ if (!mediaEngine?.audioRendition) return null;
47
+ if (element.audioInputTask.error) return null;
48
+ if (element.audioInputTask.value === void 0) return null;
49
+ const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
50
+ try {
51
+ const audioSpan = await fetchAudioSpan(element, fromMs, toMs, signal);
52
+ if (!audioSpan || !audioSpan.blob) return null;
53
+ if (audioSpan.blob.size < 100) return null;
54
+ const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
55
+ const arrayBuffer = await audioSpan.blob.arrayBuffer();
56
+ signal?.throwIfAborted();
57
+ if (arrayBuffer.byteLength < 100) return null;
58
+ let audioBuffer;
56
59
  try {
57
- audioContext = new OfflineAudioContext(2, 48e3 * (1 / 30), 48e3);
58
- } catch (error) {
59
- throw new Error(`[EFMedia.byteTimeDomainTask] Failed to create OfflineAudioContext(2, ${48e3 * (1 / 30)}, 48000) for frame ${frameIndex} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio time domain analysis.`);
60
+ audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
61
+ signal?.throwIfAborted();
62
+ } catch (decodeError) {
63
+ if (decodeError instanceof Error && decodeError.message.includes("Unable to decode audio data")) return null;
64
+ throw decodeError;
60
65
  }
61
- const source = audioContext.createBufferSource();
62
- source.buffer = audioBuffer;
63
- const analyser = audioContext.createAnalyser();
64
- analyser.fftSize = element.fftSize;
65
- analyser.minDecibels = -90;
66
- analyser.maxDecibels = -20;
67
- const gainNode = audioContext.createGain();
68
- gainNode.gain.value = element.fftGain;
69
- source.connect(gainNode);
70
- gainNode.connect(analyser);
71
- analyser.connect(audioContext.destination);
72
- source.start(0, startTime, 1 / 30);
73
- const dataLength = analyser.fftSize / 2;
74
- try {
75
- await audioContext.startRendering();
76
- const frameData = new Uint8Array(dataLength);
77
- analyser.getByteTimeDomainData(frameData);
78
- const points = new Uint8Array(dataLength);
79
- for (let i = 0; i < dataLength; i++) {
80
- const pointSamples = frameData.slice(i * (frameData.length / dataLength), (i + 1) * (frameData.length / dataLength));
81
- const rms = Math.sqrt(pointSamples.reduce((sum, sample) => {
82
- const normalized = (sample - 128) / 128;
83
- return sum + normalized * normalized;
84
- }, 0) / pointSamples.length);
85
- const avgSign = Math.sign(pointSamples.reduce((sum, sample) => sum + (sample - 128), 0));
86
- points[i] = Math.min(255, Math.round(128 + avgSign * rms * 128));
66
+ const startOffsetMs = audioSpan.startMs;
67
+ const framesData = await Promise.all(Array.from({ length: element.fftDecay }, async (_$1, frameIndex) => {
68
+ const frameOffset = frameIndex * (1e3 / 30);
69
+ const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
70
+ const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
71
+ const cachedFrame = cache.get(cacheKey);
72
+ if (cachedFrame) return cachedFrame;
73
+ let audioContext;
74
+ try {
75
+ audioContext = new OfflineAudioContext(2, 48e3 * (1 / 30), 48e3);
76
+ } catch (error) {
77
+ throw new Error(`[EFMedia.byteTimeDomainTask] Failed to create OfflineAudioContext(2, ${48e3 * (1 / 30)}, 48000) for frame ${frameIndex} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio time domain analysis.`);
78
+ }
79
+ const source = audioContext.createBufferSource();
80
+ source.buffer = audioBuffer;
81
+ const analyser = audioContext.createAnalyser();
82
+ analyser.fftSize = element.fftSize;
83
+ analyser.minDecibels = -90;
84
+ analyser.maxDecibels = -20;
85
+ const gainNode = audioContext.createGain();
86
+ gainNode.gain.value = element.fftGain;
87
+ source.connect(gainNode);
88
+ gainNode.connect(analyser);
89
+ analyser.connect(audioContext.destination);
90
+ source.start(0, startTime, 1 / 30);
91
+ const dataLength = analyser.fftSize / 2;
92
+ try {
93
+ await audioContext.startRendering();
94
+ signal?.throwIfAborted();
95
+ const frameData = new Uint8Array(dataLength);
96
+ analyser.getByteTimeDomainData(frameData);
97
+ const points = new Uint8Array(dataLength);
98
+ for (let i = 0; i < dataLength; i++) {
99
+ const pointSamples = frameData.slice(i * (frameData.length / dataLength), (i + 1) * (frameData.length / dataLength));
100
+ const rms = Math.sqrt(pointSamples.reduce((sum, sample) => {
101
+ const normalized = (sample - 128) / 128;
102
+ return sum + normalized * normalized;
103
+ }, 0) / pointSamples.length);
104
+ const avgSign = Math.sign(pointSamples.reduce((sum, sample) => sum + (sample - 128), 0));
105
+ points[i] = Math.min(255, Math.round(128 + avgSign * rms * 128));
106
+ }
107
+ cache.set(cacheKey, points);
108
+ return points;
109
+ } finally {
110
+ source.disconnect();
111
+ analyser.disconnect();
87
112
  }
88
- cache.set(cacheKey, points);
89
- return points;
90
- } finally {
91
- source.disconnect();
92
- analyser.disconnect();
113
+ }));
114
+ const frameLength = framesData[0]?.length ?? 0;
115
+ const smoothedData = new Uint8Array(frameLength);
116
+ for (let i = 0; i < frameLength; i++) {
117
+ let weightedSum = 0;
118
+ let weightSum = 0;
119
+ framesData.forEach((frame, frameIndex) => {
120
+ const decayWeight = DECAY_WEIGHT ** frameIndex;
121
+ weightedSum += (frame[i] ?? 0) * decayWeight;
122
+ weightSum += decayWeight;
123
+ });
124
+ smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
93
125
  }
94
- }));
95
- const frameLength = framesData[0]?.length ?? 0;
96
- const smoothedData = new Uint8Array(frameLength);
97
- for (let i = 0; i < frameLength; i++) {
98
- let weightedSum = 0;
99
- let weightSum = 0;
100
- framesData.forEach((frame, frameIndex) => {
101
- const decayWeight = DECAY_WEIGHT ** frameIndex;
102
- weightedSum += (frame[i] ?? 0) * decayWeight;
103
- weightSum += decayWeight;
104
- });
105
- smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
126
+ cache.set(preliminaryCacheKey, smoothedData);
127
+ return smoothedData;
128
+ } catch (error) {
129
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
130
+ 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("No segments found"))) return null;
131
+ throw error;
106
132
  }
107
- cache.set(preliminaryCacheKey, smoothedData);
108
- return smoothedData;
109
133
  }
110
134
  });
135
+ return task;
111
136
  }
112
137
 
113
138
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"makeAudioTimeDomainAnalysisTask.js","names":["audioContext: OfflineAudioContext"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\n\nimport { EF_INTERACTIVE } from \"../../../EF_INTERACTIVE.js\";\nimport { LRUCache } from \"../../../utils/LRUCache.js\";\nimport { type EFMedia, IgnorableError } from \"../../EFMedia.js\";\n\n// DECAY_WEIGHT constant - same as original\nconst DECAY_WEIGHT = 0.8;\n\nexport function makeAudioTimeDomainAnalysisTask(element: EFMedia) {\n // Internal cache for this task instance (same as original #byteTimeDomainCache)\n const cache = new LRUCache<string, Uint8Array>(1000);\n\n return new Task(element, {\n autoRun: EF_INTERACTIVE,\n onError: (error) => {\n if (error instanceof IgnorableError) {\n console.info(\"byteTimeDomainTask skipped: no audio track\");\n return;\n }\n console.error(\"byteTimeDomainTask error\", error);\n },\n args: () =>\n [\n element.currentSourceTimeMs,\n element.fftSize,\n element.fftDecay,\n element.fftGain,\n element.shouldInterpolateFrequencies,\n ] as const,\n task: async (_, { signal }) => {\n if (element.currentSourceTimeMs < 0) return null;\n\n const currentTimeMs = element.currentSourceTimeMs;\n\n // Calculate exact audio window needed based on fftDecay and frame timing\n const frameIntervalMs = 1000 / 30; // 33.33ms per frame\n\n // Need audio from earliest frame to current frame\n const earliestFrameMs =\n currentTimeMs - (element.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs; // Include current frame\n const videoDurationMs = element.intrinsicDurationMs || 0;\n const toMs =\n videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n // If the clamping results in an invalid range (seeking beyond the end), skip analysis silently\n if (fromMs >= toMs) {\n return null;\n }\n\n // Check cache early - before expensive audio fetching\n // Use a preliminary cache key that doesn't depend on actual startOffsetMs from audio span\n const preliminaryCacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${fromMs}:${currentTimeMs}`;\n const cachedData = cache.get(preliminaryCacheKey);\n if (cachedData) {\n return cachedData;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } = await import(\n \"../shared/AudioSpanUtils.ts\"\n );\n const audioSpan = await fetchAudioSpan(element, fromMs, toMs, signal);\n\n if (!audioSpan || !audioSpan.blob) {\n console.warn(\"Time domain analysis skipped: no audio data available\");\n return null;\n }\n\n // Decode the real audio data\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n\n // Use actual startOffset from audioSpan (relative to requested time)\n const startOffsetMs = audioSpan.startMs;\n\n // Process multiple frames with decay, similar to the reference code\n const framesData = await Promise.all(\n Array.from({ length: element.fftDecay }, async (_, frameIndex) => {\n const frameOffset = frameIndex * (1000 / 30);\n const startTime = Math.max(\n 0,\n (currentTimeMs - frameOffset - startOffsetMs) / 1000,\n );\n\n const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;\n const cachedFrame = cache.get(cacheKey);\n if (cachedFrame) {\n return cachedFrame;\n }\n\n let audioContext: OfflineAudioContext;\n try {\n audioContext = new OfflineAudioContext(2, 48000 * (1 / 30), 48000);\n } catch (error) {\n throw new Error(\n `[EFMedia.byteTimeDomainTask] Failed to create OfflineAudioContext(2, ${48000 * (1 / 30)}, 48000) for frame ${frameIndex} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio time domain analysis.`,\n );\n }\n\n const source = audioContext.createBufferSource();\n source.buffer = audioBuffer;\n\n // Create analyzer for PCM data\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = element.fftSize; // Ensure enough samples\n analyser.minDecibels = -90;\n analyser.maxDecibels = -20;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = element.fftGain; // Amplify the signal\n\n source.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n source.start(0, startTime, 1 / 30);\n\n const dataLength = analyser.fftSize / 2;\n try {\n await audioContext.startRendering();\n const frameData = new Uint8Array(dataLength);\n analyser.getByteTimeDomainData(frameData);\n\n // const points = frameData;\n // Calculate RMS and midpoint values\n const points = new Uint8Array(dataLength);\n for (let i = 0; i < dataLength; i++) {\n const pointSamples = frameData.slice(\n i * (frameData.length / dataLength),\n (i + 1) * (frameData.length / dataLength),\n );\n\n // Calculate RMS while preserving sign\n const rms = Math.sqrt(\n pointSamples.reduce((sum, sample) => {\n const normalized = (sample - 128) / 128;\n return sum + normalized * normalized;\n }, 0) / pointSamples.length,\n );\n\n // Get average sign of the samples to determine direction\n const avgSign = Math.sign(\n pointSamples.reduce((sum, sample) => sum + (sample - 128), 0),\n );\n\n // Convert RMS back to byte range, preserving direction\n points[i] = Math.min(255, Math.round(128 + avgSign * rms * 128));\n }\n\n cache.set(cacheKey, points);\n return points;\n } finally {\n source.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n // Combine frames with decay weighting\n const frameLength = framesData[0]?.length ?? 0;\n const smoothedData = new Uint8Array(frameLength);\n\n for (let i = 0; i < frameLength; i++) {\n let weightedSum = 0;\n let weightSum = 0;\n\n framesData.forEach((frame: Uint8Array, frameIndex: number) => {\n const decayWeight = DECAY_WEIGHT ** frameIndex;\n weightedSum += (frame[i] ?? 0) * decayWeight;\n weightSum += decayWeight;\n });\n\n smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));\n }\n\n // Cache with the preliminary key so future requests can skip audio fetching\n cache.set(preliminaryCacheKey, smoothedData);\n return smoothedData;\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"makeAudioTimeDomainAnalysisTask.js","names":["task: Task<readonly [number], Uint8Array | null>","audioContext: OfflineAudioContext"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\n\nimport { EF_INTERACTIVE } from \"../../../EF_INTERACTIVE.js\";\nimport { LRUCache } from \"../../../utils/LRUCache.js\";\nimport { type EFMedia, IgnorableError } from \"../../EFMedia.js\";\n\n// DECAY_WEIGHT constant - same as original\nconst DECAY_WEIGHT = 0.8;\n\nexport function makeAudioTimeDomainAnalysisTask(element: EFMedia): Task<readonly [number], Uint8Array | null> {\n // Internal cache for this task instance (same as original #byteTimeDomainCache)\n const cache = new LRUCache<string, Uint8Array>(1000);\n\n // Capture task reference for use in onError\n let task: Task<readonly [number], Uint8Array | null>;\n\n task = new Task(element, {\n autoRun: EF_INTERACTIVE,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete BEFORE the promise is rejected.\n // This prevents unhandled rejection when hostUpdate() triggers _performTask() without awaiting.\n task.taskComplete.catch(() => {});\n \n if (error instanceof IgnorableError) {\n console.info(\"byteTimeDomainTask skipped: no audio track\");\n return;\n }\n \n // Don't log AbortErrors - these are expected when tasks are cancelled\n const isAbortError = \n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error && (\n error.name === \"AbortError\" ||\n error.message?.includes(\"signal is aborted\") ||\n error.message?.includes(\"The user aborted a request\")\n ));\n \n if (isAbortError) {\n return;\n }\n \n // 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(\"byteTimeDomainTask error\", error);\n },\n args: () =>\n [\n element.currentSourceTimeMs,\n ] as const,\n task: async (_, { signal }) => {\n if (element.currentSourceTimeMs < 0) return null;\n\n const currentTimeMs = element.currentSourceTimeMs;\n\n // Calculate exact audio window needed based on fftDecay and frame timing\n const frameIntervalMs = 1000 / 30; // 33.33ms per frame\n\n // Need audio from earliest frame to current frame\n const earliestFrameMs =\n currentTimeMs - (element.fftDecay - 1) * frameIntervalMs;\n const fromMs = Math.max(0, earliestFrameMs);\n const maxToMs = currentTimeMs + frameIntervalMs; // Include current frame\n const videoDurationMs = element.intrinsicDurationMs || 0;\n const toMs =\n videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;\n\n // If the clamping results in an invalid range (seeking beyond the end), skip analysis silently\n if (fromMs >= toMs) {\n return null;\n }\n\n // Check cache early - before expensive audio fetching\n // Use a preliminary cache key that doesn't depend on actual startOffsetMs from audio span\n const preliminaryCacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${fromMs}:${currentTimeMs}`;\n const cachedData = cache.get(preliminaryCacheKey);\n if (cachedData) {\n return cachedData;\n }\n\n // Check if media engine task has errored (no valid source) before attempting to use it\n if (element.mediaEngineTask.error) {\n return null;\n }\n \n // Check if audio rendition exists before attempting to fetch audio data\n // This prevents unnecessary HTTP requests and warnings when audio is not available\n let mediaEngine;\n try {\n mediaEngine = await element.mediaEngineTask.taskComplete;\n } catch (error) {\n // If media engine task failed (no valid source), return null silently\n if (error instanceof Error && error.message === \"No valid media source\") {\n return null;\n }\n // Re-throw unexpected errors\n throw error;\n }\n \n // Check for abort after awaiting media engine\n signal?.throwIfAborted();\n \n if (!mediaEngine?.audioRendition) {\n // No audio rendition available - skip silently (no warning needed)\n return null;\n }\n\n // Check if audioInputTask has errored or returned undefined before fetching\n // This prevents fetch calls when we know they'll fail (e.g., 401 auth required)\n if (element.audioInputTask.error) {\n return null;\n }\n const audioInputValue = element.audioInputTask.value;\n if (audioInputValue === undefined) {\n // Audio input is not available - don't try to fetch\n return null;\n }\n\n const { fetchAudioSpanningTime: fetchAudioSpan } =\n await import(\"../shared/AudioSpanUtils.js\");\n \n // Try to fetch audio span, but return null if it fails with expected errors\n try {\n const audioSpan = await fetchAudioSpan(element, fromMs, toMs, signal);\n\n if (!audioSpan || !audioSpan.blob) {\n // Audio data not available - skip silently (already checked for rendition above)\n return null;\n }\n\n // Validate blob has sufficient data before attempting decode\n // Empty or very small blobs will fail decodeAudioData\n if (audioSpan.blob.size < 100) {\n // Too small to be valid audio data - skip silently\n return null;\n }\n\n // Decode the real audio data\n const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);\n const arrayBuffer = await audioSpan.blob.arrayBuffer();\n \n // Check for abort after expensive arrayBuffer operation\n signal?.throwIfAborted();\n \n // Validate arrayBuffer before decode attempt\n if (arrayBuffer.byteLength < 100) {\n return null;\n }\n \n let audioBuffer;\n try {\n audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);\n \n // Check for abort after expensive decode operation\n signal?.throwIfAborted();\n } catch (decodeError) {\n // Unable to decode audio data - this means the data isn't valid audio\n // This can happen with corrupted/incomplete segments - skip silently\n if (decodeError instanceof Error && \n decodeError.message.includes(\"Unable to decode audio data\")) {\n return null;\n }\n throw decodeError;\n }\n\n // Use actual startOffset from audioSpan (relative to requested time)\n const startOffsetMs = audioSpan.startMs;\n\n // Process multiple frames with decay, similar to the reference code\n const framesData = await Promise.all(\n Array.from({ length: element.fftDecay }, async (_, frameIndex) => {\n const frameOffset = frameIndex * (1000 / 30);\n const startTime = Math.max(\n 0,\n (currentTimeMs - frameOffset - startOffsetMs) / 1000,\n );\n\n const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;\n const cachedFrame = cache.get(cacheKey);\n if (cachedFrame) {\n return cachedFrame;\n }\n\n let audioContext: OfflineAudioContext;\n try {\n audioContext = new OfflineAudioContext(2, 48000 * (1 / 30), 48000);\n } catch (error) {\n throw new Error(\n `[EFMedia.byteTimeDomainTask] Failed to create OfflineAudioContext(2, ${48000 * (1 / 30)}, 48000) for frame ${frameIndex} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio time domain analysis.`,\n );\n }\n\n const source = audioContext.createBufferSource();\n source.buffer = audioBuffer;\n\n // Create analyzer for PCM data\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = element.fftSize; // Ensure enough samples\n analyser.minDecibels = -90;\n analyser.maxDecibels = -20;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = element.fftGain; // Amplify the signal\n\n source.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n source.start(0, startTime, 1 / 30);\n\n const dataLength = analyser.fftSize / 2;\n try {\n await audioContext.startRendering();\n \n // Check for abort after expensive rendering operation\n signal?.throwIfAborted();\n \n const frameData = new Uint8Array(dataLength);\n analyser.getByteTimeDomainData(frameData);\n\n // const points = frameData;\n // Calculate RMS and midpoint values\n const points = new Uint8Array(dataLength);\n for (let i = 0; i < dataLength; i++) {\n const pointSamples = frameData.slice(\n i * (frameData.length / dataLength),\n (i + 1) * (frameData.length / dataLength),\n );\n\n // Calculate RMS while preserving sign\n const rms = Math.sqrt(\n pointSamples.reduce((sum, sample) => {\n const normalized = (sample - 128) / 128;\n return sum + normalized * normalized;\n }, 0) / pointSamples.length,\n );\n\n // Get average sign of the samples to determine direction\n const avgSign = Math.sign(\n pointSamples.reduce((sum, sample) => sum + (sample - 128), 0),\n );\n\n // Convert RMS back to byte range, preserving direction\n points[i] = Math.min(255, Math.round(128 + avgSign * rms * 128));\n }\n\n cache.set(cacheKey, points);\n return points;\n } finally {\n source.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n // Combine frames with decay weighting\n const frameLength = framesData[0]?.length ?? 0;\n const smoothedData = new Uint8Array(frameLength);\n\n for (let i = 0; i < frameLength; i++) {\n let weightedSum = 0;\n let weightSum = 0;\n\n framesData.forEach((frame: Uint8Array, frameIndex: number) => {\n const decayWeight = DECAY_WEIGHT ** frameIndex;\n weightedSum += (frame[i] ?? 0) * decayWeight;\n weightSum += decayWeight;\n });\n\n smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));\n }\n\n // Cache with the preliminary key so future requests can skip audio fetching\n cache.set(preliminaryCacheKey, smoothedData);\n return smoothedData;\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 null gracefully\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(\"No segments found\"))\n ) {\n return null;\n }\n // Re-throw unexpected errors\n throw error;\n }\n },\n });\n\n return task;\n}\n"],"mappings":"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"}
@@ -11,7 +11,7 @@ const fetchAudioSegmentData = async (segmentIds, mediaEngine, signal) => {
11
11
  return [segmentId, await mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal)];
12
12
  });
13
13
  const fetchedSegments = await Promise.all(fetchPromises);
14
- signal.throwIfAborted();
14
+ signal?.throwIfAborted();
15
15
  for (const [segmentId, arrayBuffer] of fetchedSegments) segmentData.set(segmentId, arrayBuffer);
16
16
  return segmentData;
17
17
  };
@@ -30,7 +30,9 @@ const createAudioSpanBlob = (initSegment, mediaSegments) => {
30
30
  const fetchAudioSpanningTime = async (host, fromMs, toMs, signal) => {
31
31
  if (fromMs >= toMs || fromMs < 0) throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);
32
32
  const mediaEngine = await host.mediaEngineTask.taskComplete;
33
+ signal?.throwIfAborted();
33
34
  const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
35
+ signal?.throwIfAborted();
34
36
  if (!mediaEngine?.audioRendition) return;
35
37
  if (!initSegment) return;
36
38
  const segmentRanges = mediaEngine.calculateAudioSegmentRange(fromMs, toMs, mediaEngine.audioRendition, host.intrinsicDurationMs || 1e4);
@@ -1 +1 @@
1
- {"version":3,"file":"AudioSpanUtils.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/AudioSpanUtils.ts"],"sourcesContent":["import type {\n AudioSpan,\n MediaEngine,\n SegmentTimeRange,\n} from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\n\n/**\n * Fetch audio segment data using MediaEngine\n * Pure function with explicit dependencies\n */\nconst fetchAudioSegmentData = async (\n segmentIds: number[],\n mediaEngine: MediaEngine,\n signal: AbortSignal,\n): Promise<Map<number, ArrayBuffer>> => {\n const audioRendition = mediaEngine.audioRendition;\n if (!audioRendition) {\n throw new Error(\"Audio rendition not available\");\n }\n\n const segmentData = new Map<number, ArrayBuffer>();\n\n // Fetch all segments - MediaEngine handles deduplication internally\n const fetchPromises = segmentIds.map(async (segmentId) => {\n const arrayBuffer = await mediaEngine.fetchMediaSegment(\n segmentId,\n audioRendition,\n signal,\n );\n return [segmentId, arrayBuffer] as [number, ArrayBuffer];\n });\n\n const fetchedSegments = await Promise.all(fetchPromises);\n signal.throwIfAborted();\n\n for (const [segmentId, arrayBuffer] of fetchedSegments) {\n segmentData.set(segmentId, arrayBuffer);\n }\n\n return segmentData;\n};\n\n/**\n * Create audio span blob from init segment and media segments\n * Pure function for blob creation\n */\nconst createAudioSpanBlob = (\n initSegment: ArrayBuffer,\n mediaSegments: ArrayBuffer[],\n): Blob => {\n const chunks = [initSegment, ...mediaSegments];\n return new Blob(chunks, { type: \"audio/mp4\" });\n};\n\n/**\n * Fetch audio spanning a time range\n * Main function that orchestrates segment calculation, fetching, and blob creation\n */\nexport const fetchAudioSpanningTime = async (\n host: EFMedia,\n fromMs: number,\n toMs: number,\n signal: AbortSignal,\n): Promise<AudioSpan | undefined> => {\n // Validate inputs\n if (fromMs >= toMs || fromMs < 0) {\n throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);\n }\n\n // Get dependencies from host\n const mediaEngine = await host.mediaEngineTask.taskComplete;\n const initSegment = await host.audioInitSegmentFetchTask.taskComplete;\n\n // Return undefined if no audio rendition available\n if (!mediaEngine?.audioRendition) {\n return undefined;\n }\n\n if (!initSegment) {\n return undefined;\n }\n\n // Calculate segments needed using the media engine's method\n const segmentRanges = mediaEngine.calculateAudioSegmentRange(\n fromMs,\n toMs,\n mediaEngine.audioRendition,\n host.intrinsicDurationMs || 10000,\n );\n\n if (segmentRanges.length === 0) {\n throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);\n }\n\n // Fetch segment data\n const segmentIds = segmentRanges.map((r: SegmentTimeRange) => r.segmentId);\n const segmentData = await fetchAudioSegmentData(\n segmentIds,\n mediaEngine,\n signal,\n );\n\n // Create ordered array of segments\n const orderedSegments = segmentIds.map((id: number) => {\n const segment = segmentData.get(id);\n if (!segment) {\n throw new Error(`Missing segment data for segment ID ${id}`);\n }\n return segment;\n });\n\n // Create blob\n const blob = createAudioSpanBlob(initSegment, orderedSegments);\n\n // Calculate actual time boundaries\n const actualStartMs = Math.min(\n ...segmentRanges.map((r: SegmentTimeRange) => r.startMs),\n );\n const actualEndMs = Math.max(\n ...segmentRanges.map((r: SegmentTimeRange) => r.endMs),\n );\n\n return {\n startMs: actualStartMs,\n endMs: actualEndMs,\n blob,\n };\n};\n"],"mappings":";;;;;AAWA,MAAM,wBAAwB,OAC5B,YACA,aACA,WACsC;CACtC,MAAM,iBAAiB,YAAY;AACnC,KAAI,CAAC,eACH,OAAM,IAAI,MAAM,gCAAgC;CAGlD,MAAM,8BAAc,IAAI,KAA0B;CAGlD,MAAM,gBAAgB,WAAW,IAAI,OAAO,cAAc;AAMxD,SAAO,CAAC,WALY,MAAM,YAAY,kBACpC,WACA,gBACA,OACD,CAC8B;GAC/B;CAEF,MAAM,kBAAkB,MAAM,QAAQ,IAAI,cAAc;AACxD,QAAO,gBAAgB;AAEvB,MAAK,MAAM,CAAC,WAAW,gBAAgB,gBACrC,aAAY,IAAI,WAAW,YAAY;AAGzC,QAAO;;;;;;AAOT,MAAM,uBACJ,aACA,kBACS;CACT,MAAM,SAAS,CAAC,aAAa,GAAG,cAAc;AAC9C,QAAO,IAAI,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;;;;;;AAOhD,MAAa,yBAAyB,OACpC,MACA,QACA,MACA,WACmC;AAEnC,KAAI,UAAU,QAAQ,SAAS,EAC7B,OAAM,IAAI,MAAM,8BAA8B,OAAO,SAAS,OAAO;CAIvE,MAAM,cAAc,MAAM,KAAK,gBAAgB;CAC/C,MAAM,cAAc,MAAM,KAAK,0BAA0B;AAGzD,KAAI,CAAC,aAAa,eAChB;AAGF,KAAI,CAAC,YACH;CAIF,MAAM,gBAAgB,YAAY,2BAChC,QACA,MACA,YAAY,gBACZ,KAAK,uBAAuB,IAC7B;AAED,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,oCAAoC,OAAO,GAAG,KAAK,IAAI;CAIzE,MAAM,aAAa,cAAc,KAAK,MAAwB,EAAE,UAAU;CAC1E,MAAM,cAAc,MAAM,sBACxB,YACA,aACA,OACD;CAYD,MAAM,OAAO,oBAAoB,aATT,WAAW,KAAK,OAAe;EACrD,MAAM,UAAU,YAAY,IAAI,GAAG;AACnC,MAAI,CAAC,QACH,OAAM,IAAI,MAAM,uCAAuC,KAAK;AAE9D,SAAO;GACP,CAG4D;AAU9D,QAAO;EACL,SARoB,KAAK,IACzB,GAAG,cAAc,KAAK,MAAwB,EAAE,QAAQ,CACzD;EAOC,OANkB,KAAK,IACvB,GAAG,cAAc,KAAK,MAAwB,EAAE,MAAM,CACvD;EAKC;EACD"}
1
+ {"version":3,"file":"AudioSpanUtils.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/AudioSpanUtils.ts"],"sourcesContent":["import type {\n AudioSpan,\n MediaEngine,\n SegmentTimeRange,\n} from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\n\n/**\n * Fetch audio segment data using MediaEngine\n * Pure function with explicit dependencies\n */\nconst fetchAudioSegmentData = async (\n segmentIds: number[],\n mediaEngine: MediaEngine,\n signal?: AbortSignal,\n): Promise<Map<number, ArrayBuffer>> => {\n const audioRendition = mediaEngine.audioRendition;\n if (!audioRendition) {\n throw new Error(\"Audio rendition not available\");\n }\n\n const segmentData = new Map<number, ArrayBuffer>();\n\n // Fetch all segments - MediaEngine handles deduplication internally\n const fetchPromises = segmentIds.map(async (segmentId) => {\n const arrayBuffer = await mediaEngine.fetchMediaSegment(\n segmentId,\n audioRendition,\n signal,\n );\n return [segmentId, arrayBuffer] as [number, ArrayBuffer];\n });\n\n const fetchedSegments = await Promise.all(fetchPromises);\n signal?.throwIfAborted();\n\n for (const [segmentId, arrayBuffer] of fetchedSegments) {\n segmentData.set(segmentId, arrayBuffer);\n }\n\n return segmentData;\n};\n\n/**\n * Create audio span blob from init segment and media segments\n * Pure function for blob creation\n */\nconst createAudioSpanBlob = (\n initSegment: ArrayBuffer,\n mediaSegments: ArrayBuffer[],\n): Blob => {\n const chunks = [initSegment, ...mediaSegments];\n return new Blob(chunks, { type: \"audio/mp4\" });\n};\n\n/**\n * Fetch audio spanning a time range\n * Main function that orchestrates segment calculation, fetching, and blob creation\n */\nexport const fetchAudioSpanningTime = async (\n host: EFMedia,\n fromMs: number,\n toMs: number,\n signal?: AbortSignal,\n): Promise<AudioSpan | undefined> => {\n // Validate inputs\n if (fromMs >= toMs || fromMs < 0) {\n throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);\n }\n\n // Get dependencies from host\n const mediaEngine = await host.mediaEngineTask.taskComplete;\n signal?.throwIfAborted();\n \n const initSegment = await host.audioInitSegmentFetchTask.taskComplete;\n signal?.throwIfAborted();\n\n // Return undefined if no audio rendition available\n if (!mediaEngine?.audioRendition) {\n return undefined;\n }\n\n if (!initSegment) {\n return undefined;\n }\n\n // Calculate segments needed using the media engine's method\n const segmentRanges = mediaEngine.calculateAudioSegmentRange(\n fromMs,\n toMs,\n mediaEngine.audioRendition,\n host.intrinsicDurationMs || 10000,\n );\n\n if (segmentRanges.length === 0) {\n throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);\n }\n\n // Fetch segment data\n const segmentIds = segmentRanges.map((r: SegmentTimeRange) => r.segmentId);\n const segmentData = await fetchAudioSegmentData(\n segmentIds,\n mediaEngine,\n signal,\n );\n\n // Create ordered array of segments\n const orderedSegments = segmentIds.map((id: number) => {\n const segment = segmentData.get(id);\n if (!segment) {\n throw new Error(`Missing segment data for segment ID ${id}`);\n }\n return segment;\n });\n\n // Create blob\n const blob = createAudioSpanBlob(initSegment, orderedSegments);\n\n // Calculate actual time boundaries\n const actualStartMs = Math.min(\n ...segmentRanges.map((r: SegmentTimeRange) => r.startMs),\n );\n const actualEndMs = Math.max(\n ...segmentRanges.map((r: SegmentTimeRange) => r.endMs),\n );\n\n return {\n startMs: actualStartMs,\n endMs: actualEndMs,\n blob,\n };\n};\n"],"mappings":";;;;;AAWA,MAAM,wBAAwB,OAC5B,YACA,aACA,WACsC;CACtC,MAAM,iBAAiB,YAAY;AACnC,KAAI,CAAC,eACH,OAAM,IAAI,MAAM,gCAAgC;CAGlD,MAAM,8BAAc,IAAI,KAA0B;CAGlD,MAAM,gBAAgB,WAAW,IAAI,OAAO,cAAc;AAMxD,SAAO,CAAC,WALY,MAAM,YAAY,kBACpC,WACA,gBACA,OACD,CAC8B;GAC/B;CAEF,MAAM,kBAAkB,MAAM,QAAQ,IAAI,cAAc;AACxD,SAAQ,gBAAgB;AAExB,MAAK,MAAM,CAAC,WAAW,gBAAgB,gBACrC,aAAY,IAAI,WAAW,YAAY;AAGzC,QAAO;;;;;;AAOT,MAAM,uBACJ,aACA,kBACS;CACT,MAAM,SAAS,CAAC,aAAa,GAAG,cAAc;AAC9C,QAAO,IAAI,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;;;;;;AAOhD,MAAa,yBAAyB,OACpC,MACA,QACA,MACA,WACmC;AAEnC,KAAI,UAAU,QAAQ,SAAS,EAC7B,OAAM,IAAI,MAAM,8BAA8B,OAAO,SAAS,OAAO;CAIvE,MAAM,cAAc,MAAM,KAAK,gBAAgB;AAC/C,SAAQ,gBAAgB;CAExB,MAAM,cAAc,MAAM,KAAK,0BAA0B;AACzD,SAAQ,gBAAgB;AAGxB,KAAI,CAAC,aAAa,eAChB;AAGF,KAAI,CAAC,YACH;CAIF,MAAM,gBAAgB,YAAY,2BAChC,QACA,MACA,YAAY,gBACZ,KAAK,uBAAuB,IAC7B;AAED,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,oCAAoC,OAAO,GAAG,KAAK,IAAI;CAIzE,MAAM,aAAa,cAAc,KAAK,MAAwB,EAAE,UAAU;CAC1E,MAAM,cAAc,MAAM,sBACxB,YACA,aACA,OACD;CAYD,MAAM,OAAO,oBAAoB,aATT,WAAW,KAAK,OAAe;EACrD,MAAM,UAAU,YAAY,IAAI,GAAG;AACnC,MAAI,CAAC,QACH,OAAM,IAAI,MAAM,uCAAuC,KAAK;AAE9D,SAAO;GACP,CAG4D;AAU9D,QAAO;EACL,SARoB,KAAK,IACzB,GAAG,cAAc,KAAK,MAAwB,EAAE,QAAQ,CACzD;EAOC,OANkB,KAAK,IACvB,GAAG,cAAc,KAAK,MAAwB,EAAE,MAAM,CACvD;EAKC;EACD"}
@@ -67,7 +67,7 @@ const manageMediaBuffer = async (seekTimeMs, config, currentState, durationMs, s
67
67
  }).catch((error) => {
68
68
  if (signal.aborted) return;
69
69
  newActiveRequests.delete(nextSegmentId);
70
- deps.logError(`Failed to prefetch segment ${nextSegmentId}`, error);
70
+ 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")))) deps.logError(`Failed to prefetch segment ${nextSegmentId}`, error);
71
71
  if (config.enableContinuousBuffering ?? true) startNextSegment();
72
72
  });
73
73
  };