@editframe/elements 0.24.1-beta.0 → 0.25.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/dist/DelayedLoadingState.js +31 -0
  2. package/dist/DelayedLoadingState.js.map +1 -0
  3. package/dist/EF_FRAMEGEN.d.ts +50 -46
  4. package/dist/EF_FRAMEGEN.js +5 -1
  5. package/dist/EF_FRAMEGEN.js.map +1 -0
  6. package/dist/EF_INTERACTIVE.js +4 -0
  7. package/dist/EF_INTERACTIVE.js.map +1 -0
  8. package/dist/EF_RENDERING.js +4 -0
  9. package/dist/EF_RENDERING.js.map +1 -0
  10. package/dist/_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.js +4 -1
  11. package/dist/attachContextRoot.js +6 -1
  12. package/dist/attachContextRoot.js.map +1 -0
  13. package/dist/elements/CrossUpdateController.js +4 -0
  14. package/dist/elements/CrossUpdateController.js.map +1 -0
  15. package/dist/elements/EFAudio.d.ts +24 -16
  16. package/dist/elements/EFAudio.js +10 -1
  17. package/dist/elements/EFAudio.js.map +1 -0
  18. package/dist/elements/EFCaptions.d.ts +118 -109
  19. package/dist/elements/EFCaptions.js +11 -6
  20. package/dist/elements/EFCaptions.js.map +1 -0
  21. package/dist/elements/EFImage.d.ts +31 -20
  22. package/dist/elements/EFImage.js +6 -1
  23. package/dist/elements/EFImage.js.map +1 -0
  24. package/dist/elements/EFMedia/AssetIdMediaEngine.js +5 -0
  25. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +1 -0
  26. package/dist/elements/EFMedia/AssetMediaEngine.js +12 -0
  27. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -0
  28. package/dist/elements/EFMedia/BaseMediaEngine.js +53 -0
  29. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -0
  30. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +47 -46
  31. package/dist/elements/EFMedia/BufferedSeekingInput.js +6 -1
  32. package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -0
  33. package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
  34. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -0
  35. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +9 -13
  36. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +5 -0
  37. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js.map +1 -0
  38. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +6 -1
  39. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js.map +1 -0
  40. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +5 -0
  41. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js.map +1 -0
  42. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +5 -0
  43. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js.map +1 -0
  44. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +5 -0
  45. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js.map +1 -0
  46. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +5 -0
  47. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js.map +1 -0
  48. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +5 -0
  49. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js.map +1 -0
  50. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +6 -1
  51. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js.map +1 -0
  52. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +18 -2
  53. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -0
  54. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +9 -67
  55. package/dist/elements/EFMedia/shared/BufferUtils.js +15 -0
  56. package/dist/elements/EFMedia/shared/BufferUtils.js.map +1 -0
  57. package/dist/elements/EFMedia/shared/GlobalInputCache.js +29 -0
  58. package/dist/elements/EFMedia/shared/GlobalInputCache.js.map +1 -0
  59. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +11 -17
  60. package/dist/elements/EFMedia/shared/PrecisionUtils.js +25 -0
  61. package/dist/elements/EFMedia/shared/PrecisionUtils.js.map +1 -0
  62. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +22 -0
  63. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -0
  64. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +13 -0
  65. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js.map +1 -0
  66. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +21 -0
  67. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -0
  68. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +18 -0
  69. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -0
  70. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +10 -0
  71. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js.map +1 -0
  72. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +5 -0
  73. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js.map +1 -0
  74. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +5 -0
  75. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js.map +1 -0
  76. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +6 -1
  77. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js.map +1 -0
  78. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +5 -0
  79. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js.map +1 -0
  80. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +5 -0
  81. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js.map +1 -0
  82. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +16 -2
  83. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js.map +1 -0
  84. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +9 -13
  85. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +5 -0
  86. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js.map +1 -0
  87. package/dist/elements/EFMedia.d.ts +115 -104
  88. package/dist/elements/EFMedia.js +25 -1
  89. package/dist/elements/EFMedia.js.map +1 -0
  90. package/dist/elements/EFSourceMixin.d.ts +10 -11
  91. package/dist/elements/EFSourceMixin.js +5 -0
  92. package/dist/elements/EFSourceMixin.js.map +1 -0
  93. package/dist/elements/EFSurface.d.ts +35 -27
  94. package/dist/elements/EFSurface.js +6 -1
  95. package/dist/elements/EFSurface.js.map +1 -0
  96. package/dist/elements/EFTemporal.d.ts +200 -213
  97. package/dist/elements/EFTemporal.js +24 -4
  98. package/dist/elements/EFTemporal.js.map +1 -0
  99. package/dist/elements/EFThumbnailStrip.d.ts +91 -83
  100. package/dist/elements/EFThumbnailStrip.js +49 -4
  101. package/dist/elements/EFThumbnailStrip.js.map +1 -0
  102. package/dist/elements/EFTimegroup.d.ts +107 -101
  103. package/dist/elements/EFTimegroup.js +58 -3
  104. package/dist/elements/EFTimegroup.js.map +1 -0
  105. package/dist/elements/EFVideo.d.ts +120 -108
  106. package/dist/elements/EFVideo.js +46 -2
  107. package/dist/elements/EFVideo.js.map +1 -0
  108. package/dist/elements/EFWaveform.d.ts +48 -41
  109. package/dist/elements/EFWaveform.js +6 -1
  110. package/dist/elements/EFWaveform.js.map +1 -0
  111. package/dist/elements/FetchMixin.d.ts +8 -6
  112. package/dist/elements/FetchMixin.js +4 -0
  113. package/dist/elements/FetchMixin.js.map +1 -0
  114. package/dist/elements/SampleBuffer.d.ts +18 -13
  115. package/dist/elements/SampleBuffer.js +5 -0
  116. package/dist/elements/SampleBuffer.js.map +1 -0
  117. package/dist/elements/TargetController.d.ts +23 -24
  118. package/dist/elements/TargetController.js +8 -3
  119. package/dist/elements/TargetController.js.map +1 -0
  120. package/dist/elements/TimegroupController.d.ts +17 -12
  121. package/dist/elements/TimegroupController.js +4 -0
  122. package/dist/elements/TimegroupController.js.map +1 -0
  123. package/dist/elements/durationConverter.js +9 -4
  124. package/dist/elements/durationConverter.js.map +1 -0
  125. package/dist/elements/parseTimeToMs.js +4 -0
  126. package/dist/elements/parseTimeToMs.js.map +1 -0
  127. package/dist/elements/renderTemporalAudio.js +4 -0
  128. package/dist/elements/renderTemporalAudio.js.map +1 -0
  129. package/dist/elements/updateAnimations.js +29 -8
  130. package/dist/elements/updateAnimations.js.map +1 -0
  131. package/dist/elements-ZhsB7B5N.css +9 -0
  132. package/dist/elements-ZhsB7B5N.css.map +1 -0
  133. package/dist/getRenderInfo.d.ts +53 -47
  134. package/dist/getRenderInfo.js +5 -0
  135. package/dist/getRenderInfo.js.map +1 -0
  136. package/dist/gui/ContextMixin.d.ts +19 -20
  137. package/dist/gui/ContextMixin.js +32 -1
  138. package/dist/gui/ContextMixin.js.map +1 -0
  139. package/dist/gui/Controllable.d.ts +13 -14
  140. package/dist/gui/Controllable.js +5 -0
  141. package/dist/gui/Controllable.js.map +1 -0
  142. package/dist/gui/EFConfiguration.d.ts +18 -14
  143. package/dist/gui/EFConfiguration.js +6 -1
  144. package/dist/gui/EFConfiguration.js.map +1 -0
  145. package/dist/gui/EFControls.d.ts +35 -31
  146. package/dist/gui/EFControls.js +8 -3
  147. package/dist/gui/EFControls.js.map +1 -0
  148. package/dist/gui/EFDial.d.ts +23 -16
  149. package/dist/gui/EFDial.js +6 -1
  150. package/dist/gui/EFDial.js.map +1 -0
  151. package/dist/gui/EFFilmstrip.d.ts +183 -177
  152. package/dist/gui/EFFilmstrip.js +30 -25
  153. package/dist/gui/EFFilmstrip.js.map +1 -0
  154. package/dist/gui/EFFitScale.d.ts +30 -24
  155. package/dist/gui/EFFitScale.js +6 -1
  156. package/dist/gui/EFFitScale.js.map +1 -0
  157. package/dist/gui/EFFocusOverlay.d.ts +22 -14
  158. package/dist/gui/EFFocusOverlay.js +6 -1
  159. package/dist/gui/EFFocusOverlay.js.map +1 -0
  160. package/dist/gui/EFPause.d.ts +24 -18
  161. package/dist/gui/EFPause.js +6 -1
  162. package/dist/gui/EFPause.js.map +1 -0
  163. package/dist/gui/EFPlay.d.ts +24 -18
  164. package/dist/gui/EFPlay.js +6 -1
  165. package/dist/gui/EFPlay.js.map +1 -0
  166. package/dist/gui/EFPreview.d.ts +22 -15
  167. package/dist/gui/EFPreview.js +9 -1
  168. package/dist/gui/EFPreview.js.map +1 -0
  169. package/dist/gui/EFResizableBox.d.ts +39 -32
  170. package/dist/gui/EFResizableBox.js +8 -3
  171. package/dist/gui/EFResizableBox.js.map +1 -0
  172. package/dist/gui/EFScrubber.d.ts +31 -25
  173. package/dist/gui/EFScrubber.js +6 -1
  174. package/dist/gui/EFScrubber.js.map +1 -0
  175. package/dist/gui/EFTimeDisplay.d.ts +21 -14
  176. package/dist/gui/EFTimeDisplay.js +6 -1
  177. package/dist/gui/EFTimeDisplay.js.map +1 -0
  178. package/dist/gui/EFToggleLoop.d.ts +19 -13
  179. package/dist/gui/EFToggleLoop.js +6 -1
  180. package/dist/gui/EFToggleLoop.js.map +1 -0
  181. package/dist/gui/EFTogglePlay.d.ts +23 -17
  182. package/dist/gui/EFTogglePlay.js +6 -1
  183. package/dist/gui/EFTogglePlay.js.map +1 -0
  184. package/dist/gui/EFWorkbench.d.ts +24 -16
  185. package/dist/gui/EFWorkbench.js +6 -1
  186. package/dist/gui/EFWorkbench.js.map +1 -0
  187. package/dist/gui/PlaybackController.d.ts +54 -50
  188. package/dist/gui/PlaybackController.js +18 -0
  189. package/dist/gui/PlaybackController.js.map +1 -0
  190. package/dist/gui/TWMixin.js +5 -1
  191. package/dist/gui/TWMixin.js.map +1 -0
  192. package/dist/gui/TWMixin2.js +6 -1
  193. package/dist/gui/TWMixin2.js.map +1 -0
  194. package/dist/gui/TargetOrContextMixin.js +5 -0
  195. package/dist/gui/TargetOrContextMixin.js.map +1 -0
  196. package/dist/gui/currentTimeContext.js +5 -0
  197. package/dist/gui/currentTimeContext.js.map +1 -0
  198. package/dist/gui/durationContext.js +5 -0
  199. package/dist/gui/durationContext.js.map +1 -0
  200. package/dist/gui/efContext.js +5 -0
  201. package/dist/gui/efContext.js.map +1 -0
  202. package/dist/gui/fetchContext.js +5 -0
  203. package/dist/gui/fetchContext.js.map +1 -0
  204. package/dist/gui/focusContext.d.ts +6 -5
  205. package/dist/gui/focusContext.js +5 -0
  206. package/dist/gui/focusContext.js.map +1 -0
  207. package/dist/gui/focusedElementContext.js +5 -0
  208. package/dist/gui/focusedElementContext.js.map +1 -0
  209. package/dist/gui/playingContext.js +5 -0
  210. package/dist/gui/playingContext.js.map +1 -0
  211. package/dist/index.d.ts +27 -26
  212. package/dist/index.js +6 -1
  213. package/dist/index.js.map +1 -0
  214. package/dist/msToTimeCode.js +4 -0
  215. package/dist/msToTimeCode.js.map +1 -0
  216. package/dist/otel/BridgeSpanExporter.js +5 -0
  217. package/dist/otel/BridgeSpanExporter.js.map +1 -0
  218. package/dist/otel/setupBrowserTracing.js +7 -2
  219. package/dist/otel/setupBrowserTracing.js.map +1 -0
  220. package/dist/otel/tracingHelpers.d.ts +7 -34
  221. package/dist/otel/tracingHelpers.js +34 -2
  222. package/dist/otel/tracingHelpers.js.map +1 -0
  223. package/dist/transcoding/cache/RequestDeduplicator.js +25 -0
  224. package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -0
  225. package/dist/transcoding/cache/URLTokenDeduplicator.js +23 -0
  226. package/dist/transcoding/cache/URLTokenDeduplicator.js.map +1 -0
  227. package/dist/transcoding/types/index.d.ts +96 -270
  228. package/dist/transcoding/utils/UrlGenerator.d.ts +30 -25
  229. package/dist/transcoding/utils/UrlGenerator.js +19 -0
  230. package/dist/transcoding/utils/UrlGenerator.js.map +1 -0
  231. package/dist/utils/LRUCache.js +44 -0
  232. package/dist/utils/LRUCache.js.map +1 -0
  233. package/package.json +11 -24
  234. package/tsdown.config.ts +36 -0
  235. package/dist/DelayedLoadingState.d.ts +0 -48
  236. package/dist/DelayedLoadingState.integration.test.d.ts +0 -1
  237. package/dist/DelayedLoadingState.test.d.ts +0 -1
  238. package/dist/EF_INTERACTIVE.d.ts +0 -1
  239. package/dist/EF_RENDERING.d.ts +0 -1
  240. package/dist/LoadingDebounce.test.d.ts +0 -1
  241. package/dist/ManualScrubTest.test.d.ts +0 -1
  242. package/dist/ScrubResolvedFlashing.test.d.ts +0 -1
  243. package/dist/ScrubTrackManager.test.d.ts +0 -1
  244. package/dist/VideoSeekFlashing.browsertest.d.ts +0 -0
  245. package/dist/VideoStuckDiagnostic.test.d.ts +0 -1
  246. package/dist/attachContextRoot.d.ts +0 -1
  247. package/dist/elements/ContextProxiesController.d.ts +0 -39
  248. package/dist/elements/CrossUpdateController.d.ts +0 -8
  249. package/dist/elements/EFAudio.browsertest.d.ts +0 -0
  250. package/dist/elements/EFCaptions.browsertest.d.ts +0 -0
  251. package/dist/elements/EFImage.browsertest.d.ts +0 -0
  252. package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +0 -19
  253. package/dist/elements/EFMedia/AssetIdMediaEngine.test.d.ts +0 -1
  254. package/dist/elements/EFMedia/AssetMediaEngine.browsertest.d.ts +0 -0
  255. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +0 -56
  256. package/dist/elements/EFMedia/BaseMediaEngine.browsertest.d.ts +0 -1
  257. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +0 -103
  258. package/dist/elements/EFMedia/BufferedSeekingInput.browsertest.d.ts +0 -1
  259. package/dist/elements/EFMedia/JitMediaEngine.browsertest.d.ts +0 -0
  260. package/dist/elements/EFMedia/JitMediaEngine.d.ts +0 -46
  261. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.d.ts +0 -9
  262. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +0 -3
  263. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.d.ts +0 -9
  264. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +0 -4
  265. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.d.ts +0 -9
  266. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.d.ts +0 -3
  267. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts +0 -0
  268. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +0 -7
  269. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +0 -4
  270. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +0 -4
  271. package/dist/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.d.ts +0 -1
  272. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +0 -3
  273. package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +0 -7
  274. package/dist/elements/EFMedia/shared/GlobalInputCache.d.ts +0 -39
  275. package/dist/elements/EFMedia/shared/PrecisionUtils.d.ts +0 -28
  276. package/dist/elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts +0 -1
  277. package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +0 -11
  278. package/dist/elements/EFMedia/shared/ThumbnailExtractor.d.ts +0 -27
  279. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +0 -9
  280. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +0 -17
  281. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +0 -29
  282. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.d.ts +0 -25
  283. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.d.ts +0 -8
  284. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.d.ts +0 -4
  285. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.d.ts +0 -3
  286. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.d.ts +0 -6
  287. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.d.ts +0 -4
  288. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.d.ts +0 -4
  289. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.d.ts +0 -6
  290. package/dist/elements/EFMedia.browsertest.d.ts +0 -10
  291. package/dist/elements/EFSurface.browsertest.d.ts +0 -0
  292. package/dist/elements/EFTemporal.browsertest.d.ts +0 -22
  293. package/dist/elements/EFThumbnailStrip.browsertest.d.ts +0 -0
  294. package/dist/elements/EFThumbnailStrip.media-engine.browsertest.d.ts +0 -0
  295. package/dist/elements/EFTimegroup.browsertest.d.ts +0 -41
  296. package/dist/elements/EFVideo.browsertest.d.ts +0 -0
  297. package/dist/elements/FetchContext.browsertest.d.ts +0 -0
  298. package/dist/elements/TargetController.browsertest.d.ts +0 -19
  299. package/dist/elements/durationConverter.d.ts +0 -16
  300. package/dist/elements/parseTimeToMs.d.ts +0 -1
  301. package/dist/elements/printTaskStatus.d.ts +0 -2
  302. package/dist/elements/renderTemporalAudio.d.ts +0 -10
  303. package/dist/elements/updateAnimations.browsertest.d.ts +0 -13
  304. package/dist/elements/updateAnimations.d.ts +0 -24
  305. package/dist/elements/util.d.ts +0 -3
  306. package/dist/gui/ContextMixin.browsertest.d.ts +0 -15
  307. package/dist/gui/Controllable.browsertest.d.ts +0 -0
  308. package/dist/gui/EFControls.browsertest.d.ts +0 -11
  309. package/dist/gui/EFDial.browsertest.d.ts +0 -0
  310. package/dist/gui/EFFilmstrip.browsertest.d.ts +0 -11
  311. package/dist/gui/EFPause.browsertest.d.ts +0 -0
  312. package/dist/gui/EFPlay.browsertest.d.ts +0 -0
  313. package/dist/gui/EFResizableBox.browsertest.d.ts +0 -0
  314. package/dist/gui/EFTimeDisplay.browsertest.d.ts +0 -0
  315. package/dist/gui/TWMixin.d.ts +0 -2
  316. package/dist/gui/TargetOrContextMixin.d.ts +0 -10
  317. package/dist/gui/currentTimeContext.d.ts +0 -3
  318. package/dist/gui/durationContext.d.ts +0 -3
  319. package/dist/gui/efContext.d.ts +0 -4
  320. package/dist/gui/fetchContext.d.ts +0 -3
  321. package/dist/gui/focusedElementContext.d.ts +0 -3
  322. package/dist/gui/playingContext.d.ts +0 -6
  323. package/dist/msToTimeCode.d.ts +0 -1
  324. package/dist/otel/BridgeSpanExporter.d.ts +0 -13
  325. package/dist/otel/setupBrowserTracing.d.ts +0 -12
  326. package/dist/style.css +0 -2
  327. package/dist/transcoding/cache/RequestDeduplicator.d.ts +0 -29
  328. package/dist/transcoding/cache/RequestDeduplicator.test.d.ts +0 -1
  329. package/dist/transcoding/cache/URLTokenDeduplicator.d.ts +0 -38
  330. package/dist/transcoding/cache/URLTokenDeduplicator.test.d.ts +0 -1
  331. package/dist/transcoding/utils/MediaUtils.d.ts +0 -9
  332. package/dist/transcoding/utils/constants.d.ts +0 -27
  333. package/dist/utils/LRUCache.d.ts +0 -80
  334. package/dist/utils/LRUCache.test.d.ts +0 -1
  335. /package/dist/{LoadingIndicator.browsertest.d.ts → elements.js} +0 -0
