@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":"JitMediaEngine.js","names":["rendition: VideoRendition"],"sources":["../../../src/elements/EFMedia/JitMediaEngine.ts"],"sourcesContent":["import type {\n AudioRendition,\n MediaEngine,\n RenditionId,\n ThumbnailResult,\n VideoRendition,\n} from \"../../transcoding/types\";\nimport type { ManifestResponse } from \"../../transcoding/types/index.js\";\nimport type { UrlGenerator } from \"../../transcoding/utils/UrlGenerator\";\nimport type { EFMedia } from \"../EFMedia.js\";\nimport { BaseMediaEngine } from \"./BaseMediaEngine\";\nimport { ThumbnailExtractor } from \"./shared/ThumbnailExtractor.js\";\n\nexport class JitMediaEngine extends BaseMediaEngine implements MediaEngine {\n private urlGenerator: UrlGenerator;\n private data: ManifestResponse = {} as ManifestResponse;\n private thumbnailExtractor: ThumbnailExtractor;\n\n static async fetch(host: EFMedia, urlGenerator: UrlGenerator, url: string) {\n const engine = new JitMediaEngine(host, urlGenerator);\n const data = await engine.fetchManifest(url);\n engine.data = data;\n return engine;\n }\n\n constructor(host: EFMedia, urlGenerator: UrlGenerator) {\n super(host);\n this.urlGenerator = urlGenerator;\n this.thumbnailExtractor = new ThumbnailExtractor(this);\n }\n\n get durationMs() {\n return this.data.durationMs;\n }\n\n get src() {\n return this.data.sourceUrl;\n }\n\n get audioRendition(): AudioRendition | undefined {\n if (!this.data.audioRenditions || this.data.audioRenditions.length === 0) {\n return undefined;\n }\n\n const rendition = this.data.audioRenditions[0];\n if (!rendition) return undefined;\n\n return {\n id: rendition.id as RenditionId,\n trackId: undefined,\n src: this.data.sourceUrl,\n segmentDurationMs: rendition.segmentDurationMs,\n segmentDurationsMs: rendition.segmentDurationsMs,\n };\n }\n\n get videoRendition(): VideoRendition | undefined {\n if (!this.data.videoRenditions || this.data.videoRenditions.length === 0) {\n return undefined;\n }\n\n const rendition = this.data.videoRenditions[0];\n if (!rendition) return undefined;\n\n return {\n id: rendition.id as RenditionId,\n trackId: undefined,\n src: this.data.sourceUrl,\n segmentDurationMs: rendition.segmentDurationMs,\n segmentDurationsMs: rendition.segmentDurationsMs,\n };\n }\n\n get templates() {\n return this.data.endpoints;\n }\n\n async fetchInitSegment(\n rendition: { id?: RenditionId; trackId: number | undefined; src: string },\n signal: AbortSignal,\n ) {\n if (!rendition.id) {\n throw new Error(\"Rendition ID is required for JIT metadata\");\n }\n const url = this.urlGenerator.generateSegmentUrl(\n \"init\",\n rendition.id,\n this,\n );\n\n // Use unified fetch method\n return this.fetchMedia(url, signal);\n }\n\n async fetchMediaSegment(\n segmentId: number,\n rendition: { id?: RenditionId; trackId: number | undefined; src: string },\n ) {\n if (!rendition.id) {\n throw new Error(\"Rendition ID is required for JIT metadata\");\n }\n const url = this.urlGenerator.generateSegmentUrl(\n segmentId,\n rendition.id,\n this,\n );\n return this.fetchMedia(url);\n }\n\n computeSegmentId(\n desiredSeekTimeMs: number,\n rendition: VideoRendition | AudioRendition,\n ) {\n // Don't request segments beyond the actual file duration\n // Note: seeking to exactly durationMs should be allowed (it's the last moment of the file)\n if (desiredSeekTimeMs > this.durationMs) {\n return undefined;\n }\n\n // Use actual segment durations if available (more accurate)\n if (\n rendition.segmentDurationsMs &&\n rendition.segmentDurationsMs.length > 0\n ) {\n let cumulativeTime = 0;\n\n for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {\n const segmentDuration = rendition.segmentDurationsMs[i];\n if (segmentDuration === undefined) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = cumulativeTime + segmentDuration;\n\n // Check if the desired seek time falls within this segment\n // Special case: for the last segment, include the exact end time\n const isLastSegment = i === rendition.segmentDurationsMs.length - 1;\n const includesEndTime =\n isLastSegment && desiredSeekTimeMs === this.durationMs;\n\n if (\n desiredSeekTimeMs >= segmentStartMs &&\n (desiredSeekTimeMs < segmentEndMs || includesEndTime)\n ) {\n return i + 1; // Convert 0-based to 1-based segment ID\n }\n\n cumulativeTime += segmentDuration;\n\n // If we've reached or exceeded file duration, stop\n if (cumulativeTime >= this.durationMs) {\n break;\n }\n }\n\n // If we didn't find a segment, return undefined\n return undefined;\n }\n\n // Fall back to fixed duration calculation for backward compatibility\n if (!rendition.segmentDurationMs) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n\n const segmentIndex = Math.floor(\n desiredSeekTimeMs / rendition.segmentDurationMs,\n );\n\n // Calculate the actual segment start time\n const segmentStartMs = segmentIndex * rendition.segmentDurationMs;\n\n // If this segment would start at or beyond file duration, it doesn't exist\n if (segmentStartMs >= this.durationMs) {\n return undefined;\n }\n\n return segmentIndex + 1; // Convert 0-based to 1-based\n }\n\n getScrubVideoRendition(): VideoRendition | undefined {\n if (!this.data.videoRenditions) return undefined;\n\n const scrubManifestRendition = this.data.videoRenditions.find(\n (r) => r.id === \"scrub\",\n );\n\n if (!scrubManifestRendition) return this.videoRendition; // Fallback to main\n\n return {\n id: scrubManifestRendition.id as any,\n trackId: undefined,\n src: this.src,\n segmentDurationMs: scrubManifestRendition.segmentDurationMs,\n segmentDurationsMs: scrubManifestRendition.segmentDurationsMs,\n };\n }\n\n /**\n * Get preferred buffer configuration for JIT transcoding\n * Uses higher buffering since transcoding introduces latency\n */\n getBufferConfig() {\n return {\n // Buffer more aggressively for JIT transcoding to smooth over latency\n videoBufferDurationMs: 8000,\n audioBufferDurationMs: 8000,\n maxVideoBufferFetches: 3,\n maxAudioBufferFetches: 3,\n bufferThresholdMs: 30000, // Timeline-aware buffering threshold\n };\n }\n\n /**\n * Extract thumbnail canvases using same rendition priority as video playback for frame alignment\n */\n async extractThumbnails(\n timestamps: number[],\n ): Promise<(ThumbnailResult | null)[]> {\n // Use same rendition priority as video: try main rendition first for frame alignment\n let rendition: VideoRendition;\n try {\n const mainRendition = this.getVideoRendition();\n if (mainRendition) {\n rendition = mainRendition;\n } else {\n const scrubRendition = this.getScrubVideoRendition();\n if (scrubRendition) {\n rendition = scrubRendition;\n } else {\n throw new Error(\"No video rendition available\");\n }\n }\n } catch (error) {\n console.warn(\n \"JitMediaEngine: No video rendition available for thumbnails\",\n error,\n );\n return timestamps.map(() => null);\n }\n\n // Use shared thumbnail extraction logic\n return this.thumbnailExtractor.extractThumbnails(\n timestamps,\n rendition,\n this.durationMs,\n );\n }\n\n convertToSegmentRelativeTimestamps(\n globalTimestamps: number[],\n _segmentId: number,\n _rendition: VideoRendition,\n ): number[] {\n return globalTimestamps.map((timestamp) => timestamp / 1000);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"JitMediaEngine.js","names":["rendition: VideoRendition"],"sources":["../../../src/elements/EFMedia/JitMediaEngine.ts"],"sourcesContent":["import type {\n AudioRendition,\n MediaEngine,\n RenditionId,\n ThumbnailResult,\n VideoRendition,\n} from \"../../transcoding/types\";\nimport type { ManifestResponse } from \"../../transcoding/types/index.js\";\nimport type { UrlGenerator } from \"../../transcoding/utils/UrlGenerator\";\nimport type { EFMedia } from \"../EFMedia.js\";\nimport { BaseMediaEngine } from \"./BaseMediaEngine\";\nimport { ThumbnailExtractor } from \"./shared/ThumbnailExtractor.js\";\n\nexport class JitMediaEngine extends BaseMediaEngine implements MediaEngine {\n private urlGenerator: UrlGenerator;\n private data: ManifestResponse = {} as ManifestResponse;\n private thumbnailExtractor: ThumbnailExtractor;\n\n static async fetch(host: EFMedia, urlGenerator: UrlGenerator, url: string, signal?: AbortSignal) {\n const engine = new JitMediaEngine(host, urlGenerator);\n const data = await engine.fetchManifest(url, signal);\n \n // Check for abort after potentially slow network operation\n signal?.throwIfAborted();\n \n engine.data = data;\n return engine;\n }\n\n constructor(host: EFMedia, urlGenerator: UrlGenerator) {\n super(host);\n this.urlGenerator = urlGenerator;\n this.thumbnailExtractor = new ThumbnailExtractor(this);\n }\n\n get durationMs() {\n return this.data.durationMs;\n }\n\n get src() {\n return this.data.sourceUrl;\n }\n\n get audioRendition(): AudioRendition | undefined {\n if (!this.data.audioRenditions || this.data.audioRenditions.length === 0) {\n return undefined;\n }\n\n const rendition = this.data.audioRenditions[0];\n if (!rendition) return undefined;\n\n return {\n id: rendition.id as RenditionId,\n trackId: undefined,\n src: this.data.sourceUrl,\n segmentDurationMs: rendition.segmentDurationMs,\n segmentDurationsMs: rendition.segmentDurationsMs,\n startTimeOffsetMs: rendition.startTimeOffsetMs,\n };\n }\n\n get videoRendition(): VideoRendition | undefined {\n if (!this.data.videoRenditions || this.data.videoRenditions.length === 0) {\n return undefined;\n }\n\n const rendition = this.data.videoRenditions[0];\n if (!rendition) return undefined;\n\n return {\n id: rendition.id as RenditionId,\n trackId: undefined,\n src: this.data.sourceUrl,\n segmentDurationMs: rendition.segmentDurationMs,\n segmentDurationsMs: rendition.segmentDurationsMs,\n startTimeOffsetMs: rendition.startTimeOffsetMs,\n };\n }\n\n get templates() {\n return this.data.endpoints;\n }\n\n async fetchInitSegment(\n rendition: { id?: RenditionId; trackId: number | undefined; src: string },\n signal?: AbortSignal,\n ) {\n if (!rendition.id) {\n throw new Error(\"Rendition ID is required for JIT metadata\");\n }\n const url = this.urlGenerator.generateSegmentUrl(\n \"init\",\n rendition.id,\n this,\n );\n\n // Use unified fetch method\n return this.fetchMedia(url, signal);\n }\n\n async fetchMediaSegment(\n segmentId: number,\n rendition: { id?: RenditionId; trackId: number | undefined; src: string },\n signal?: AbortSignal,\n ) {\n if (!rendition.id) {\n throw new Error(\"Rendition ID is required for JIT metadata\");\n }\n const url = this.urlGenerator.generateSegmentUrl(\n segmentId,\n rendition.id,\n this,\n );\n return this.fetchMedia(url, signal);\n }\n\n computeSegmentId(\n desiredSeekTimeMs: number,\n rendition: VideoRendition | AudioRendition,\n ) {\n // Don't request segments beyond the actual file duration\n // Note: seeking to exactly durationMs should be allowed (it's the last moment of the file)\n if (desiredSeekTimeMs > this.durationMs) {\n return undefined;\n }\n\n // Use actual segment durations if available (more accurate)\n if (\n rendition.segmentDurationsMs &&\n rendition.segmentDurationsMs.length > 0\n ) {\n let cumulativeTime = 0;\n\n for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {\n const segmentDuration = rendition.segmentDurationsMs[i];\n if (segmentDuration === undefined) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = cumulativeTime + segmentDuration;\n\n // Check if the desired seek time falls within this segment\n // Special case: for the last segment, include the exact end time\n const isLastSegment = i === rendition.segmentDurationsMs.length - 1;\n const includesEndTime =\n isLastSegment && desiredSeekTimeMs === this.durationMs;\n\n if (\n desiredSeekTimeMs >= segmentStartMs &&\n (desiredSeekTimeMs < segmentEndMs || includesEndTime)\n ) {\n return i + 1; // Convert 0-based to 1-based segment ID\n }\n\n cumulativeTime += segmentDuration;\n\n // If we've reached or exceeded file duration, stop\n if (cumulativeTime >= this.durationMs) {\n break;\n }\n }\n\n // If we didn't find a segment, return undefined\n return undefined;\n }\n\n // Fall back to fixed duration calculation for backward compatibility\n if (!rendition.segmentDurationMs) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n\n const segmentIndex = Math.floor(\n desiredSeekTimeMs / rendition.segmentDurationMs,\n );\n\n // Calculate the actual segment start time\n const segmentStartMs = segmentIndex * rendition.segmentDurationMs;\n\n // If this segment would start at or beyond file duration, it doesn't exist\n if (segmentStartMs >= this.durationMs) {\n return undefined;\n }\n\n return segmentIndex + 1; // Convert 0-based to 1-based\n }\n\n getScrubVideoRendition(): VideoRendition | undefined {\n if (!this.data.videoRenditions) return undefined;\n\n const scrubManifestRendition = this.data.videoRenditions.find(\n (r) => r.id === \"scrub\",\n );\n\n if (!scrubManifestRendition) return this.videoRendition; // Fallback to main\n\n return {\n id: scrubManifestRendition.id as any,\n trackId: undefined,\n src: this.src,\n segmentDurationMs: scrubManifestRendition.segmentDurationMs,\n segmentDurationsMs: scrubManifestRendition.segmentDurationsMs,\n };\n }\n\n /**\n * Get preferred buffer configuration for JIT transcoding\n * Uses higher buffering since transcoding introduces latency\n */\n getBufferConfig() {\n return {\n // Buffer more aggressively for JIT transcoding to smooth over latency\n videoBufferDurationMs: 8000,\n audioBufferDurationMs: 8000,\n maxVideoBufferFetches: 3,\n maxAudioBufferFetches: 3,\n bufferThresholdMs: 30000, // Timeline-aware buffering threshold\n };\n }\n\n /**\n * Extract thumbnail canvases using same rendition priority as video playback for frame alignment\n */\n async extractThumbnails(\n timestamps: number[],\n signal?: AbortSignal,\n ): Promise<(ThumbnailResult | null)[]> {\n // Use same rendition priority as video: try main rendition first for frame alignment\n let rendition: VideoRendition;\n try {\n const mainRendition = this.getVideoRendition();\n if (mainRendition) {\n rendition = mainRendition;\n } else {\n const scrubRendition = this.getScrubVideoRendition();\n if (scrubRendition) {\n rendition = scrubRendition;\n } else {\n throw new Error(\"No video rendition available\");\n }\n }\n } catch (error) {\n console.warn(\n \"JitMediaEngine: No video rendition available for thumbnails\",\n error,\n );\n return timestamps.map(() => null);\n }\n\n // Use shared thumbnail extraction logic\n return this.thumbnailExtractor.extractThumbnails(\n timestamps,\n rendition,\n this.durationMs,\n signal,\n );\n }\n\n convertToSegmentRelativeTimestamps(\n globalTimestamps: number[],\n _segmentId: number,\n _rendition: VideoRendition,\n ): number[] {\n return globalTimestamps.map((timestamp) => timestamp / 1000);\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
2
2
  import { EF_RENDERING } from "../../../EF_RENDERING.js";
3
+ import { AssetMediaEngine } from "../AssetMediaEngine.js";
3
4
  import { manageMediaBuffer } from "../shared/BufferUtils.js";
4
5
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
5
6
  import { Task } from "@lit/task";
@@ -12,10 +13,14 @@ const makeAudioBufferTask = (host) => {
12
13
  activeRequests: /* @__PURE__ */ new Set(),
13
14
  requestQueue: []
14
15
  };
15
- return new Task(host, {
16
+ let task;
17
+ task = new Task(host, {
16
18
  autoRun: EF_INTERACTIVE,
17
19
  args: () => [host.desiredSeekTimeMs],
18
20
  onError: (error) => {
21
+ task.taskComplete.catch(() => {});
22
+ if (error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message?.includes("signal is aborted") || error.message?.includes("The user aborted a request"))) return;
23
+ if (error instanceof Error && (error.message === "No valid media source" || error.message.includes("File not found") || error.message.includes("is not valid JSON") || error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch"))) return;
19
24
  console.error("audioBufferTask error", error);
20
25
  },
21
26
  onComplete: (value) => {
@@ -23,7 +28,15 @@ const makeAudioBufferTask = (host) => {
23
28
  },
24
29
  task: async ([seekTimeMs], { signal }) => {
25
30
  if (EF_RENDERING()) return currentState;
26
- const mediaEngine = await getLatestMediaEngine(host, signal);
31
+ if (host.mediaEngineTask.error) return currentState;
32
+ let mediaEngine;
33
+ try {
34
+ mediaEngine = await getLatestMediaEngine(host, signal);
35
+ } catch (error) {
36
+ if (error instanceof Error && error.message === "No valid media source") return currentState;
37
+ throw error;
38
+ }
39
+ if (!mediaEngine) return currentState;
27
40
  if (!mediaEngine.audioRendition) return currentState;
28
41
  const engineConfig = mediaEngine.getBufferConfig();
29
42
  const currentConfig = {
@@ -39,10 +52,28 @@ const makeAudioBufferTask = (host) => {
39
52
  } : void 0;
40
53
  return manageMediaBuffer(seekTimeMs, currentConfig, currentState, host.intrinsicDurationMs || 1e4, signal, {
41
54
  computeSegmentId: async (timeMs, rendition) => {
42
- return (await getLatestMediaEngine(host, signal)).computeSegmentId(timeMs, rendition);
55
+ try {
56
+ const mediaEngine$1 = await getLatestMediaEngine(host, signal);
57
+ if (!mediaEngine$1) return void 0;
58
+ return mediaEngine$1.computeSegmentId(timeMs, rendition);
59
+ } catch (error) {
60
+ if (error instanceof Error && error.message === "No valid media source") return;
61
+ throw error;
62
+ }
43
63
  },
44
64
  prefetchSegment: async (segmentId, rendition) => {
45
- await (await getLatestMediaEngine(host, signal)).fetchMediaSegment(segmentId, rendition);
65
+ try {
66
+ const mediaEngine$1 = await getLatestMediaEngine(host, signal);
67
+ if (!mediaEngine$1) return;
68
+ if (mediaEngine$1 instanceof AssetMediaEngine) {
69
+ const trackData = mediaEngine$1.data?.[rendition.trackId];
70
+ if (!trackData?.segments || segmentId >= trackData.segments.length) return;
71
+ }
72
+ await mediaEngine$1.fetchMediaSegment(segmentId, rendition, signal);
73
+ } catch (error) {
74
+ if (error instanceof Error && (error.message === "No valid media source" || error.message.includes("Media segment not found") || error.message.includes("Track not found") || error.message.includes("Failed to fetch") || error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("File not found"))) return;
75
+ throw error;
76
+ }
46
77
  },
47
78
  isSegmentCached: (segmentId, rendition) => {
48
79
  const mediaEngine$1 = host.mediaEngineTask.value;
@@ -50,14 +81,22 @@ const makeAudioBufferTask = (host) => {
50
81
  return mediaEngine$1.isSegmentCached(segmentId, rendition);
51
82
  },
52
83
  getRendition: async () => {
53
- const audioRendition = (await getLatestMediaEngine(host, signal)).audioRendition;
54
- if (!audioRendition) throw new Error("Audio rendition not available");
55
- return audioRendition;
84
+ try {
85
+ const mediaEngine$1 = await getLatestMediaEngine(host, signal);
86
+ if (!mediaEngine$1) throw new Error("Audio rendition not available");
87
+ const audioRendition = mediaEngine$1.audioRendition;
88
+ if (!audioRendition) throw new Error("Audio rendition not available");
89
+ return audioRendition;
90
+ } catch (error) {
91
+ if (error instanceof Error && error.message === "No valid media source") throw new Error("Audio rendition not available");
92
+ throw error;
93
+ }
56
94
  },
57
95
  logError: console.error
58
96
  }, timelineContext);
59
97
  }
60
98
  });
99
+ return task;
61
100
  };
62
101
 
63
102
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"makeAudioBufferTask.js","names":["currentState: AudioBufferState","currentConfig: AudioBufferConfig","mediaEngine"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\n\nimport { EF_INTERACTIVE } from \"../../../EF_INTERACTIVE\";\nimport { EF_RENDERING } from \"../../../EF_RENDERING\";\nimport type { AudioRendition } from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\nimport {\n type MediaBufferConfig,\n type MediaBufferState,\n manageMediaBuffer,\n} from \"../shared/BufferUtils\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\n/**\n * Configuration for audio buffering - extends the generic interface\n */\nexport interface AudioBufferConfig extends MediaBufferConfig {}\n\n/**\n * State of the audio buffer - uses the generic interface\n */\nexport interface AudioBufferState extends MediaBufferState {}\n\ntype AudioBufferTask = Task<readonly [number], AudioBufferState>;\nexport const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {\n let currentState: AudioBufferState = {\n currentSeekTimeMs: 0,\n requestedSegments: new Set(),\n activeRequests: new Set(),\n requestQueue: [],\n };\n\n return new Task(host, {\n autoRun: EF_INTERACTIVE, // Make lazy - only run when element becomes timeline-active\n args: () => [host.desiredSeekTimeMs] as const,\n onError: (error) => {\n console.error(\"audioBufferTask error\", error);\n },\n onComplete: (value) => {\n currentState = value;\n },\n task: async ([seekTimeMs], { signal }) => {\n // Skip buffering entirely in rendering mode\n if (EF_RENDERING()) {\n return currentState; // Return existing state without any buffering activity\n }\n\n // Get media engine to potentially override buffer configuration\n const mediaEngine = await getLatestMediaEngine(host, signal);\n\n // Return existing state if no audio rendition available\n if (!mediaEngine.audioRendition) {\n return currentState;\n }\n\n // Use media engine's buffer config, falling back to host properties\n const engineConfig = mediaEngine.getBufferConfig();\n const bufferDurationMs = engineConfig.audioBufferDurationMs;\n const maxParallelFetches = engineConfig.maxAudioBufferFetches;\n\n const currentConfig: AudioBufferConfig = {\n bufferDurationMs,\n maxParallelFetches,\n enableBuffering: host.enableAudioBuffering,\n bufferThresholdMs: engineConfig.bufferThresholdMs,\n };\n\n // Timeline context for priority-based buffering\n const timelineContext =\n host.rootTimegroup?.currentTimeMs !== undefined\n ? {\n elementStartMs: host.startTimeMs,\n elementEndMs: host.endTimeMs,\n playheadMs: host.rootTimegroup.currentTimeMs,\n }\n : undefined;\n\n return manageMediaBuffer<AudioRendition>(\n seekTimeMs,\n currentConfig,\n currentState,\n (host as any).intrinsicDurationMs || 10000,\n signal,\n {\n computeSegmentId: async (timeMs, rendition) => {\n // Use media engine's computeSegmentId\n const mediaEngine = await getLatestMediaEngine(host, signal);\n return mediaEngine.computeSegmentId(timeMs, rendition);\n },\n prefetchSegment: async (segmentId, rendition) => {\n // Trigger prefetch through BaseMediaEngine - let it handle caching\n const mediaEngine = await getLatestMediaEngine(host, signal);\n await mediaEngine.fetchMediaSegment(segmentId, rendition);\n // Don't return data - just ensure it's cached in BaseMediaEngine\n },\n isSegmentCached: (segmentId, rendition) => {\n // Check if segment is already cached in BaseMediaEngine\n const mediaEngine = host.mediaEngineTask.value;\n if (!mediaEngine) return false;\n\n return mediaEngine.isSegmentCached(segmentId, rendition);\n },\n getRendition: async () => {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n const audioRendition = mediaEngine.audioRendition;\n if (!audioRendition) {\n throw new Error(\"Audio rendition not available\");\n }\n return audioRendition;\n },\n logError: console.error,\n },\n timelineContext,\n );\n },\n });\n};\n"],"mappings":";;;;;;;AAwBA,MAAa,uBAAuB,SAAmC;CACrE,IAAIA,eAAiC;EACnC,mBAAmB;EACnB,mCAAmB,IAAI,KAAK;EAC5B,gCAAgB,IAAI,KAAK;EACzB,cAAc,EAAE;EACjB;AAED,QAAO,IAAI,KAAK,MAAM;EACpB,SAAS;EACT,YAAY,CAAC,KAAK,kBAAkB;EACpC,UAAU,UAAU;AAClB,WAAQ,MAAM,yBAAyB,MAAM;;EAE/C,aAAa,UAAU;AACrB,kBAAe;;EAEjB,MAAM,OAAO,CAAC,aAAa,EAAE,aAAa;AAExC,OAAI,cAAc,CAChB,QAAO;GAIT,MAAM,cAAc,MAAM,qBAAqB,MAAM,OAAO;AAG5D,OAAI,CAAC,YAAY,eACf,QAAO;GAIT,MAAM,eAAe,YAAY,iBAAiB;GAIlD,MAAMC,gBAAmC;IACvC,kBAJuB,aAAa;IAKpC,oBAJyB,aAAa;IAKtC,iBAAiB,KAAK;IACtB,mBAAmB,aAAa;IACjC;GAGD,MAAM,kBACJ,KAAK,eAAe,kBAAkB,SAClC;IACE,gBAAgB,KAAK;IACrB,cAAc,KAAK;IACnB,YAAY,KAAK,cAAc;IAChC,GACD;AAEN,UAAO,kBACL,YACA,eACA,cACC,KAAa,uBAAuB,KACrC,QACA;IACE,kBAAkB,OAAO,QAAQ,cAAc;AAG7C,aADoB,MAAM,qBAAqB,MAAM,OAAO,EACzC,iBAAiB,QAAQ,UAAU;;IAExD,iBAAiB,OAAO,WAAW,cAAc;AAG/C,YADoB,MAAM,qBAAqB,MAAM,OAAO,EAC1C,kBAAkB,WAAW,UAAU;;IAG3D,kBAAkB,WAAW,cAAc;KAEzC,MAAMC,gBAAc,KAAK,gBAAgB;AACzC,SAAI,CAACA,cAAa,QAAO;AAEzB,YAAOA,cAAY,gBAAgB,WAAW,UAAU;;IAE1D,cAAc,YAAY;KAExB,MAAM,kBADc,MAAM,qBAAqB,MAAM,OAAO,EACzB;AACnC,SAAI,CAAC,eACH,OAAM,IAAI,MAAM,gCAAgC;AAElD,YAAO;;IAET,UAAU,QAAQ;IACnB,EACD,gBACD;;EAEJ,CAAC"}
1
+ {"version":3,"file":"makeAudioBufferTask.js","names":["currentState: AudioBufferState","task: AudioBufferTask","currentConfig: AudioBufferConfig","mediaEngine"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\n\nimport { EF_INTERACTIVE } from \"../../../EF_INTERACTIVE\";\nimport { EF_RENDERING } from \"../../../EF_RENDERING\";\nimport type { AudioRendition } from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\nimport { AssetMediaEngine } from \"../AssetMediaEngine\";\nimport {\n type MediaBufferConfig,\n type MediaBufferState,\n manageMediaBuffer,\n} from \"../shared/BufferUtils\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\n/**\n * Configuration for audio buffering - extends the generic interface\n */\nexport interface AudioBufferConfig extends MediaBufferConfig {}\n\n/**\n * State of the audio buffer - uses the generic interface\n */\nexport interface AudioBufferState extends MediaBufferState {}\n\ntype AudioBufferTask = Task<readonly [number], AudioBufferState>;\nexport const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {\n let currentState: AudioBufferState = {\n currentSeekTimeMs: 0,\n requestedSegments: new Set(),\n activeRequests: new Set(),\n requestQueue: [],\n };\n\n // Capture task reference for use in onError\n let task: AudioBufferTask;\n\n task = new Task(host, {\n autoRun: EF_INTERACTIVE, // Make lazy - only run when element becomes timeline-active\n args: () => [host.desiredSeekTimeMs] as const,\n onError: (error) => {\n // CRITICAL: Attach .catch() handler to taskComplete 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, 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(\"audioBufferTask error\", error);\n },\n onComplete: (value) => {\n currentState = value;\n },\n task: async ([seekTimeMs], { signal }) => {\n // Skip buffering entirely in rendering mode\n if (EF_RENDERING()) {\n return currentState; // Return existing state without any buffering activity\n }\n\n // Check if media engine task has errored (no valid source) before attempting to use it\n if (host.mediaEngineTask.error) {\n return currentState;\n }\n\n // Get media engine to potentially override buffer configuration\n let mediaEngine;\n try {\n mediaEngine = await getLatestMediaEngine(host, signal);\n } catch (error) {\n // If media engine task failed (no valid source), return current state silently\n if (error instanceof Error && error.message === \"No valid media source\") {\n return currentState;\n }\n // Re-throw unexpected errors\n throw error;\n }\n\n // Return existing state if no valid media engine (no valid source)\n if (!mediaEngine) {\n return currentState;\n }\n\n // Return existing state if no audio rendition available\n if (!mediaEngine.audioRendition) {\n return currentState;\n }\n\n // Use media engine's buffer config, falling back to host properties\n const engineConfig = mediaEngine.getBufferConfig();\n const bufferDurationMs = engineConfig.audioBufferDurationMs;\n const maxParallelFetches = engineConfig.maxAudioBufferFetches;\n\n const currentConfig: AudioBufferConfig = {\n bufferDurationMs,\n maxParallelFetches,\n enableBuffering: host.enableAudioBuffering,\n bufferThresholdMs: engineConfig.bufferThresholdMs,\n };\n\n // Timeline context for priority-based buffering\n const timelineContext =\n host.rootTimegroup?.currentTimeMs !== undefined\n ? {\n elementStartMs: host.startTimeMs,\n elementEndMs: host.endTimeMs,\n playheadMs: host.rootTimegroup.currentTimeMs,\n }\n : undefined;\n\n return manageMediaBuffer<AudioRendition>(\n seekTimeMs,\n currentConfig,\n currentState,\n (host as any).intrinsicDurationMs || 10000,\n signal,\n {\n computeSegmentId: async (timeMs, rendition) => {\n // Use media engine's computeSegmentId\n try {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n if (!mediaEngine) return undefined;\n return mediaEngine.computeSegmentId(timeMs, rendition);\n } catch (error) {\n // If media engine task failed (no valid source), return undefined\n if (error instanceof Error && error.message === \"No valid media source\") {\n return undefined;\n }\n throw error;\n }\n },\n prefetchSegment: async (segmentId, rendition) => {\n // Trigger prefetch through BaseMediaEngine - let it handle caching\n try {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n if (!mediaEngine) return;\n \n // Check if the segment exists in AssetMediaEngine data before prefetching\n 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?.[rendition.trackId];\n if (!trackData?.segments || segmentId >= trackData.segments.length) {\n // Segment doesn't exist in the data - don't prefetch\n return;\n }\n }\n \n await mediaEngine.fetchMediaSegment(segmentId, rendition, signal);\n } catch (error) {\n // If media engine task failed, segment doesn't exist, or fetch fails (401, etc.), skip prefetch silently\n if (\n error instanceof Error &&\n (error.message === \"No valid media source\" ||\n error.message.includes(\"Media segment not found\") ||\n error.message.includes(\"Track not found\") ||\n error.message.includes(\"Failed to fetch\") ||\n error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n error.message.includes(\"File not found\"))\n ) {\n return;\n }\n throw error;\n }\n // Don't return data - just ensure it's cached in BaseMediaEngine\n },\n isSegmentCached: (segmentId, rendition) => {\n // Check if segment is already cached in BaseMediaEngine\n const mediaEngine = host.mediaEngineTask.value;\n if (!mediaEngine) return false;\n\n return mediaEngine.isSegmentCached(segmentId, rendition);\n },\n getRendition: async () => {\n try {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n if (!mediaEngine) {\n throw new Error(\"Audio rendition not available\");\n }\n const audioRendition = mediaEngine.audioRendition;\n if (!audioRendition) {\n throw new Error(\"Audio rendition not available\");\n }\n return audioRendition;\n } catch (error) {\n // If media engine task failed (no valid source), throw error for getRendition\n if (error instanceof Error && error.message === \"No valid media source\") {\n throw new Error(\"Audio rendition not available\");\n }\n throw error;\n }\n },\n logError: console.error,\n },\n timelineContext,\n );\n },\n });\n\n return task;\n};\n"],"mappings":"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"}
@@ -38,18 +38,16 @@ function interpolateData(data, targetSize) {
38
38
  }
39
39
  function makeAudioFrequencyAnalysisTask(element) {
40
40
  const cache = new LRUCache(100);
41
- return new Task(element, {
41
+ let task;
42
+ task = new Task(element, {
42
43
  autoRun: EF_INTERACTIVE,
43
44
  onError: (error) => {
45
+ task.taskComplete.catch(() => {});
46
+ 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;
47
+ 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;
44
48
  console.error("frequencyDataTask error", error);
45
49
  },
46
- args: () => [
47
- element.currentSourceTimeMs,
48
- element.fftSize,
49
- element.fftDecay,
50
- element.fftGain,
51
- element.shouldInterpolateFrequencies
52
- ],
50
+ args: () => [element.currentSourceTimeMs],
53
51
  task: async (_, { signal }) => {
54
52
  if (element.currentSourceTimeMs < 0) return null;
55
53
  const currentTimeMs = element.currentSourceTimeMs;
@@ -63,79 +61,106 @@ function makeAudioFrequencyAnalysisTask(element) {
63
61
  const preliminaryCacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${fromMs}:${currentTimeMs}`;
64
62
  const cachedSmoothedData = cache.get(preliminaryCacheKey);
65
63
  if (cachedSmoothedData) return cachedSmoothedData;
66
- const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
67
- const audioSpan = await fetchAudioSpan(element, fromMs, toMs, signal);
68
- if (!audioSpan || !audioSpan.blob) {
69
- console.warn("Frequency analysis skipped: no audio data available");
70
- return null;
64
+ if (element.mediaEngineTask.error) return null;
65
+ let mediaEngine;
66
+ try {
67
+ mediaEngine = await element.mediaEngineTask.taskComplete;
68
+ } catch (error) {
69
+ if (error instanceof Error && error.message === "No valid media source") return null;
70
+ throw error;
71
71
  }
72
- const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
73
- const arrayBuffer = await audioSpan.blob.arrayBuffer();
74
- const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
75
- const startOffsetMs = audioSpan.startMs;
76
- const framesData = await Promise.all(Array.from({ length: element.fftDecay }, async (_$1, i) => {
77
- const frameOffset = i * (1e3 / 30);
78
- const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
79
- const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
80
- const cachedFrame = cache.get(cacheKey);
81
- if (cachedFrame) return cachedFrame;
82
- const SIZE = 48e3 / 30;
83
- let audioContext;
72
+ signal?.throwIfAborted();
73
+ if (!mediaEngine?.audioRendition) return null;
74
+ if (element.audioInputTask.error) return null;
75
+ if (element.audioInputTask.value === void 0) return null;
76
+ const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
77
+ try {
78
+ const audioSpan = await fetchAudioSpan(element, fromMs, toMs, signal);
79
+ if (!audioSpan || !audioSpan.blob) return null;
80
+ if (audioSpan.blob.size < 100) return null;
81
+ const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
82
+ const arrayBuffer = await audioSpan.blob.arrayBuffer();
83
+ signal?.throwIfAborted();
84
+ if (arrayBuffer.byteLength < 100) return null;
85
+ let audioBuffer;
84
86
  try {
85
- audioContext = new OfflineAudioContext(2, SIZE, 48e3);
86
- } catch (error) {
87
- throw new Error(`[EFMedia.frequencyDataTask] Failed to create OfflineAudioContext(2, ${SIZE}, 48000) for frame ${i} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio frequency analysis.`);
87
+ audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
88
+ signal?.throwIfAborted();
89
+ } catch (decodeError) {
90
+ if (decodeError instanceof Error && decodeError.message.includes("Unable to decode audio data")) return null;
91
+ throw decodeError;
88
92
  }
89
- const analyser = audioContext.createAnalyser();
90
- analyser.fftSize = element.fftSize;
91
- analyser.minDecibels = -90;
92
- analyser.maxDecibels = -10;
93
- const gainNode = audioContext.createGain();
94
- gainNode.gain.value = element.fftGain;
95
- const filter = audioContext.createBiquadFilter();
96
- filter.type = "bandpass";
97
- filter.frequency.value = 15e3;
98
- filter.Q.value = .05;
99
- const audioBufferSource = audioContext.createBufferSource();
100
- audioBufferSource.buffer = audioBuffer;
101
- audioBufferSource.connect(filter);
102
- filter.connect(gainNode);
103
- gainNode.connect(analyser);
104
- analyser.connect(audioContext.destination);
105
- audioBufferSource.start(0, startTime, 1 / 30);
106
- try {
107
- await audioContext.startRendering();
108
- const frameData = new Uint8Array(element.fftSize / 2);
109
- analyser.getByteFrequencyData(frameData);
110
- cache.set(cacheKey, frameData);
111
- return frameData;
112
- } finally {
113
- audioBufferSource.disconnect();
114
- analyser.disconnect();
93
+ const startOffsetMs = audioSpan.startMs;
94
+ const framesData = await Promise.all(Array.from({ length: element.fftDecay }, async (_$1, i) => {
95
+ const frameOffset = i * (1e3 / 30);
96
+ const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
97
+ const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
98
+ const cachedFrame = cache.get(cacheKey);
99
+ if (cachedFrame) return cachedFrame;
100
+ const SIZE = 48e3 / 30;
101
+ let audioContext;
102
+ try {
103
+ audioContext = new OfflineAudioContext(2, SIZE, 48e3);
104
+ } catch (error) {
105
+ throw new Error(`[EFMedia.frequencyDataTask] Failed to create OfflineAudioContext(2, ${SIZE}, 48000) for frame ${i} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio frequency analysis.`);
106
+ }
107
+ const analyser = audioContext.createAnalyser();
108
+ analyser.fftSize = element.fftSize;
109
+ analyser.minDecibels = -90;
110
+ analyser.maxDecibels = -10;
111
+ const gainNode = audioContext.createGain();
112
+ gainNode.gain.value = element.fftGain;
113
+ const filter = audioContext.createBiquadFilter();
114
+ filter.type = "bandpass";
115
+ filter.frequency.value = 15e3;
116
+ filter.Q.value = .05;
117
+ const audioBufferSource = audioContext.createBufferSource();
118
+ audioBufferSource.buffer = audioBuffer;
119
+ audioBufferSource.connect(filter);
120
+ filter.connect(gainNode);
121
+ gainNode.connect(analyser);
122
+ analyser.connect(audioContext.destination);
123
+ audioBufferSource.start(0, startTime, 1 / 30);
124
+ try {
125
+ await audioContext.startRendering();
126
+ signal?.throwIfAborted();
127
+ const frameData = new Uint8Array(element.fftSize / 2);
128
+ analyser.getByteFrequencyData(frameData);
129
+ cache.set(cacheKey, frameData);
130
+ return frameData;
131
+ } finally {
132
+ audioBufferSource.disconnect();
133
+ analyser.disconnect();
134
+ }
135
+ }));
136
+ const frameLength = framesData[0]?.length ?? 0;
137
+ const smoothedData = new Uint8Array(frameLength);
138
+ for (let i = 0; i < frameLength; i++) {
139
+ let weightedSum = 0;
140
+ let weightSum = 0;
141
+ framesData.forEach((frame, frameIndex) => {
142
+ const decayWeight = DECAY_WEIGHT ** frameIndex;
143
+ weightedSum += (frame[i] ?? 0) * decayWeight;
144
+ weightSum += decayWeight;
145
+ });
146
+ smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
115
147
  }
116
- }));
117
- const frameLength = framesData[0]?.length ?? 0;
118
- const smoothedData = new Uint8Array(frameLength);
119
- for (let i = 0; i < frameLength; i++) {
120
- let weightedSum = 0;
121
- let weightSum = 0;
122
- framesData.forEach((frame, frameIndex) => {
123
- const decayWeight = DECAY_WEIGHT ** frameIndex;
124
- weightedSum += (frame[i] ?? 0) * decayWeight;
125
- weightSum += decayWeight;
148
+ smoothedData.forEach((value, i) => {
149
+ const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;
150
+ smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
126
151
  });
127
- smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
152
+ const slicedData = smoothedData.slice(0, Math.floor(smoothedData.length / 2));
153
+ const processedData = element.shouldInterpolateFrequencies ? processFFTData(slicedData) : slicedData;
154
+ cache.set(preliminaryCacheKey, processedData);
155
+ return processedData;
156
+ } catch (error) {
157
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
158
+ 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;
159
+ throw error;
128
160
  }
129
- smoothedData.forEach((value, i) => {
130
- const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;
131
- smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
132
- });
133
- const slicedData = smoothedData.slice(0, Math.floor(smoothedData.length / 2));
134
- const processedData = element.shouldInterpolateFrequencies ? processFFTData(slicedData) : slicedData;
135
- cache.set(preliminaryCacheKey, processedData);
136
- return processedData;
137
161
  }
138
162
  });
163
+ return task;
139
164
  }
140
165
 
141
166
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"makeAudioFrequencyAnalysisTask.js","names":["audioContext: OfflineAudioContext"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { EF_INTERACTIVE } from \"../../../EF_INTERACTIVE.js\";\nimport { LRUCache } from \"../../../utils/LRUCache.js\";\nimport type { EFMedia } from \"../../EFMedia.js\";\n\n// DECAY_WEIGHT constant - same as original\nconst DECAY_WEIGHT = 0.8;\n\nfunction processFFTData(\n fftData: Uint8Array,\n zeroThresholdPercent = 0.1,\n): Uint8Array {\n // Step 1: Determine the threshold for zeros\n const totalBins = fftData.length;\n const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);\n\n // Step 2: Interrogate the FFT output to find the cutoff point\n let zeroCount = 0;\n let cutoffIndex = totalBins; // Default to the end of the array\n\n for (let i = totalBins - 1; i >= 0; i--) {\n if (fftData[i] ?? 0 < 10) {\n zeroCount++;\n } else {\n // If we encounter a non-zero value, we can stop\n if (zeroCount >= zeroThresholdCount) {\n cutoffIndex = i + 1; // Include this index\n break;\n }\n }\n }\n\n if (cutoffIndex < zeroThresholdCount) {\n return fftData;\n }\n\n // Step 3: Resample the \"good\" portion of the data\n const goodData = fftData.slice(0, cutoffIndex);\n const resampledData = interpolateData(goodData, fftData.length);\n\n // Step 4: Attenuate the top 10% of interpolated samples\n const attenuationStartIndex = Math.floor(totalBins * 0.9);\n for (let i = attenuationStartIndex; i < totalBins; i++) {\n // Calculate attenuation factor that goes from 1 to 0 over the top 10%\n const attenuationProgress =\n (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;\n const attenuationFactor = Math.max(0, 1 - attenuationProgress);\n resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);\n }\n\n return resampledData;\n}\n\nfunction interpolateData(data: Uint8Array, targetSize: number): Uint8Array {\n const resampled = new Uint8Array(targetSize);\n const dataLength = data.length;\n\n for (let i = 0; i < targetSize; i++) {\n // Calculate the corresponding index in the original data\n const ratio = (i / (targetSize - 1)) * (dataLength - 1);\n const index = Math.floor(ratio);\n const fraction = ratio - index;\n\n // Handle edge cases\n if (index >= dataLength - 1) {\n resampled[i] = data[dataLength - 1] ?? 0; // Last value\n } else {\n // Linear interpolation\n resampled[i] = Math.round(\n (data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,\n );\n }\n }\n\n return resampled;\n}\n\nexport function makeAudioFrequencyAnalysisTask(element: EFMedia) {\n // Internal cache for this task instance (same as original #frequencyDataCache)\n const cache = new LRUCache<string, Uint8Array>(100);\n\n return new Task(element, {\n autoRun: EF_INTERACTIVE,\n onError: (error) => {\n console.error(\"frequencyDataTask 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 cachedSmoothedData = cache.get(preliminaryCacheKey);\n if (cachedSmoothedData) {\n return cachedSmoothedData;\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(\"Frequency 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 const framesData = await Promise.all(\n Array.from({ length: element.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(\n 0,\n (currentTimeMs - frameOffset - startOffsetMs) / 1000,\n );\n\n // Cache key for this specific frame\n const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;\n\n // Check cache for this specific frame\n const cachedFrame = cache.get(cacheKey);\n if (cachedFrame) {\n return cachedFrame;\n }\n\n // Running 48000 * (1 / 30) = 1600 broke something terrible, it came out as 0,\n // I'm assuming weird floating point nonsense to do with running on rosetta\n const SIZE = 48000 / 30;\n let audioContext: OfflineAudioContext;\n try {\n audioContext = new OfflineAudioContext(2, SIZE, 48000);\n } catch (error) {\n throw new Error(\n `[EFMedia.frequencyDataTask] Failed to create OfflineAudioContext(2, ${SIZE}, 48000) for frame ${i} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio frequency analysis.`,\n );\n }\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = element.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = element.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n const frameData = new Uint8Array(element.fftSize / 2);\n analyser.getByteFrequencyData(frameData);\n\n // Cache this frame's analysis\n cache.set(cacheKey, frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Combine frames with decay\n const smoothedData = new Uint8Array(frameLength);\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 // Apply frequency weights using instance FREQ_WEIGHTS\n smoothedData.forEach((value, i) => {\n const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;\n smoothedData[i] = Math.min(255, Math.round(value * freqWeight));\n });\n\n // Only return the lower half of the frequency data\n // The top half is zeroed out, which makes for aesthetically unpleasing waveforms\n const slicedData = smoothedData.slice(\n 0,\n Math.floor(smoothedData.length / 2),\n );\n const processedData = element.shouldInterpolateFrequencies\n ? processFFTData(slicedData)\n : slicedData;\n // Cache with the preliminary key so future requests can skip audio fetching\n cache.set(preliminaryCacheKey, processedData);\n return processedData;\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"makeAudioFrequencyAnalysisTask.js","names":["task: Task<readonly [number], Uint8Array | null>","audioContext: OfflineAudioContext"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { EF_INTERACTIVE } from \"../../../EF_INTERACTIVE.js\";\nimport { LRUCache } from \"../../../utils/LRUCache.js\";\nimport type { EFMedia } from \"../../EFMedia.js\";\n\n// DECAY_WEIGHT constant - same as original\nconst DECAY_WEIGHT = 0.8;\n\nfunction processFFTData(\n fftData: Uint8Array,\n zeroThresholdPercent = 0.1,\n): Uint8Array {\n // Step 1: Determine the threshold for zeros\n const totalBins = fftData.length;\n const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);\n\n // Step 2: Interrogate the FFT output to find the cutoff point\n let zeroCount = 0;\n let cutoffIndex = totalBins; // Default to the end of the array\n\n for (let i = totalBins - 1; i >= 0; i--) {\n if (fftData[i] ?? 0 < 10) {\n zeroCount++;\n } else {\n // If we encounter a non-zero value, we can stop\n if (zeroCount >= zeroThresholdCount) {\n cutoffIndex = i + 1; // Include this index\n break;\n }\n }\n }\n\n if (cutoffIndex < zeroThresholdCount) {\n return fftData;\n }\n\n // Step 3: Resample the \"good\" portion of the data\n const goodData = fftData.slice(0, cutoffIndex);\n const resampledData = interpolateData(goodData, fftData.length);\n\n // Step 4: Attenuate the top 10% of interpolated samples\n const attenuationStartIndex = Math.floor(totalBins * 0.9);\n for (let i = attenuationStartIndex; i < totalBins; i++) {\n // Calculate attenuation factor that goes from 1 to 0 over the top 10%\n const attenuationProgress =\n (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;\n const attenuationFactor = Math.max(0, 1 - attenuationProgress);\n resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);\n }\n\n return resampledData;\n}\n\nfunction interpolateData(data: Uint8Array, targetSize: number): Uint8Array {\n const resampled = new Uint8Array(targetSize);\n const dataLength = data.length;\n\n for (let i = 0; i < targetSize; i++) {\n // Calculate the corresponding index in the original data\n const ratio = (i / (targetSize - 1)) * (dataLength - 1);\n const index = Math.floor(ratio);\n const fraction = ratio - index;\n\n // Handle edge cases\n if (index >= dataLength - 1) {\n resampled[i] = data[dataLength - 1] ?? 0; // Last value\n } else {\n // Linear interpolation\n resampled[i] = Math.round(\n (data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,\n );\n }\n }\n\n return resampled;\n}\n\nexport function makeAudioFrequencyAnalysisTask(element: EFMedia): Task<readonly [number], Uint8Array | null> {\n // Internal cache for this task instance (same as original #frequencyDataCache)\n const cache = new LRUCache<string, Uint8Array>(100);\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 // 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(\"frequencyDataTask 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 cachedSmoothedData = cache.get(preliminaryCacheKey);\n if (cachedSmoothedData) {\n return cachedSmoothedData;\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 const framesData = await Promise.all(\n Array.from({ length: element.fftDecay }, async (_, i) => {\n const frameOffset = i * (1000 / 30);\n const startTime = Math.max(\n 0,\n (currentTimeMs - frameOffset - startOffsetMs) / 1000,\n );\n\n // Cache key for this specific frame\n const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;\n\n // Check cache for this specific frame\n const cachedFrame = cache.get(cacheKey);\n if (cachedFrame) {\n return cachedFrame;\n }\n\n // Running 48000 * (1 / 30) = 1600 broke something terrible, it came out as 0,\n // I'm assuming weird floating point nonsense to do with running on rosetta\n const SIZE = 48000 / 30;\n let audioContext: OfflineAudioContext;\n try {\n audioContext = new OfflineAudioContext(2, SIZE, 48000);\n } catch (error) {\n throw new Error(\n `[EFMedia.frequencyDataTask] Failed to create OfflineAudioContext(2, ${SIZE}, 48000) for frame ${i} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio frequency analysis.`,\n );\n }\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = element.fftSize;\n analyser.minDecibels = -90;\n analyser.maxDecibels = -10;\n\n const gainNode = audioContext.createGain();\n gainNode.gain.value = element.fftGain;\n\n const filter = audioContext.createBiquadFilter();\n filter.type = \"bandpass\";\n filter.frequency.value = 15000;\n filter.Q.value = 0.05;\n\n const audioBufferSource = audioContext.createBufferSource();\n audioBufferSource.buffer = audioBuffer;\n\n audioBufferSource.connect(filter);\n filter.connect(gainNode);\n gainNode.connect(analyser);\n analyser.connect(audioContext.destination);\n\n audioBufferSource.start(0, startTime, 1 / 30);\n\n try {\n await audioContext.startRendering();\n \n // Check for abort after expensive rendering operation\n signal?.throwIfAborted();\n \n const frameData = new Uint8Array(element.fftSize / 2);\n analyser.getByteFrequencyData(frameData);\n\n // Cache this frame's analysis\n cache.set(cacheKey, frameData);\n return frameData;\n } finally {\n audioBufferSource.disconnect();\n analyser.disconnect();\n }\n }),\n );\n\n const frameLength = framesData[0]?.length ?? 0;\n\n // Combine frames with decay\n const smoothedData = new Uint8Array(frameLength);\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 // Apply frequency weights using instance FREQ_WEIGHTS\n smoothedData.forEach((value, i) => {\n const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;\n smoothedData[i] = Math.min(255, Math.round(value * freqWeight));\n });\n\n // Only return the lower half of the frequency data\n // The top half is zeroed out, which makes for aesthetically unpleasing waveforms\n const slicedData = smoothedData.slice(\n 0,\n Math.floor(smoothedData.length / 2),\n );\n const processedData = element.shouldInterpolateFrequencies\n ? processFFTData(slicedData)\n : slicedData;\n // Cache with the preliminary key so future requests can skip audio fetching\n cache.set(preliminaryCacheKey, processedData);\n return processedData;\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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"}
@@ -1,21 +1,44 @@
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/makeAudioInitSegmentFetchTask.ts
5
6
  const makeAudioInitSegmentFetchTask = (host) => {
6
- return new Task(host, {
7
+ let task;
8
+ task = new Task(host, {
7
9
  args: () => [host.mediaEngineTask.value],
8
10
  onError: (error) => {
9
- console.error("audioInitSegmentFetchTask error", 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.includes("401") && !error.message.includes("Unauthorized") && !error.message.includes("audio rendition") && 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")) console.error("audioInitSegmentFetchTask error", error);
10
14
  },
11
15
  onComplete: (_value) => {},
12
- task: async ([_mediaEngine], { signal }) => {
13
- const mediaEngine = await getLatestMediaEngine(host, signal);
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;
14
26
  const audioRendition = mediaEngine.getAudioRendition();
15
27
  if (!audioRendition) return;
16
- return mediaEngine.fetchInitSegment(audioRendition, signal);
28
+ if (mediaEngine instanceof AssetMediaEngine) {
29
+ const trackData = mediaEngine.data?.[audioRendition.trackId];
30
+ if (!trackData || !trackData.initSegment) return;
31
+ }
32
+ try {
33
+ return await mediaEngine.fetchInitSegment(audioRendition, signal);
34
+ } catch (error) {
35
+ if (error instanceof DOMException && error.name === "AbortError") throw error;
36
+ if (error instanceof Error && (error.message.includes("401") || error.message.includes("Unauthorized") || error.message.includes("Failed to load resource") || error.message.includes("Init segment not found") || error.message.includes("Track not found") || error.message.includes("Failed to fetch"))) return;
37
+ throw error;
38
+ }
17
39
  }
18
40
  });
41
+ return task;
19
42
  };
20
43
 
21
44
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"makeAudioInitSegmentFetchTask.js","names":[],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.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 makeAudioInitSegmentFetchTask = (\n host: EFMedia,\n): Task<readonly [MediaEngine | undefined], ArrayBuffer | undefined> => {\n return new Task(host, {\n args: () => [host.mediaEngineTask.value] as const,\n onError: (error) => {\n console.error(\"audioInitSegmentFetchTask error\", error);\n },\n onComplete: (_value) => {},\n task: async ([_mediaEngine], { signal }) => {\n const mediaEngine = await getLatestMediaEngine(host, signal);\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.fetchInitSegment(audioRendition, signal);\n },\n });\n};\n"],"mappings":";;;;AAKA,MAAa,iCACX,SACsE;AACtE,QAAO,IAAI,KAAK,MAAM;EACpB,YAAY,CAAC,KAAK,gBAAgB,MAAM;EACxC,UAAU,UAAU;AAClB,WAAQ,MAAM,mCAAmC,MAAM;;EAEzD,aAAa,WAAW;EACxB,MAAM,OAAO,CAAC,eAAe,EAAE,aAAa;GAC1C,MAAM,cAAc,MAAM,qBAAqB,MAAM,OAAO;GAC5D,MAAM,iBAAiB,YAAY,mBAAmB;AAGtD,OAAI,CAAC,eACH;AAGF,UAAO,YAAY,iBAAiB,gBAAgB,OAAO;;EAE9D,CAAC"}
1
+ {"version":3,"file":"makeAudioInitSegmentFetchTask.js","names":["task: AudioInitSegmentFetchTask"],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.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 AudioInitSegmentFetchTask = Task<readonly [MediaEngine | undefined], ArrayBuffer | undefined>;\n\nexport const makeAudioInitSegmentFetchTask = (\n host: EFMedia,\n): AudioInitSegmentFetchTask => {\n // Capture task reference for use in onError\n let task: AudioInitSegmentFetchTask;\n\n task = new Task(host, {\n args: () => [host.mediaEngineTask.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 // Only log unexpected errors - 401/auth errors, missing audio, no valid source, file not found, and fetch failures are handled gracefully\n if (\n error instanceof Error &&\n !error.message.includes(\"401\") &&\n !error.message.includes(\"Unauthorized\") &&\n !error.message.includes(\"audio rendition\") &&\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 console.error(\"audioInitSegmentFetchTask error\", error);\n }\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 if (!audioRendition) {\n return undefined;\n }\n\n // Check if the track exists in AssetMediaEngine data before fetching init segment\n // This prevents fetch errors when tracks 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.initSegment) {\n // Track doesn't exist or has no init segment - don't fetch\n return undefined;\n }\n }\n\n try {\n return await mediaEngine.fetchInitSegment(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 // Handle 401/auth errors and missing segments gracefully\n // Return undefined instead of throwing to prevent error propagation\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"Unauthorized\") ||\n error.message.includes(\"Failed to load resource\") ||\n error.message.includes(\"Init segment not found\") ||\n error.message.includes(\"Track not found\") ||\n error.message.includes(\"Failed to fetch\"))\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,iCACX,SAC8B;CAE9B,IAAIA;AAEJ,QAAO,IAAI,KAAK,MAAM;EACpB,YAAY,CAAC,KAAK,gBAAgB,MAAM;EACxC,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,OACE,iBAAiB,SACjB,CAAC,MAAM,QAAQ,SAAS,MAAM,IAC9B,CAAC,MAAM,QAAQ,SAAS,eAAe,IACvC,CAAC,MAAM,QAAQ,SAAS,kBAAkB,IAC1C,MAAM,YAAY,2BAClB,CAAC,MAAM,QAAQ,SAAS,iBAAiB,IACzC,CAAC,MAAM,QAAQ,SAAS,oBAAoB,IAC5C,CAAC,MAAM,QAAQ,SAAS,kBAAkB,CAE1C,SAAQ,MAAM,mCAAmC,MAAM;;EAG3D,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;AAGtD,OAAI,CAAC,eACH;AAKF,OAAI,uBAAuB,kBAAkB;IAE3C,MAAM,YAAa,YAAoB,OAAO,eAAe;AAC7D,QAAI,CAAC,aAAa,CAAC,UAAU,YAE3B;;AAIJ,OAAI;AACF,WAAO,MAAM,YAAY,iBAAiB,gBAAgB,OAAO;YAC1D,OAAO;AAEd,QAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAIR,QACE,iBAAiB,UAChB,MAAM,QAAQ,SAAS,MAAM,IAC5B,MAAM,QAAQ,SAAS,eAAe,IACtC,MAAM,QAAQ,SAAS,0BAA0B,IACjD,MAAM,QAAQ,SAAS,yBAAyB,IAChD,MAAM,QAAQ,SAAS,kBAAkB,IACzC,MAAM,QAAQ,SAAS,kBAAkB,EAE3C;AAGF,UAAM;;;EAGX,CAAC;AAEF,QAAO"}
@@ -4,25 +4,36 @@ import { Task } from "@lit/task";
4
4
 
5
5
  //#region src/elements/EFMedia/audioTasks/makeAudioInputTask.ts
6
6
  const makeAudioInputTask = (host) => {
7
- return new Task(host, {
7
+ let task;
8
+ task = new Task(host, {
8
9
  args: () => [host.audioInitSegmentFetchTask.value, host.audioSegmentFetchTask.value],
9
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;
10
13
  console.error("audioInputTask error", error);
11
14
  },
12
15
  onComplete: (_value) => {},
13
16
  task: async (_, { signal }) => {
14
- const mediaEngine = await host.mediaEngineTask.taskComplete;
15
- if (signal.aborted) return void 0;
17
+ if (host.mediaEngineTask.error) return;
18
+ let mediaEngine;
19
+ try {
20
+ mediaEngine = await host.mediaEngineTask.taskComplete;
21
+ } catch (error) {
22
+ if (error instanceof Error && error.message === "No valid media source") return;
23
+ throw error;
24
+ }
25
+ signal?.throwIfAborted();
26
+ if (!mediaEngine) return;
16
27
  const audioRendition = mediaEngine?.audioRendition;
17
28
  if (!audioRendition) return;
18
29
  const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
19
- if (signal.aborted) return void 0;
30
+ signal?.throwIfAborted();
20
31
  const segment = await host.audioSegmentFetchTask.taskComplete;
21
- if (signal.aborted) return void 0;
32
+ signal?.throwIfAborted();
22
33
  if (!initSegment || !segment) return;
23
34
  const startTimeOffsetMs = audioRendition.startTimeOffsetMs;
24
35
  const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
25
- if (signal.aborted) return void 0;
36
+ signal?.throwIfAborted();
26
37
  return new BufferedSeekingInput(arrayBuffer, {
27
38
  videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
28
39
  audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
@@ -30,6 +41,7 @@ const makeAudioInputTask = (host) => {
30
41
  });
31
42
  }
32
43
  });
44
+ return task;
33
45
  };
34
46
 
35
47
  //#endregion