@@ -1,21 +1,38 @@
1
1
  import { withSpan } from "../../otel/tracingHelpers.js";
2
2
  import { RequestDeduplicator } from "../../transcoding/cache/RequestDeduplicator.js";
3
3
  import { SizeAwareLRUCache } from "../../utils/LRUCache.js";
4
+
5
+ //#region src/elements/EFMedia/BaseMediaEngine.ts
4
6
  const mediaCache = new SizeAwareLRUCache(100 * 1024 * 1024);
5
7
  const globalRequestDeduplicator = new RequestDeduplicator();
6
8
  var BaseMediaEngine = class {
7
9
  constructor(host) {
8
10
  this.host = host;
9
11
  }
12
+ /**
13
+ * Get video rendition if available. Returns undefined for audio-only assets.
14
+ * Callers should handle undefined gracefully.
15
+ */
10
16
  getVideoRendition() {
11
17
  return this.videoRendition;
12
18
  }
19
+ /**
20
+ * Get audio rendition if available. Returns undefined for video-only assets.
21
+ * Callers should handle undefined gracefully.
22
+ */
13
23
  getAudioRendition() {
14
24
  return this.audioRendition;
15
25
  }
26
+ /**
27
+ * Generate cache key for segment requests
28
+ */
16
29
  getSegmentCacheKey(segmentId, rendition) {
17
30
  return `${rendition.src}-${rendition.id}-${segmentId}-${rendition.trackId}`;
18
31
  }
32
+ /**
33
+ * Unified fetch method with caching and global deduplication
34
+ * All requests (media, manifest, init segments) go through this method
35
+ */
19
36
  async fetchWithCache(url, options) {
20
37
  return withSpan("mediaEngine.fetchWithCache", {
21
38
  url: url.length > 100 ? `${url.substring(0, 100)}...` : url,
@@ -74,6 +91,11 @@ var BaseMediaEngine = class {
74
91
  return result;
75
92
  });
76
93
  }
94
+ /**
95
+ * Handles abort logic for a cached request without affecting the underlying fetch
96
+ * This allows multiple instances to share the same cached request while each
97
+ * manages their own abort behavior
98
+ */
77
99
  handleAbortForCachedRequest(promise, signal) {
78
100
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
79
101
  return Promise.race([promise, new Promise((_, reject) => {
@@ -113,22 +135,39 @@ var BaseMediaEngine = class {
113
135
  async fetchMediaCacheWithHeaders(url, headers, signal) {
114
136
  return this.fetchMediaWithHeaders(url, headers, signal);
115
137
  }
138
+ /**
139
+ * Fetch media segment with built-in deduplication
140
+ * Now uses global deduplication for all requests
141
+ */
116
142
  async fetchMediaSegmentWithDeduplication(segmentId, rendition, _signal) {
117
143
  const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
118
144
  return globalRequestDeduplicator.executeRequest(cacheKey, async () => {
119
145
  return this.fetchMediaSegment(segmentId, rendition);
120
146
  });
121
147
  }
148
+ /**
149
+ * Check if a segment is currently being fetched
150
+ */
122
151
  isSegmentBeingFetched(segmentId, rendition) {
123
152
  const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
124
153
  return globalRequestDeduplicator.isPending(cacheKey);
125
154
  }
155
+ /**
156
+ * Get count of active segment requests (for debugging/monitoring)
157
+ */
126
158
  getActiveSegmentRequestCount() {
127
159
  return globalRequestDeduplicator.getPendingCount();
128
160
  }
161
+ /**
162
+ * Cancel all active segment requests (for cleanup)
163
+ */
129
164
  cancelAllSegmentRequests() {
130
165
  globalRequestDeduplicator.clear();
131
166
  }
167
+ /**
168
+ * Calculate audio segments needed for a time range
169
+ * Each media engine implements this based on their segment structure
170
+ */
132
171
  calculateAudioSegmentRange(fromMs, toMs, rendition, durationMs) {
133
172
  if (fromMs >= toMs) return [];
134
173
  const segments = [];
@@ -166,6 +205,10 @@ var BaseMediaEngine = class {
166
205
  }
167
206
  return segments;
168
207
  }
208
+ /**
209
+ * Check if a segment is cached for a given rendition
210
+ * This needs to check the URL-based cache since that's where segments are actually stored
211
+ */
169
212
  isSegmentCached(segmentId, rendition) {
170
213
  try {
171
214
  const maybeJitEngine = this;
@@ -181,13 +224,23 @@ var BaseMediaEngine = class {
181
224
  return false;
182
225
  }
183
226
  }
227
+ /**
228
+ * Get cached segment IDs from a list for a given rendition
229
+ */
184
230
  getCachedSegments(segmentIds, rendition) {
185
231
  return new Set(segmentIds.filter((id) => this.isSegmentCached(id, rendition)));
186
232
  }
233
+ /**
234
+ * Extract thumbnail canvases at multiple timestamps efficiently
235
+ * Default implementation provides helpful error information
236
+ */
187
237
  async extractThumbnails(timestamps) {
188
238
  const engineName = this.constructor.name;
189
239
  console.warn(`${engineName}: extractThumbnails not properly implemented. This MediaEngine type does not support thumbnail generation. Supported engines: JitMediaEngine. Requested ${timestamps.length} thumbnail${timestamps.length === 1 ? "" : "s"}.`);
190
240
  return timestamps.map(() => null);
191
241
  }
192
242
  };
243
+
244
+ //#endregion
193
245
  export { BaseMediaEngine };
246
+ //# sourceMappingURL=BaseMediaEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseMediaEngine.js","names":["result","tEnd","segments: SegmentTimeRange[]"],"sources":["../../../src/elements/EFMedia/BaseMediaEngine.ts"],"sourcesContent":["import { withSpan } from \"../../otel/tracingHelpers.js\";\nimport { RequestDeduplicator } from \"../../transcoding/cache/RequestDeduplicator.js\";\nimport type {\n AudioRendition,\n SegmentTimeRange,\n ThumbnailResult,\n VideoRendition,\n} from \"../../transcoding/types\";\nimport { SizeAwareLRUCache } from \"../../utils/LRUCache.js\";\nimport type { EFMedia } from \"../EFMedia.js\";\nimport type { MediaRendition } from \"./shared/MediaTaskUtils.js\";\n\n// Global instances shared across all media engines\nexport const mediaCache = new SizeAwareLRUCache<string>(100 * 1024 * 1024); // 100MB cache limit\nexport const globalRequestDeduplicator = new RequestDeduplicator();\n\nexport abstract class BaseMediaEngine {\n protected host: EFMedia;\n\n constructor(host: EFMedia) {\n this.host = host;\n }\n\n abstract get videoRendition(): VideoRendition | undefined;\n abstract get audioRendition(): AudioRendition | undefined;\n\n /**\n * Get video rendition if available. Returns undefined for audio-only assets.\n * Callers should handle undefined gracefully.\n */\n getVideoRendition(): VideoRendition | undefined {\n return this.videoRendition;\n }\n\n /**\n * Get audio rendition if available. Returns undefined for video-only assets.\n * Callers should handle undefined gracefully.\n */\n getAudioRendition(): AudioRendition | undefined {\n return this.audioRendition;\n }\n\n /**\n * Generate cache key for segment requests\n */\n private getSegmentCacheKey(\n segmentId: number,\n rendition: { src: string; trackId: number | undefined; id?: string },\n ): string {\n return `${rendition.src}-${rendition.id}-${segmentId}-${rendition.trackId}`;\n }\n\n /**\n * Unified fetch method with caching and global deduplication\n * All requests (media, manifest, init segments) go through this method\n */\n protected async fetchWithCache(\n url: string,\n options: {\n responseType: \"arrayBuffer\" | \"json\";\n headers?: Record<string, string>;\n signal?: AbortSignal;\n },\n ): Promise<any> {\n return withSpan(\n \"mediaEngine.fetchWithCache\",\n {\n url: url.length > 100 ? `${url.substring(0, 100)}...` : url,\n responseType: options.responseType,\n hasHeaders: !!options.headers,\n },\n undefined,\n async (span) => {\n const t0 = performance.now();\n const { responseType, headers, signal } = options;\n\n // Create cache key that includes URL and headers for proper isolation\n // Note: We don't include signal in cache key as it would prevent proper deduplication\n const cacheKey = headers ? `${url}:${JSON.stringify(headers)}` : url;\n\n // Check cache first\n const t1 = performance.now();\n const cached = mediaCache.get(cacheKey);\n const t2 = performance.now();\n span.setAttribute(\"cacheLookupMs\", Math.round((t2 - t1) * 1000) / 1000);\n\n if (cached) {\n span.setAttribute(\"cacheHit\", true);\n // If we have a cached promise, we need to handle the caller's abort signal\n // without affecting the underlying request that other instances might be using\n if (signal) {\n const t3 = performance.now();\n const result = await this.handleAbortForCachedRequest(\n cached,\n signal,\n );\n const t4 = performance.now();\n span.setAttribute(\n \"handleAbortMs\",\n Math.round((t4 - t3) * 100) / 100,\n );\n span.setAttribute(\n \"totalCacheHitMs\",\n Math.round((t4 - t0) * 100) / 100,\n );\n return result;\n }\n span.setAttribute(\n \"totalCacheHitMs\",\n Math.round((t2 - t0) * 100) / 100,\n );\n return cached;\n }\n\n span.setAttribute(\"cacheHit\", false);\n\n // Use global deduplicator to prevent concurrent requests for the same resource\n // Note: We do NOT pass the signal to the deduplicator - each caller manages their own abort\n const promise = globalRequestDeduplicator.executeRequest(\n cacheKey,\n async () => {\n const fetchStart = performance.now();\n try {\n // Make the fetch request WITHOUT the signal - let each caller handle their own abort\n // This prevents one instance's abort from affecting other instances using the shared cache\n const response = await this.host.fetch(url, { headers });\n const fetchEnd = performance.now();\n span.setAttribute(\"fetchMs\", fetchEnd - fetchStart);\n\n if (responseType === \"json\") {\n return response.json();\n }\n const buffer = await response.arrayBuffer();\n span.setAttribute(\"sizeBytes\", buffer.byteLength);\n return buffer;\n } catch (error) {\n // If the request was aborted, don't cache the error\n if (\n error instanceof DOMException &&\n error.name === \"AbortError\"\n ) {\n // Remove from cache so other requests can retry\n mediaCache.delete(cacheKey);\n }\n throw error;\n }\n },\n );\n\n // Cache the promise (not the result) to handle concurrent requests\n mediaCache.set(cacheKey, promise);\n\n // Handle the case where the promise might be aborted\n promise.catch((error) => {\n // If the request was aborted, remove it from cache to prevent corrupted data\n if (error instanceof DOMException && error.name === \"AbortError\") {\n mediaCache.delete(cacheKey);\n }\n });\n\n // If the caller has a signal, handle abort logic without affecting the underlying request\n if (signal) {\n const result = await this.handleAbortForCachedRequest(\n promise,\n signal,\n );\n const tEnd = performance.now();\n span.setAttribute(\n \"totalFetchMs\",\n Math.round((tEnd - t0) * 100) / 100,\n );\n return result;\n }\n\n const result = await promise;\n const tEnd = performance.now();\n span.setAttribute(\"totalFetchMs\", Math.round((tEnd - t0) * 100) / 100);\n return result;\n },\n );\n }\n\n /**\n * Handles abort logic for a cached request without affecting the underlying fetch\n * This allows multiple instances to share the same cached request while each\n * manages their own abort behavior\n */\n private handleAbortForCachedRequest<T>(\n promise: Promise<T>,\n signal: AbortSignal,\n ): Promise<T> {\n // If signal is already aborted, reject immediately\n if (signal.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n\n // Return a promise that respects the caller's abort signal\n // but doesn't affect the underlying cached request\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n signal.addEventListener(\"abort\", () => {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n });\n }),\n ]);\n }\n\n // Public wrapper methods that delegate to fetchWithCache\n async fetchMedia(url: string, signal?: AbortSignal): Promise<ArrayBuffer> {\n // Check abort signal immediately before any processing\n if (signal?.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n return this.fetchWithCache(url, { responseType: \"arrayBuffer\", signal });\n }\n\n async fetchManifest(url: string, signal?: AbortSignal): Promise<any> {\n // Check abort signal immediately before any processing\n if (signal?.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n return this.fetchWithCache(url, { responseType: \"json\", signal });\n }\n\n async fetchMediaWithHeaders(\n url: string,\n headers: Record<string, string>,\n signal?: AbortSignal,\n ): Promise<ArrayBuffer> {\n // Check abort signal immediately before any processing\n if (signal?.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n return this.fetchWithCache(url, {\n responseType: \"arrayBuffer\",\n headers,\n signal,\n });\n }\n\n // Legacy methods for backward compatibility\n async fetchMediaCache(\n url: string,\n signal?: AbortSignal,\n ): Promise<ArrayBuffer> {\n return this.fetchMedia(url, signal);\n }\n\n async fetchManifestCache(url: string, signal?: AbortSignal): Promise<any> {\n return this.fetchManifest(url, signal);\n }\n\n async fetchMediaCacheWithHeaders(\n url: string,\n headers: Record<string, string>,\n signal?: AbortSignal,\n ): Promise<ArrayBuffer> {\n return this.fetchMediaWithHeaders(url, headers, signal);\n }\n\n /**\n * Abstract method for actual segment fetching - implemented by subclasses\n */\n abstract fetchMediaSegment(\n segmentId: number,\n rendition: { trackId: number | undefined; src: string },\n ): Promise<ArrayBuffer>;\n\n abstract fetchInitSegment(\n rendition: { trackId: number | undefined; src: string },\n signal: AbortSignal,\n ): Promise<ArrayBuffer>;\n\n abstract computeSegmentId(\n desiredSeekTimeMs: number,\n rendition: MediaRendition,\n ): number | undefined;\n\n /**\n * Fetch media segment with built-in deduplication\n * Now uses global deduplication for all requests\n */\n async fetchMediaSegmentWithDeduplication(\n segmentId: number,\n rendition: { trackId: number | undefined; src: string },\n _signal?: AbortSignal,\n ): Promise<ArrayBuffer> {\n const cacheKey = this.getSegmentCacheKey(segmentId, rendition);\n\n return globalRequestDeduplicator.executeRequest(cacheKey, async () => {\n return this.fetchMediaSegment(segmentId, rendition);\n });\n }\n\n /**\n * Check if a segment is currently being fetched\n */\n isSegmentBeingFetched(\n segmentId: number,\n rendition: { src: string; trackId: number | undefined },\n ): boolean {\n const cacheKey = this.getSegmentCacheKey(segmentId, rendition);\n return globalRequestDeduplicator.isPending(cacheKey);\n }\n\n /**\n * Get count of active segment requests (for debugging/monitoring)\n */\n getActiveSegmentRequestCount(): number {\n return globalRequestDeduplicator.getPendingCount();\n }\n\n /**\n * Cancel all active segment requests (for cleanup)\n */\n cancelAllSegmentRequests(): void {\n globalRequestDeduplicator.clear();\n }\n\n /**\n * Calculate audio segments needed for a time range\n * Each media engine implements this based on their segment structure\n */\n calculateAudioSegmentRange(\n fromMs: number,\n toMs: number,\n rendition: AudioRendition,\n durationMs: number,\n ): SegmentTimeRange[] {\n // Default implementation for uniform segments (used by JitMediaEngine)\n if (fromMs >= toMs) {\n return [];\n }\n\n const segments: SegmentTimeRange[] = [];\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 continue; // Skip undefined segment durations\n }\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = Math.min(\n cumulativeTime + segmentDuration,\n durationMs,\n );\n\n // Don't include segments that start at or beyond the file duration\n if (segmentStartMs >= durationMs) {\n break;\n }\n\n // Only include segments that overlap with requested time range\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n segments.push({\n segmentId: i + 1, // Convert to 1-based\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n\n cumulativeTime += segmentDuration;\n\n // If we've reached or exceeded file duration, stop\n if (cumulativeTime >= durationMs) {\n break;\n }\n }\n\n return segments;\n }\n\n // Fall back to fixed duration calculation for backward compatibility\n const segmentDurationMs = rendition.segmentDurationMs || 1000;\n const startSegmentIndex = Math.floor(fromMs / segmentDurationMs);\n const endSegmentIndex = Math.floor(toMs / segmentDurationMs);\n\n for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {\n const segmentId = i + 1; // Convert to 1-based\n const segmentStartMs = i * segmentDurationMs;\n const segmentEndMs = Math.min((i + 1) * segmentDurationMs, durationMs);\n\n // Don't include segments that start at or beyond the file duration\n if (segmentStartMs >= durationMs) {\n break;\n }\n\n // Only include segments that overlap with requested time range\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n segments.push({\n segmentId,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n }\n\n return segments;\n }\n\n /**\n * Check if a segment is cached for a given rendition\n * This needs to check the URL-based cache since that's where segments are actually stored\n */\n isSegmentCached(\n segmentId: number,\n rendition: AudioRendition | VideoRendition,\n ): boolean {\n try {\n // Check if this is a JIT engine by looking for urlGenerator property\n const maybeJitEngine = this as any;\n if (\n maybeJitEngine.urlGenerator &&\n typeof maybeJitEngine.urlGenerator.generateSegmentUrl === \"function\"\n ) {\n // This is a JIT engine - generate the URL and check URL-based cache\n if (!rendition.id) {\n return false;\n }\n\n const segmentUrl = maybeJitEngine.urlGenerator.generateSegmentUrl(\n segmentId,\n rendition.id,\n maybeJitEngine,\n );\n const urlIsCached = mediaCache.has(segmentUrl);\n\n return urlIsCached;\n }\n // For other engine types, fall back to the old segment-based key approach\n const cacheKey = `${rendition.src}-${rendition.id || \"default\"}-${segmentId}-${rendition.trackId}`;\n const isCached = mediaCache.has(cacheKey);\n return isCached;\n } catch (error) {\n console.warn(\n `🎬 BaseMediaEngine: Error checking if segment ${segmentId} is cached:`,\n error,\n );\n return false;\n }\n }\n\n /**\n * Get cached segment IDs from a list for a given rendition\n */\n getCachedSegments(\n segmentIds: number[],\n rendition: AudioRendition | VideoRendition,\n ): Set<number> {\n return new Set(\n segmentIds.filter((id) => this.isSegmentCached(id, rendition)),\n );\n }\n\n /**\n * Extract thumbnail canvases at multiple timestamps efficiently\n * Default implementation provides helpful error information\n */\n async extractThumbnails(\n timestamps: number[],\n ): Promise<(ThumbnailResult | null)[]> {\n const engineName = this.constructor.name;\n console.warn(\n `${engineName}: extractThumbnails not properly implemented. ` +\n \"This MediaEngine type does not support thumbnail generation. \" +\n \"Supported engines: JitMediaEngine. \" +\n `Requested ${timestamps.length} thumbnail${timestamps.length === 1 ? \"\" : \"s\"}.`,\n );\n return timestamps.map(() => null);\n }\n\n abstract convertToSegmentRelativeTimestamps(\n globalTimestamps: number[],\n segmentId: number,\n rendition: VideoRendition,\n ): number[];\n}\n"],"mappings":"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"}
@@ -1,49 +1,50 @@
1
- import { AudioSampleSink, InputAudioTrack, InputTrack, InputVideoTrack, VideoSampleSink } from 'mediabunny';
2
- import { MediaSample, SampleBuffer } from '../SampleBuffer';
1
+ import { MediaSample, SampleBuffer } from "../SampleBuffer.js";
2
+ import * as mediabunny3 from "mediabunny";
3
+ import { AudioSampleSink, InputAudioTrack, InputTrack, InputVideoTrack, VideoSampleSink } from "mediabunny";
4
+
5
+ //#region src/elements/EFMedia/BufferedSeekingInput.d.ts
3
6
  interface BufferedSeekingInputOptions {
4
- videoBufferSize?: number;
5
- audioBufferSize?: number;
6
- /**
7
- * Timeline offset in milliseconds to map user timeline to media timeline.
8
- * Applied during seeking to handle media that doesn't start at 0ms.
9
- */
10
- startTimeOffsetMs?: number;
7
+ videoBufferSize?: number;
8
+ audioBufferSize?: number;
9
+ /**
10
+ * Timeline offset in milliseconds to map user timeline to media timeline.
11
+ * Applied during seeking to handle media that doesn't start at 0ms.
12
+ */
13
+ startTimeOffsetMs?: number;
11
14
  }
12
- export declare class NoSample extends RangeError {
15
+ declare class BufferedSeekingInput {
16
+ #private;
17
+ private input;
18
+ private trackIterators;
19
+ private trackBuffers;
20
+ private options;
21
+ private trackIteratorCreationPromises;
22
+ private trackSeekPromises;
23
+ /**
24
+ * Timeline offset in milliseconds to map user timeline to media timeline.
25
+ * Applied during seeking to handle media that doesn't start at 0ms.
26
+ */
27
+ private readonly startTimeOffsetMs;
28
+ constructor(arrayBuffer: ArrayBuffer, options?: BufferedSeekingInputOptions);
29
+ getBufferSize(trackId: number): number;
30
+ getBufferContents(trackId: number): readonly MediaSample[];
31
+ getBufferTimestamps(trackId: number): number[];
32
+ clearBuffer(trackId: number): void;
33
+ computeDuration(): Promise<number>;
34
+ getTrack(trackId: number): Promise<InputTrack>;
35
+ getAudioTrack(trackId: number): Promise<InputAudioTrack>;
36
+ getVideoTrack(trackId: number): Promise<InputVideoTrack>;
37
+ getFirstVideoTrack(): Promise<InputVideoTrack | undefined>;
38
+ getFirstAudioTrack(): Promise<InputAudioTrack | undefined>;
39
+ getTrackIterator(track: InputTrack): AsyncIterator<MediaSample, any, any>;
40
+ createTrackSampleSink(track: InputTrack): AudioSampleSink | VideoSampleSink;
41
+ createTrackIterator(track: InputTrack): AsyncGenerator<mediabunny3.VideoSample, void, unknown> | AsyncGenerator<mediabunny3.AudioSample, void, unknown>;
42
+ createTrackBuffer(track: InputTrack): SampleBuffer;
43
+ getTrackBuffer(track: InputTrack): SampleBuffer;
44
+ seek(trackId: number, timeMs: number): Promise<MediaSample | undefined>;
45
+ private resetIterator;
46
+ private seekSafe;
13
47
  }
14
- export declare class ConcurrentSeekError extends RangeError {
15
- }
16
- export declare class BufferedSeekingInput {
17
- #private;
18
- private input;
19
- private trackIterators;
20
- private trackBuffers;
21
- private options;
22
- private trackIteratorCreationPromises;
23
- private trackSeekPromises;
24
- /**
25
- * Timeline offset in milliseconds to map user timeline to media timeline.
26
- * Applied during seeking to handle media that doesn't start at 0ms.
27
- */
28
- private readonly startTimeOffsetMs;
29
- constructor(arrayBuffer: ArrayBuffer, options?: BufferedSeekingInputOptions);
30
- getBufferSize(trackId: number): number;
31
- getBufferContents(trackId: number): readonly MediaSample[];
32
- getBufferTimestamps(trackId: number): number[];
33
- clearBuffer(trackId: number): void;
34
- computeDuration(): Promise<number>;
35
- getTrack(trackId: number): Promise<InputTrack>;
36
- getAudioTrack(trackId: number): Promise<InputAudioTrack>;
37
- getVideoTrack(trackId: number): Promise<InputVideoTrack>;
38
- getFirstVideoTrack(): Promise<InputVideoTrack | undefined>;
39
- getFirstAudioTrack(): Promise<InputAudioTrack | undefined>;
40
- getTrackIterator(track: InputTrack): AsyncIterator<MediaSample, any, any>;
41
- createTrackSampleSink(track: InputTrack): AudioSampleSink | VideoSampleSink;
42
- createTrackIterator(track: InputTrack): AsyncGenerator<import('mediabunny').VideoSample, void, unknown> | AsyncGenerator<import('mediabunny').AudioSample, void, unknown>;
43
- createTrackBuffer(track: InputTrack): SampleBuffer;
44
- getTrackBuffer(track: InputTrack): SampleBuffer;
45
- seek(trackId: number, timeMs: number): Promise<MediaSample | undefined>;
46
- private resetIterator;
47
- private seekSafe;
48
- }
49
- export {};
48
+ //#endregion
49
+ export { BufferedSeekingInput };
50
+ //# sourceMappingURL=BufferedSeekingInput.d.ts.map
@@ -2,7 +2,9 @@ import { withSpan } from "../../otel/tracingHelpers.js";
2
2
  import { roundToMilliseconds } from "./shared/PrecisionUtils.js";
3
3
  import { SampleBuffer } from "../SampleBuffer.js";
4
4
  import { AudioSampleSink, BufferSource, Input, InputAudioTrack, InputVideoTrack, MP4, VideoSampleSink } from "mediabunny";
5
- var defaultOptions = {
5
+
6
+ //#region src/elements/EFMedia/BufferedSeekingInput.ts
7
+ const defaultOptions = {
6
8
  videoBufferSize: 30,
7
9
  audioBufferSize: 100,
8
10
  startTimeOffsetMs: 0
@@ -230,4 +232,7 @@ var BufferedSeekingInput = class {
230
232
  });
231
233
  }
232
234
  };
235
+
236
+ //#endregion
233
237
  export { BufferedSeekingInput };
238
+ //# sourceMappingURL=BufferedSeekingInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BufferedSeekingInput.js","names":["defaultOptions: BufferedSeekingInputOptions","track","bufferSize","#seekLock","contents"],"sources":["../../../src/elements/EFMedia/BufferedSeekingInput.ts"],"sourcesContent":["import {\n AudioSampleSink,\n BufferSource,\n Input,\n InputAudioTrack,\n type InputTrack,\n InputVideoTrack,\n MP4,\n VideoSampleSink,\n} from \"mediabunny\";\nimport { withSpan } from \"../../otel/tracingHelpers.js\";\nimport { type MediaSample, SampleBuffer } from \"../SampleBuffer\";\nimport { roundToMilliseconds } from \"./shared/PrecisionUtils\";\n\ninterface BufferedSeekingInputOptions {\n videoBufferSize?: number;\n audioBufferSize?: number;\n /**\n * Timeline offset in milliseconds to map user timeline to media timeline.\n * Applied during seeking to handle media that doesn't start at 0ms.\n */\n startTimeOffsetMs?: number;\n}\n\nconst defaultOptions: BufferedSeekingInputOptions = {\n videoBufferSize: 30,\n audioBufferSize: 100,\n startTimeOffsetMs: 0,\n};\n\nexport class NoSample extends RangeError {}\n\nexport class ConcurrentSeekError extends RangeError {}\n\nexport class BufferedSeekingInput {\n private input: Input;\n private trackIterators: Map<number, AsyncIterator<MediaSample>> = new Map();\n private trackBuffers: Map<number, SampleBuffer> = new Map();\n private options: BufferedSeekingInputOptions;\n // Separate locks for different operation types to prevent unnecessary blocking\n private trackIteratorCreationPromises: Map<number, Promise<any>> = new Map();\n private trackSeekPromises: Map<number, Promise<any>> = new Map();\n\n /**\n * Timeline offset in milliseconds to map user timeline to media timeline.\n * Applied during seeking to handle media that doesn't start at 0ms.\n */\n private readonly startTimeOffsetMs: number;\n\n constructor(arrayBuffer: ArrayBuffer, options?: BufferedSeekingInputOptions) {\n const bufferSource = new BufferSource(arrayBuffer);\n const input = new Input({\n source: bufferSource,\n formats: [MP4],\n });\n this.input = input;\n this.options = { ...defaultOptions, ...options };\n this.startTimeOffsetMs = this.options.startTimeOffsetMs ?? 0;\n }\n\n // Buffer inspection API for testing\n getBufferSize(trackId: number): number {\n const buffer = this.trackBuffers.get(trackId);\n return buffer ? buffer.length : 0;\n }\n\n getBufferContents(trackId: number): readonly MediaSample[] {\n const buffer = this.trackBuffers.get(trackId);\n return buffer ? Object.freeze([...buffer.getContents()]) : [];\n }\n\n getBufferTimestamps(trackId: number): number[] {\n const contents = this.getBufferContents(trackId);\n return contents.map((sample) => sample.timestamp || 0);\n }\n\n clearBuffer(trackId: number): void {\n const buffer = this.trackBuffers.get(trackId);\n if (buffer) {\n buffer.clear();\n }\n }\n\n computeDuration() {\n return this.input.computeDuration();\n }\n\n async getTrack(trackId: number) {\n const tracks = await this.input.getTracks();\n const track = tracks.find((track) => track.id === trackId);\n if (!track) {\n throw new Error(`Track ${trackId} not found`);\n }\n return track;\n }\n\n async getAudioTrack(trackId: number) {\n const tracks = await this.input.getAudioTracks();\n const track = tracks.find(\n (track) => track.id === trackId && track.type === \"audio\",\n );\n if (!track) {\n throw new Error(`Track ${trackId} not found`);\n }\n return track;\n }\n\n async getVideoTrack(trackId: number) {\n const tracks = await this.input.getVideoTracks();\n const track = tracks.find(\n (track) => track.id === trackId && track.type === \"video\",\n );\n if (!track) {\n throw new Error(`Track ${trackId} not found`);\n }\n return track;\n }\n\n async getFirstVideoTrack() {\n const tracks = await this.input.getVideoTracks();\n return tracks[0];\n }\n\n async getFirstAudioTrack() {\n const tracks = await this.input.getAudioTracks();\n return tracks[0];\n }\n\n getTrackIterator(track: InputTrack) {\n if (this.trackIterators.has(track.id)) {\n // biome-ignore lint/style/noNonNullAssertion: we know the map has the key\n return this.trackIterators.get(track.id)!;\n }\n\n const trackIterator = this.createTrackIterator(track);\n\n this.trackIterators.set(track.id, trackIterator);\n\n return trackIterator;\n }\n\n createTrackSampleSink(track: InputTrack) {\n if (track instanceof InputAudioTrack) {\n return new AudioSampleSink(track);\n }\n if (track instanceof InputVideoTrack) {\n return new VideoSampleSink(track);\n }\n throw new Error(`Unsupported track type ${track.type}`);\n }\n\n createTrackIterator(track: InputTrack) {\n const sampleSink = this.createTrackSampleSink(track);\n return sampleSink.samples();\n }\n\n createTrackBuffer(track: InputTrack) {\n if (track.type === \"audio\") {\n const bufferSize = this.options.audioBufferSize;\n const sampleBuffer = new SampleBuffer(bufferSize);\n return sampleBuffer;\n }\n const bufferSize = this.options.videoBufferSize;\n const sampleBuffer = new SampleBuffer(bufferSize);\n return sampleBuffer;\n }\n\n getTrackBuffer(track: InputTrack) {\n const maybeTrackBuffer = this.trackBuffers.get(track.id);\n\n if (maybeTrackBuffer) {\n return maybeTrackBuffer;\n }\n\n const trackBuffer = this.createTrackBuffer(track);\n this.trackBuffers.set(track.id, trackBuffer);\n return trackBuffer;\n }\n\n async seek(trackId: number, timeMs: number) {\n return withSpan(\n \"bufferedInput.seek\",\n {\n trackId,\n timeMs,\n startTimeOffsetMs: this.startTimeOffsetMs,\n },\n undefined,\n async (span) => {\n // Apply timeline offset to map user timeline to media timeline\n const mediaTimeMs = timeMs + this.startTimeOffsetMs;\n\n // Round using consistent precision handling\n const roundedMediaTimeMs = roundToMilliseconds(mediaTimeMs);\n span.setAttribute(\"roundedMediaTimeMs\", roundedMediaTimeMs);\n\n // Serialize seek operations per track (but don't block iterator creation)\n const existingSeek = this.trackSeekPromises.get(trackId);\n if (existingSeek) {\n span.setAttribute(\"waitedForExistingSeek\", true);\n await existingSeek;\n }\n\n const seekPromise = this.seekSafe(trackId, roundedMediaTimeMs);\n this.trackSeekPromises.set(trackId, seekPromise);\n\n try {\n return await seekPromise;\n } finally {\n this.trackSeekPromises.delete(trackId);\n }\n },\n );\n }\n\n private async resetIterator(track: InputTrack) {\n const trackBuffer = this.trackBuffers.get(track.id);\n trackBuffer?.clear();\n // Clean up iterator safely - wait for any ongoing iterator creation\n const ongoingIteratorCreation = this.trackIteratorCreationPromises.get(\n track.id,\n );\n if (ongoingIteratorCreation) {\n await ongoingIteratorCreation;\n }\n\n const iterator = this.trackIterators.get(track.id);\n if (iterator) {\n try {\n await iterator.return?.();\n } catch (_error) {\n // Iterator cleanup failed, continue anyway\n }\n }\n this.trackIterators.delete(track.id);\n }\n\n #seekLock?: PromiseWithResolvers<void>;\n\n private async seekSafe(trackId: number, timeMs: number) {\n return withSpan(\n \"bufferedInput.seekSafe\",\n {\n trackId,\n timeMs,\n },\n undefined,\n async (span) => {\n if (this.#seekLock) {\n span.setAttribute(\"waitedForSeekLock\", true);\n await this.#seekLock.promise;\n }\n const seekLock = Promise.withResolvers<void>();\n this.#seekLock = seekLock;\n\n try {\n const track = await this.getTrack(trackId);\n span.setAttribute(\"trackType\", track.type);\n\n const trackBuffer = this.getTrackBuffer(track);\n\n const roundedTimeMs = roundToMilliseconds(timeMs);\n const firstTimestampMs = roundToMilliseconds(\n (await track.getFirstTimestamp()) * 1000,\n );\n span.setAttribute(\"firstTimestampMs\", firstTimestampMs);\n\n if (roundedTimeMs < firstTimestampMs) {\n console.error(\"Seeking outside bounds of input\", {\n roundedTimeMs,\n firstTimestampMs,\n });\n throw new NoSample(\n `Seeking outside bounds of input ${roundedTimeMs} < ${firstTimestampMs}`,\n );\n }\n\n // Check if we need to reset iterator for seeks outside current buffer range\n const bufferContents = trackBuffer.getContents();\n span.setAttribute(\"bufferContentsLength\", bufferContents.length);\n\n if (bufferContents.length > 0) {\n const bufferStartMs = roundToMilliseconds(\n trackBuffer.firstTimestamp * 1000,\n );\n span.setAttribute(\"bufferStartMs\", bufferStartMs);\n\n if (roundedTimeMs < bufferStartMs) {\n span.setAttribute(\"resetIterator\", true);\n await this.resetIterator(track);\n }\n }\n\n const alreadyInBuffer = trackBuffer.find(timeMs);\n if (alreadyInBuffer) {\n span.setAttribute(\"foundInBuffer\", true);\n span.setAttribute(\"bufferSize\", trackBuffer.length);\n const contents = trackBuffer.getContents();\n if (contents.length > 0) {\n span.setAttribute(\n \"bufferTimestamps\",\n contents\n .map((s) => Math.round((s.timestamp || 0) * 1000))\n .slice(0, 10)\n .join(\",\"),\n );\n }\n return alreadyInBuffer;\n }\n\n // Buffer miss - record buffer state\n span.setAttribute(\"foundInBuffer\", false);\n span.setAttribute(\"bufferSize\", trackBuffer.length);\n span.setAttribute(\"requestedTimeMs\", Math.round(timeMs));\n\n const contents = trackBuffer.getContents();\n if (contents.length > 0) {\n const firstSample = contents[0];\n const lastSample = contents[contents.length - 1];\n if (firstSample && lastSample) {\n const bufferStartMs = Math.round(\n (firstSample.timestamp || 0) * 1000,\n );\n const bufferEndMs = Math.round(\n ((lastSample.timestamp || 0) + (lastSample.duration || 0)) *\n 1000,\n );\n span.setAttribute(\"bufferStartMs\", bufferStartMs);\n span.setAttribute(\"bufferEndMs\", bufferEndMs);\n span.setAttribute(\n \"bufferRangeMs\",\n `${bufferStartMs}-${bufferEndMs}`,\n );\n }\n }\n\n const iterator = this.getTrackIterator(track);\n let iterationCount = 0;\n const decodeStart = performance.now();\n\n while (true) {\n iterationCount++;\n const iterStart = performance.now();\n const { done, value: decodedSample } = await iterator.next();\n const iterEnd = performance.now();\n\n // Record individual iteration timing for first 5 iterations\n if (iterationCount <= 5) {\n span.setAttribute(\n `iter${iterationCount}Ms`,\n Math.round((iterEnd - iterStart) * 100) / 100,\n );\n }\n\n if (decodedSample) {\n trackBuffer.push(decodedSample);\n if (iterationCount <= 5) {\n span.setAttribute(\n `iter${iterationCount}Timestamp`,\n Math.round((decodedSample.timestamp || 0) * 1000),\n );\n }\n }\n\n const foundSample = trackBuffer.find(roundedTimeMs);\n if (foundSample) {\n const decodeEnd = performance.now();\n span.setAttribute(\"iterationCount\", iterationCount);\n span.setAttribute(\n \"decodeMs\",\n Math.round((decodeEnd - decodeStart) * 100) / 100,\n );\n span.setAttribute(\n \"avgIterMs\",\n Math.round(((decodeEnd - decodeStart) / iterationCount) * 100) /\n 100,\n );\n span.setAttribute(\"foundSample\", true);\n span.setAttribute(\n \"foundTimestamp\",\n Math.round((foundSample.timestamp || 0) * 1000),\n );\n return foundSample;\n }\n if (done) {\n break;\n }\n }\n\n span.setAttribute(\"iterationCount\", iterationCount);\n span.setAttribute(\"reachedEnd\", true);\n\n // Check if we're seeking to the exact end of the track (legitimate use case)\n const finalBufferContents = trackBuffer.getContents();\n if (finalBufferContents.length > 0) {\n const lastSample =\n finalBufferContents[finalBufferContents.length - 1];\n const lastSampleEndMs = roundToMilliseconds(\n ((lastSample?.timestamp || 0) + (lastSample?.duration || 0)) *\n 1000,\n );\n\n // Only return last sample if seeking to exactly the track duration\n // (end of video) AND we have the final segment loaded\n const trackDurationMs = (await track.computeDuration()) * 1000;\n const isSeekingToTrackEnd =\n roundToMilliseconds(timeMs) ===\n roundToMilliseconds(trackDurationMs);\n const isAtEndOfTrack =\n roundToMilliseconds(timeMs) >= lastSampleEndMs;\n\n if (isSeekingToTrackEnd && isAtEndOfTrack) {\n span.setAttribute(\"returnedLastSample\", true);\n return lastSample;\n }\n }\n\n // For all other cases (seeking within track but outside buffer range), throw error\n // The caller should ensure the correct segment is loaded before seeking\n throw new NoSample(\n `Sample not found for time ${timeMs} in ${track.type} track ${trackId}`,\n );\n } finally {\n this.#seekLock = undefined;\n seekLock.resolve();\n }\n },\n );\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { BaseMediaEngine } from "./BaseMediaEngine.js";
2
2
  import { ThumbnailExtractor } from "./shared/ThumbnailExtractor.js";
3
+
4
+ //#region src/elements/EFMedia/JitMediaEngine.ts
3
5
  var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
4
6
  static async fetch(host, urlGenerator, url) {
5
7
  const engine = new JitMediaEngine(host, urlGenerator);
@@ -88,6 +90,10 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
88
90
  segmentDurationsMs: scrubManifestRendition.segmentDurationsMs
89
91
  };
90
92
  }
93
+ /**
94
+ * Get preferred buffer configuration for JIT transcoding
95
+ * Uses higher buffering since transcoding introduces latency
96
+ */
91
97
  getBufferConfig() {
92
98
  return {
93
99
  videoBufferDurationMs: 8e3,
@@ -96,6 +102,9 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
96
102
  maxAudioBufferFetches: 3
97
103
  };
98
104
  }
105
+ /**
106
+ * Extract thumbnail canvases using same rendition priority as video playback for frame alignment
107
+ */
99
108
  async extractThumbnails(timestamps) {
100
109
  let rendition;
101
110
  try {
@@ -116,4 +125,7 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
116
125
  return globalTimestamps.map((timestamp) => timestamp / 1e3);
117
126
  }
118
127
  };
128
+
129
+ //#endregion
119
130
  export { JitMediaEngine };
131
+ //# sourceMappingURL=JitMediaEngine.js.map
@@ -0,0 +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 };\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,16 +1,12 @@
1
- import { Task } from '@lit/task';
2
- import { EFMedia } from '../../EFMedia';
3
- import { MediaBufferConfig, MediaBufferState } from '../shared/BufferUtils';
4
- /**
5
- * Configuration for audio buffering - extends the generic interface
6
- */
7
- export interface AudioBufferConfig extends MediaBufferConfig {
8
- }
1
+ import { MediaBufferState } from "../shared/BufferUtils.js";
2
+ import { Task } from "@lit/task";
3
+
4
+ //#region src/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts
5
+
9
6
  /**
10
7
  * State of the audio buffer - uses the generic interface
11
8
  */
12
- export interface AudioBufferState extends MediaBufferState {
13
- }
14
- type AudioBufferTask = Task<readonly [number], AudioBufferState>;
15
- export declare const makeAudioBufferTask: (host: EFMedia) => AudioBufferTask;
16
- export {};
9
+ interface AudioBufferState extends MediaBufferState {}
10
+ //#endregion
11
+ export { AudioBufferState };
12
+ //# sourceMappingURL=makeAudioBufferTask.d.ts.map
@@ -3,6 +3,8 @@ import { EF_RENDERING } from "../../../EF_RENDERING.js";
3
3
  import { manageMediaBuffer } from "../shared/BufferUtils.js";
4
4
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
5
5
  import { Task } from "@lit/task";
6
+
7
+ //#region src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts
6
8
  const makeAudioBufferTask = (host) => {
7
9
  let currentState = {
8
10
  currentSeekTimeMs: 0,
@@ -50,4 +52,7 @@ const makeAudioBufferTask = (host) => {
50
52
  }
51
53
  });
52
54
  };
55
+
56
+ //#endregion
53
57
  export { makeAudioBufferTask };
58
+ //# sourceMappingURL=makeAudioBufferTask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"makeAudioBufferTask.js","names":["currentState: AudioBufferState","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 };\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 );\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;AAUlD,UAAO,kBACL,YAPuC;IACvC,kBAJuB,aAAa;IAKpC,oBAJyB,aAAa;IAKtC,iBAAiB,KAAK;IACvB,EAKC,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,CACF;;EAEJ,CAAC"}
@@ -1,7 +1,9 @@
1
1
  import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
2
2
  import { LRUCache } from "../../../utils/LRUCache.js";
3
3
  import { Task } from "@lit/task";
4
- var DECAY_WEIGHT = .8;
4
+
5
+ //#region src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts
6
+ const DECAY_WEIGHT = .8;
5
7
  function processFFTData(fftData, zeroThresholdPercent = .1) {
6
8
  const totalBins = fftData.length;
7
9
  const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
@@ -135,4 +137,7 @@ function makeAudioFrequencyAnalysisTask(element) {
135
137
  }
136
138
  });
137
139
  }
140
+
141
+ //#endregion
138
142
  export { makeAudioFrequencyAnalysisTask };
143
+ //# sourceMappingURL=makeAudioFrequencyAnalysisTask.js.map
@@ -0,0 +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,5 +1,7 @@
1
1
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
2
  import { Task } from "@lit/task";
3
+
4
+ //#region src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts
3
5
  const makeAudioInitSegmentFetchTask = (host) => {
4
6
  return new Task(host, {
5
7
  args: () => [host.mediaEngineTask.value],
@@ -15,4 +17,7 @@ const makeAudioInitSegmentFetchTask = (host) => {
15
17
  }
16
18
  });
17
19
  };
20
+
21
+ //#endregion
18
22
  export { makeAudioInitSegmentFetchTask };
23
+ //# sourceMappingURL=makeAudioInitSegmentFetchTask.js.map
@@ -0,0 +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,6 +1,8 @@
1
1
  import { BufferedSeekingInput } from "../BufferedSeekingInput.js";
2
2
  import { EFMedia } from "../../EFMedia.js";
3
3
  import { Task } from "@lit/task";
4
+
5
+ //#region src/elements/EFMedia/audioTasks/makeAudioInputTask.ts
4
6
  const makeAudioInputTask = (host) => {
5
7
  return new Task(host, {
6
8
  args: () => [host.audioInitSegmentFetchTask.value, host.audioSegmentFetchTask.value],
@@ -29,4 +31,7 @@ const makeAudioInputTask = (host) => {
29
31
  }
30
32
  });
31
33
  };
34
+
35
+ //#endregion
32
36
  export { makeAudioInputTask };
37
+ //# sourceMappingURL=makeAudioInputTask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"makeAudioInputTask.js","names":[],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioInputTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport { EFMedia } from \"../../EFMedia\";\nimport { BufferedSeekingInput } from \"../BufferedSeekingInput\";\nimport type { InputTask } from \"../shared/MediaTaskUtils\";\n\nexport const makeAudioInputTask = (host: EFMedia): InputTask => {\n return new Task<\n readonly [ArrayBuffer | undefined, ArrayBuffer | undefined],\n BufferedSeekingInput | undefined\n >(host, {\n args: () =>\n [\n host.audioInitSegmentFetchTask.value,\n host.audioSegmentFetchTask.value,\n ] as const,\n onError: (error) => {\n console.error(\"audioInputTask error\", error);\n },\n onComplete: (_value) => {},\n task: async (_, { signal }) => {\n const mediaEngine = await host.mediaEngineTask.taskComplete;\n if (signal.aborted) return undefined;\n\n const audioRendition = mediaEngine?.audioRendition;\n\n // Return undefined if no audio rendition available (video-only asset)\n if (!audioRendition) {\n return undefined;\n }\n\n const initSegment = await host.audioInitSegmentFetchTask.taskComplete;\n if (signal.aborted) return undefined;\n\n const segment = await host.audioSegmentFetchTask.taskComplete;\n if (signal.aborted) return undefined;\n\n if (!initSegment || !segment) {\n return undefined;\n }\n\n const startTimeOffsetMs = audioRendition.startTimeOffsetMs;\n\n const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();\n if (signal.aborted) return undefined;\n\n return new BufferedSeekingInput(arrayBuffer, {\n videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,\n audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,\n startTimeOffsetMs,\n });\n },\n });\n};\n"],"mappings":";;;;;AAKA,MAAa,sBAAsB,SAA6B;AAC9D,QAAO,IAAI,KAGT,MAAM;EACN,YACE,CACE,KAAK,0BAA0B,OAC/B,KAAK,sBAAsB,MAC5B;EACH,UAAU,UAAU;AAClB,WAAQ,MAAM,wBAAwB,MAAM;;EAE9C,aAAa,WAAW;EACxB,MAAM,OAAO,GAAG,EAAE,aAAa;GAC7B,MAAM,cAAc,MAAM,KAAK,gBAAgB;AAC/C,OAAI,OAAO,QAAS,QAAO;GAE3B,MAAM,iBAAiB,aAAa;AAGpC,OAAI,CAAC,eACH;GAGF,MAAM,cAAc,MAAM,KAAK,0BAA0B;AACzD,OAAI,OAAO,QAAS,QAAO;GAE3B,MAAM,UAAU,MAAM,KAAK,sBAAsB;AACjD,OAAI,OAAO,QAAS,QAAO;AAE3B,OAAI,CAAC,eAAe,CAAC,QACnB;GAGF,MAAM,oBAAoB,eAAe;GAEzC,MAAM,cAAc,MAAM,IAAI,KAAK,CAAC,aAAa,QAAQ,CAAC,CAAC,aAAa;AACxE,OAAI,OAAO,QAAS,QAAO;AAE3B,UAAO,IAAI,qBAAqB,aAAa;IAC3C,iBAAiB,QAAQ;IACzB,iBAAiB,QAAQ;IACzB;IACD,CAAC;;EAEL,CAAC"}
@@ -1,5 +1,7 @@
1
1
  import { IgnorableError } from "../../EFMedia.js";
2
2
  import { Task } from "@lit/task";
3
+
4
+ //#region src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts
3
5
  const makeAudioSeekTask = (host) => {
4
6
  return new Task(host, {
5
7
  args: () => [host.desiredSeekTimeMs, host.audioInputTask.value],
@@ -16,4 +18,7 @@ const makeAudioSeekTask = (host) => {
16
18
  task: async () => {}
17
19
  });
18
20
  };
21
+
22
+ //#endregion
19
23
  export { makeAudioSeekTask };
24
+ //# sourceMappingURL=makeAudioSeekTask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"makeAudioSeekTask.js","names":[],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { VideoSample } from \"mediabunny\";\nimport { type EFMedia, IgnorableError } from \"../../EFMedia\";\nimport type { BufferedSeekingInput } from \"../BufferedSeekingInput\";\n\ntype AudioSeekTask = Task<\n readonly [number, BufferedSeekingInput | undefined],\n VideoSample | undefined\n>;\nexport const makeAudioSeekTask = (host: EFMedia): AudioSeekTask => {\n return new Task(host, {\n args: () => [host.desiredSeekTimeMs, host.audioInputTask.value] as const,\n onError: (error) => {\n if (error instanceof IgnorableError) {\n console.info(\"audioSeekTask aborted\");\n return;\n }\n if (error instanceof DOMException) {\n console.error(\n `audioSeekTask error: ${error.message} ${error.name} ${error.code}`,\n );\n } else if (error instanceof Error) {\n console.error(`audioSeekTask error ${error.name}: ${error.message}`);\n } else {\n console.error(\"audioSeekTask unknown error\", error);\n }\n },\n onComplete: (_value) => {},\n task: async (): Promise<VideoSample | undefined> => {\n return undefined;\n // TODO: validate that the audio seek task is not actually used to render any audio\n // CRITICAL FIX: Use the targetSeekTimeMs from args, not host.desiredSeekTimeMs\n // This ensures we use the same seek time that the segment loading tasks used\n\n // await host.audioSegmentIdTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // await host.audioSegmentFetchTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // await host.audioInitSegmentFetchTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // const audioInput = await host.audioInputTask.taskComplete;\n // signal.throwIfAborted(); // Abort if a new seek started\n // if (!audioInput) {\n // throw new Error(\"Audio input is not available\");\n // }\n // const audioTrack = await audioInput.getFirstAudioTrack();\n // if (!audioTrack) {\n // throw new Error(\"Audio track is not available\");\n // }\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // const sample = (await audioInput.seek(\n // audioTrack.id,\n // targetSeekTimeMs, // Use the captured value, not host.desiredSeekTimeMs\n // )) as unknown as VideoSample | undefined;\n // signal.throwIfAborted(); // Abort if a new seek started\n\n // // If seek returned undefined, it was aborted - don't throw\n // if (sample === undefined && signal.aborted) {\n // return undefined;\n // }\n\n // // If we got undefined but weren't aborted, that's an actual error\n // if (sample === undefined) {\n // throw new Error(\"Audio seek failed to find sample\");\n // }\n\n // return sample;\n },\n });\n};\n"],"mappings":";;;;AASA,MAAa,qBAAqB,SAAiC;AACjE,QAAO,IAAI,KAAK,MAAM;EACpB,YAAY,CAAC,KAAK,mBAAmB,KAAK,eAAe,MAAM;EAC/D,UAAU,UAAU;AAClB,OAAI,iBAAiB,gBAAgB;AACnC,YAAQ,KAAK,wBAAwB;AACrC;;AAEF,OAAI,iBAAiB,aACnB,SAAQ,MACN,wBAAwB,MAAM,QAAQ,GAAG,MAAM,KAAK,GAAG,MAAM,OAC9D;YACQ,iBAAiB,MAC1B,SAAQ,MAAM,uBAAuB,MAAM,KAAK,IAAI,MAAM,UAAU;OAEpE,SAAQ,MAAM,+BAA+B,MAAM;;EAGvD,aAAa,WAAW;EACxB,MAAM,YAA8C;EA0CrD,CAAC"}
@@ -1,5 +1,7 @@
1
1
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
2
  import { Task } from "@lit/task";
3
+
4
+ //#region src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts
3
5
  const makeAudioSegmentFetchTask = (host) => {
4
6
  return new Task(host, {
5
7
  args: () => [host.mediaEngineTask.value, host.audioSegmentIdTask.value],
@@ -16,4 +18,7 @@ const makeAudioSegmentFetchTask = (host) => {
16
18
  }
17
19
  });
18
20
  };
21
+
22
+ //#endregion
19
23
  export { makeAudioSegmentFetchTask };
24
+ //# sourceMappingURL=makeAudioSegmentFetchTask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"makeAudioSegmentFetchTask.js","names":[],"sources":["../../../../src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts"],"sourcesContent":["import { Task } from \"@lit/task\";\nimport type { MediaEngine } from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\nimport { getLatestMediaEngine } from \"../tasks/makeMediaEngineTask\";\n\nexport const makeAudioSegmentFetchTask = (\n host: EFMedia,\n): Task<\n readonly [MediaEngine | undefined, number | undefined],\n ArrayBuffer | undefined\n> => {\n return new Task(host, {\n args: () =>\n [host.mediaEngineTask.value, host.audioSegmentIdTask.value] as const,\n onError: (error) => {\n console.error(\"audioSegmentFetchTask error\", error);\n },\n onComplete: (_value) => {},\n task: async (_, { signal }) => {\n const mediaEngine = await getLatestMediaEngine(host, signal);\n const segmentId = await host.audioSegmentIdTask.taskComplete;\n const audioRendition = mediaEngine.getAudioRendition();\n\n // Return undefined if no audio rendition or segment ID available (video-only asset)\n if (!audioRendition || segmentId === undefined) {\n return undefined;\n }\n\n return mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);\n },\n });\n};\n"],"mappings":";;;;AAKA,MAAa,6BACX,SAIG;AACH,QAAO,IAAI,KAAK,MAAM;EACpB,YACE,CAAC,KAAK,gBAAgB,OAAO,KAAK,mBAAmB,MAAM;EAC7D,UAAU,UAAU;AAClB,WAAQ,MAAM,+BAA+B,MAAM;;EAErD,aAAa,WAAW;EACxB,MAAM,OAAO,GAAG,EAAE,aAAa;GAC7B,MAAM,cAAc,MAAM,qBAAqB,MAAM,OAAO;GAC5D,MAAM,YAAY,MAAM,KAAK,mBAAmB;GAChD,MAAM,iBAAiB,YAAY,mBAAmB;AAGtD,OAAI,CAAC,kBAAkB,cAAc,OACnC;AAGF,UAAO,YAAY,kBAAkB,WAAW,gBAAgB,OAAO;;EAE1E,CAAC"}