@editframe/elements 0.17.6-beta.0 → 0.18.7-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 (211) hide show
  1. package/dist/EF_FRAMEGEN.js +1 -1
  2. package/dist/elements/EFAudio.d.ts +21 -2
  3. package/dist/elements/EFAudio.js +41 -11
  4. package/dist/elements/EFImage.d.ts +1 -0
  5. package/dist/elements/EFImage.js +11 -3
  6. package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +18 -0
  7. package/dist/elements/EFMedia/AssetIdMediaEngine.js +41 -0
  8. package/dist/elements/EFMedia/AssetMediaEngine.browsertest.d.ts +0 -0
  9. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +45 -0
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +135 -0
  11. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +55 -0
  12. package/dist/elements/EFMedia/BaseMediaEngine.js +115 -0
  13. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +43 -0
  14. package/dist/elements/EFMedia/BufferedSeekingInput.js +179 -0
  15. package/dist/elements/EFMedia/JitMediaEngine.browsertest.d.ts +0 -0
  16. package/dist/elements/EFMedia/JitMediaEngine.d.ts +31 -0
  17. package/dist/elements/EFMedia/JitMediaEngine.js +81 -0
  18. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.d.ts +9 -0
  19. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +16 -0
  20. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +48 -0
  21. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +3 -0
  22. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +141 -0
  23. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.d.ts +9 -0
  24. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +4 -0
  25. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +16 -0
  26. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.d.ts +9 -0
  27. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.d.ts +3 -0
  28. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +30 -0
  29. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts +0 -0
  30. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +7 -0
  31. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +32 -0
  32. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +4 -0
  33. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +28 -0
  34. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +4 -0
  35. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +17 -0
  36. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +3 -0
  37. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +107 -0
  38. package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +7 -0
  39. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +54 -0
  40. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +70 -0
  41. package/dist/elements/EFMedia/shared/BufferUtils.js +89 -0
  42. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +23 -0
  43. package/dist/elements/EFMedia/shared/PrecisionUtils.d.ts +28 -0
  44. package/dist/elements/EFMedia/shared/PrecisionUtils.js +29 -0
  45. package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +19 -0
  46. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +18 -0
  47. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +60 -0
  48. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +9 -0
  49. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +16 -0
  50. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +46 -0
  51. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +9 -0
  52. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.d.ts +4 -0
  53. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +16 -0
  54. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +9 -0
  55. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.d.ts +3 -0
  56. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +27 -0
  57. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +7 -0
  58. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +34 -0
  59. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +9 -0
  60. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +4 -0
  61. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +28 -0
  62. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +9 -0
  63. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +4 -0
  64. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +17 -0
  65. package/dist/elements/EFMedia.browsertest.d.ts +1 -0
  66. package/dist/elements/EFMedia.d.ts +63 -111
  67. package/dist/elements/EFMedia.js +117 -1113
  68. package/dist/elements/EFTemporal.d.ts +1 -1
  69. package/dist/elements/EFTemporal.js +1 -1
  70. package/dist/elements/EFTimegroup.d.ts +11 -0
  71. package/dist/elements/EFTimegroup.js +83 -13
  72. package/dist/elements/EFVideo.d.ts +54 -32
  73. package/dist/elements/EFVideo.js +100 -207
  74. package/dist/elements/EFWaveform.js +2 -2
  75. package/dist/elements/SampleBuffer.d.ts +14 -0
  76. package/dist/elements/SampleBuffer.js +52 -0
  77. package/dist/getRenderInfo.js +2 -1
  78. package/dist/gui/ContextMixin.js +3 -2
  79. package/dist/gui/EFFilmstrip.d.ts +3 -3
  80. package/dist/gui/EFFilmstrip.js +1 -1
  81. package/dist/gui/EFFitScale.d.ts +2 -2
  82. package/dist/gui/TWMixin.js +1 -1
  83. package/dist/style.css +1 -1
  84. package/dist/transcoding/cache/CacheManager.d.ts +73 -0
  85. package/dist/transcoding/cache/RequestDeduplicator.d.ts +29 -0
  86. package/dist/transcoding/cache/RequestDeduplicator.js +53 -0
  87. package/dist/transcoding/cache/RequestDeduplicator.test.d.ts +1 -0
  88. package/dist/transcoding/types/index.d.ts +242 -0
  89. package/dist/transcoding/utils/MediaUtils.d.ts +9 -0
  90. package/dist/transcoding/utils/UrlGenerator.d.ts +26 -0
  91. package/dist/transcoding/utils/UrlGenerator.js +45 -0
  92. package/dist/transcoding/utils/constants.d.ts +27 -0
  93. package/dist/utils/LRUCache.d.ts +34 -0
  94. package/dist/utils/LRUCache.js +115 -0
  95. package/package.json +3 -3
  96. package/src/elements/EFAudio.browsertest.ts +189 -49
  97. package/src/elements/EFAudio.ts +59 -13
  98. package/src/elements/EFImage.browsertest.ts +42 -0
  99. package/src/elements/EFImage.ts +23 -3
  100. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +222 -0
  101. package/src/elements/EFMedia/AssetIdMediaEngine.ts +70 -0
  102. package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +100 -0
  103. package/src/elements/EFMedia/AssetMediaEngine.ts +255 -0
  104. package/src/elements/EFMedia/BaseMediaEngine.test.ts +164 -0
  105. package/src/elements/EFMedia/BaseMediaEngine.ts +219 -0
  106. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +481 -0
  107. package/src/elements/EFMedia/BufferedSeekingInput.ts +324 -0
  108. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +165 -0
  109. package/src/elements/EFMedia/JitMediaEngine.ts +166 -0
  110. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +554 -0
  111. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +81 -0
  112. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +250 -0
  113. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +59 -0
  114. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +23 -0
  115. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +55 -0
  116. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +43 -0
  117. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +199 -0
  118. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +64 -0
  119. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +45 -0
  120. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +24 -0
  121. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +183 -0
  122. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +128 -0
  123. package/src/elements/EFMedia/shared/BufferUtils.ts +310 -0
  124. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +44 -0
  125. package/src/elements/EFMedia/shared/PrecisionUtils.ts +46 -0
  126. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +247 -0
  127. package/src/elements/EFMedia/shared/RenditionHelpers.ts +79 -0
  128. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +128 -0
  129. package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +233 -0
  130. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +89 -0
  131. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +555 -0
  132. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +79 -0
  133. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +59 -0
  134. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts +23 -0
  135. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +55 -0
  136. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.ts +45 -0
  137. package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +68 -0
  138. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +57 -0
  139. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +43 -0
  140. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +56 -0
  141. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +24 -0
  142. package/src/elements/EFMedia.browsertest.ts +706 -273
  143. package/src/elements/EFMedia.ts +136 -1769
  144. package/src/elements/EFTemporal.ts +3 -4
  145. package/src/elements/EFTimegroup.browsertest.ts +6 -3
  146. package/src/elements/EFTimegroup.ts +147 -21
  147. package/src/elements/EFVideo.browsertest.ts +980 -169
  148. package/src/elements/EFVideo.ts +113 -458
  149. package/src/elements/EFWaveform.ts +1 -1
  150. package/src/elements/MediaController.ts +2 -12
  151. package/src/elements/SampleBuffer.ts +95 -0
  152. package/src/gui/ContextMixin.ts +3 -6
  153. package/src/transcoding/cache/CacheManager.ts +208 -0
  154. package/src/transcoding/cache/RequestDeduplicator.test.ts +170 -0
  155. package/src/transcoding/cache/RequestDeduplicator.ts +65 -0
  156. package/src/transcoding/types/index.ts +269 -0
  157. package/src/transcoding/utils/MediaUtils.ts +63 -0
  158. package/src/transcoding/utils/UrlGenerator.ts +68 -0
  159. package/src/transcoding/utils/constants.ts +36 -0
  160. package/src/utils/LRUCache.ts +153 -0
  161. package/test/EFVideo.framegen.browsertest.ts +39 -30
  162. package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/data.bin +0 -0
  163. package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/metadata.json +21 -0
  164. package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/data.bin +0 -0
  165. package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/metadata.json +22 -0
  166. package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/data.bin +0 -0
  167. package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/metadata.json +21 -0
  168. package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/data.bin +0 -0
  169. package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/metadata.json +22 -0
  170. package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/data.bin +0 -0
  171. package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/metadata.json +21 -0
  172. package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/data.bin +0 -0
  173. package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/metadata.json +22 -0
  174. package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/data.bin +0 -0
  175. package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/metadata.json +21 -0
  176. package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/data.bin +0 -0
  177. package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/metadata.json +21 -0
  178. package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/data.bin +0 -0
  179. package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/metadata.json +21 -0
  180. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/data.bin +0 -0
  181. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +21 -0
  182. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/data.bin +0 -0
  183. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +21 -0
  184. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/data.bin +0 -0
  185. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/metadata.json +21 -0
  186. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
  187. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +21 -0
  188. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/data.bin +0 -0
  189. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +21 -0
  190. package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/data.bin +0 -0
  191. package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/metadata.json +21 -0
  192. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -0
  193. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +19 -0
  194. package/test/createJitTestClips.ts +320 -188
  195. package/test/recordReplayProxyPlugin.js +352 -0
  196. package/test/useAssetMSW.ts +1 -1
  197. package/test/useMSW.ts +35 -22
  198. package/types.json +1 -1
  199. package/dist/JitTranscodingClient.d.ts +0 -167
  200. package/dist/JitTranscodingClient.js +0 -373
  201. package/dist/ScrubTrackManager.d.ts +0 -96
  202. package/dist/ScrubTrackManager.js +0 -216
  203. package/dist/elements/printTaskStatus.js +0 -11
  204. package/src/elements/__screenshots__/EFMedia.browsertest.ts/EFMedia-JIT-audio-playback-audioBufferTask-should-work-in-JIT-mode-without-URL-errors-1.png +0 -0
  205. package/test/EFVideo.frame-tasks.browsertest.ts +0 -524
  206. /package/dist/{DecoderResetFrequency.test.d.ts → elements/EFMedia/AssetIdMediaEngine.test.d.ts} +0 -0
  207. /package/dist/{DecoderResetRecovery.test.d.ts → elements/EFMedia/BaseMediaEngine.test.d.ts} +0 -0
  208. /package/dist/{JitTranscodingClient.browsertest.d.ts → elements/EFMedia/BufferedSeekingInput.browsertest.d.ts} +0 -0
  209. /package/dist/{JitTranscodingClient.test.d.ts → elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts} +0 -0
  210. /package/dist/{ScrubTrackIntegration.test.d.ts → elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts} +0 -0
  211. /package/dist/{SegmentSwitchLoading.test.d.ts → elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts} +0 -0
@@ -0,0 +1,250 @@
1
+ import { Task } from "@lit/task";
2
+ import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
3
+ import { LRUCache } from "../../../utils/LRUCache.js";
4
+ import type { EFMedia } from "../../EFMedia.js";
5
+
6
+ // DECAY_WEIGHT constant - same as original
7
+ const DECAY_WEIGHT = 0.8;
8
+
9
+ function processFFTData(
10
+ fftData: Uint8Array,
11
+ zeroThresholdPercent = 0.1,
12
+ ): Uint8Array {
13
+ // Step 1: Determine the threshold for zeros
14
+ const totalBins = fftData.length;
15
+ const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
16
+
17
+ // Step 2: Interrogate the FFT output to find the cutoff point
18
+ let zeroCount = 0;
19
+ let cutoffIndex = totalBins; // Default to the end of the array
20
+
21
+ for (let i = totalBins - 1; i >= 0; i--) {
22
+ if (fftData[i] ?? 0 < 10) {
23
+ zeroCount++;
24
+ } else {
25
+ // If we encounter a non-zero value, we can stop
26
+ if (zeroCount >= zeroThresholdCount) {
27
+ cutoffIndex = i + 1; // Include this index
28
+ break;
29
+ }
30
+ }
31
+ }
32
+
33
+ if (cutoffIndex < zeroThresholdCount) {
34
+ return fftData;
35
+ }
36
+
37
+ // Step 3: Resample the "good" portion of the data
38
+ const goodData = fftData.slice(0, cutoffIndex);
39
+ const resampledData = interpolateData(goodData, fftData.length);
40
+
41
+ // Step 4: Attenuate the top 10% of interpolated samples
42
+ const attenuationStartIndex = Math.floor(totalBins * 0.9);
43
+ for (let i = attenuationStartIndex; i < totalBins; i++) {
44
+ // Calculate attenuation factor that goes from 1 to 0 over the top 10%
45
+ const attenuationProgress =
46
+ (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;
47
+ const attenuationFactor = Math.max(0, 1 - attenuationProgress);
48
+ resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);
49
+ }
50
+
51
+ return resampledData;
52
+ }
53
+
54
+ function interpolateData(data: Uint8Array, targetSize: number): Uint8Array {
55
+ const resampled = new Uint8Array(targetSize);
56
+ const dataLength = data.length;
57
+
58
+ for (let i = 0; i < targetSize; i++) {
59
+ // Calculate the corresponding index in the original data
60
+ const ratio = (i / (targetSize - 1)) * (dataLength - 1);
61
+ const index = Math.floor(ratio);
62
+ const fraction = ratio - index;
63
+
64
+ // Handle edge cases
65
+ if (index >= dataLength - 1) {
66
+ resampled[i] = data[dataLength - 1] ?? 0; // Last value
67
+ } else {
68
+ // Linear interpolation
69
+ resampled[i] = Math.round(
70
+ (data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,
71
+ );
72
+ }
73
+ }
74
+
75
+ return resampled;
76
+ }
77
+
78
+ export function makeAudioFrequencyAnalysisTask(element: EFMedia) {
79
+ // Internal cache for this task instance (same as original #frequencyDataCache)
80
+ const cache = new LRUCache<string, Uint8Array>(100);
81
+
82
+ return new Task(element, {
83
+ autoRun: EF_INTERACTIVE,
84
+ onError: (error) => {
85
+ console.error("frequencyDataTask error", error);
86
+ },
87
+ args: () =>
88
+ [
89
+ element.audioBufferTask.status,
90
+ element.currentSourceTimeMs,
91
+ element.fftSize,
92
+ element.fftDecay,
93
+ element.fftGain,
94
+ element.shouldInterpolateFrequencies,
95
+ ] as const,
96
+ task: async () => {
97
+ await element.audioBufferTask.taskComplete;
98
+ if (!element.audioBufferTask.value) return null;
99
+ if (element.currentSourceTimeMs < 0) return null;
100
+
101
+ const currentTimeMs = element.currentSourceTimeMs;
102
+
103
+ // ONLY CHANGE: Get real audio data for analysis (same technique as playback)
104
+ const analysisWindowMs = 5000; // Get 5 seconds for better analysis
105
+ const fromMs = Math.max(0, currentTimeMs);
106
+ // Clamp toMs to video duration to prevent requesting segments beyond available content
107
+ const maxToMs = fromMs + analysisWindowMs;
108
+ const videoDurationMs = element.intrinsicDurationMs || 0;
109
+ const toMs =
110
+ videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;
111
+
112
+ // If the clamping results in an invalid range (seeking beyond the end), skip analysis silently
113
+ if (fromMs >= toMs) {
114
+ return null;
115
+ }
116
+
117
+ const { fetchAudioSpanningTime: fetchAudioSpan } = await import(
118
+ "../shared/AudioSpanUtils.ts"
119
+ );
120
+ const audioSpan = await fetchAudioSpan(
121
+ element,
122
+ fromMs,
123
+ toMs,
124
+ new AbortController().signal,
125
+ );
126
+
127
+ if (!audioSpan || !audioSpan.blob) {
128
+ console.warn("Frequency analysis skipped: no audio data available");
129
+ return null;
130
+ }
131
+
132
+ // Decode the real audio data
133
+ const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);
134
+ const arrayBuffer = await audioSpan.blob.arrayBuffer();
135
+ const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
136
+
137
+ // Use actual startOffset from audioSpan (relative to requested time)
138
+ const startOffsetMs = audioSpan.startMs;
139
+
140
+ // ORIGINAL ALGORITHM FROM HERE - unchanged customer logic
141
+ const smoothedKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${startOffsetMs}:${currentTimeMs}`;
142
+
143
+ const cachedSmoothedData = cache.get(smoothedKey);
144
+ if (cachedSmoothedData) {
145
+ return cachedSmoothedData;
146
+ }
147
+
148
+ const framesData = await Promise.all(
149
+ Array.from({ length: element.fftDecay }, async (_, i) => {
150
+ const frameOffset = i * (1000 / 30);
151
+ const startTime = Math.max(
152
+ 0,
153
+ (currentTimeMs - frameOffset - startOffsetMs) / 1000,
154
+ );
155
+
156
+ // Cache key for this specific frame
157
+ const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
158
+
159
+ // Check cache for this specific frame
160
+ const cachedFrame = cache.get(cacheKey);
161
+ if (cachedFrame) {
162
+ return cachedFrame;
163
+ }
164
+
165
+ // Running 48000 * (1 / 30) = 1600 broke something terrible, it came out as 0,
166
+ // I'm assuming weird floating point nonsense to do with running on rosetta
167
+ const SIZE = 48000 / 30;
168
+ let audioContext: OfflineAudioContext;
169
+ try {
170
+ audioContext = new OfflineAudioContext(2, SIZE, 48000);
171
+ } catch (error) {
172
+ throw new Error(
173
+ `[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.`,
174
+ );
175
+ }
176
+ const analyser = audioContext.createAnalyser();
177
+ analyser.fftSize = element.fftSize;
178
+ analyser.minDecibels = -90;
179
+ analyser.maxDecibels = -10;
180
+
181
+ const gainNode = audioContext.createGain();
182
+ gainNode.gain.value = element.fftGain;
183
+
184
+ const filter = audioContext.createBiquadFilter();
185
+ filter.type = "bandpass";
186
+ filter.frequency.value = 15000;
187
+ filter.Q.value = 0.05;
188
+
189
+ const audioBufferSource = audioContext.createBufferSource();
190
+ audioBufferSource.buffer = audioBuffer;
191
+
192
+ audioBufferSource.connect(filter);
193
+ filter.connect(gainNode);
194
+ gainNode.connect(analyser);
195
+ analyser.connect(audioContext.destination);
196
+
197
+ audioBufferSource.start(0, startTime, 1 / 30);
198
+
199
+ try {
200
+ await audioContext.startRendering();
201
+ const frameData = new Uint8Array(element.fftSize / 2);
202
+ analyser.getByteFrequencyData(frameData);
203
+
204
+ // Cache this frame's analysis
205
+ cache.set(cacheKey, frameData);
206
+ return frameData;
207
+ } finally {
208
+ audioBufferSource.disconnect();
209
+ analyser.disconnect();
210
+ }
211
+ }),
212
+ );
213
+
214
+ const frameLength = framesData[0]?.length ?? 0;
215
+
216
+ // Combine frames with decay
217
+ const smoothedData = new Uint8Array(frameLength);
218
+ for (let i = 0; i < frameLength; i++) {
219
+ let weightedSum = 0;
220
+ let weightSum = 0;
221
+
222
+ framesData.forEach((frame: Uint8Array, frameIndex: number) => {
223
+ const decayWeight = DECAY_WEIGHT ** frameIndex;
224
+ weightedSum += (frame[i] ?? 0) * decayWeight;
225
+ weightSum += decayWeight;
226
+ });
227
+
228
+ smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
229
+ }
230
+
231
+ // Apply frequency weights using instance FREQ_WEIGHTS
232
+ smoothedData.forEach((value, i) => {
233
+ const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;
234
+ smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
235
+ });
236
+
237
+ // Only return the lower half of the frequency data
238
+ // The top half is zeroed out, which makes for aesthetically unpleasing waveforms
239
+ const slicedData = smoothedData.slice(
240
+ 0,
241
+ Math.floor(smoothedData.length / 2),
242
+ );
243
+ const processedData = element.shouldInterpolateFrequencies
244
+ ? processFFTData(slicedData)
245
+ : slicedData;
246
+ cache.set(smoothedKey, processedData);
247
+ return processedData;
248
+ },
249
+ });
250
+ }
@@ -0,0 +1,59 @@
1
+ import { TaskStatus } from "@lit/task";
2
+ import { customElement } from "lit/decorators.js";
3
+ import { afterEach, beforeEach, describe, vi } from "vitest";
4
+ import { test as baseTest } from "../../../../test/useMSW.js";
5
+ import { EFAudio } from "../../EFAudio";
6
+ import { makeAudioInitSegmentFetchTask } from "./makeAudioInitSegmentFetchTask";
7
+
8
+ @customElement("test-media-audio-init-segment-fetch")
9
+ class TestMediaAudioInitSegmentFetch extends EFAudio {}
10
+
11
+ declare global {
12
+ interface HTMLElementTagNameMap {
13
+ "test-media-audio-init-segment-fetch": TestMediaAudioInitSegmentFetch;
14
+ }
15
+ }
16
+
17
+ const test = baseTest.extend<{
18
+ element: TestMediaAudioInitSegmentFetch;
19
+ }>({
20
+ element: async ({}, use) => {
21
+ const element = document.createElement(
22
+ "test-media-audio-init-segment-fetch",
23
+ );
24
+ await use(element);
25
+ element.remove();
26
+ },
27
+ });
28
+
29
+ describe("makeAudioInitSegmentFetchTask", () => {
30
+ beforeEach(() => {
31
+ // MSW setup is now handled by test fixtures
32
+ });
33
+
34
+ afterEach(() => {
35
+ const elements = document.querySelectorAll(
36
+ "test-media-audio-init-segment-fetch",
37
+ );
38
+ for (const element of elements) {
39
+ element.remove();
40
+ }
41
+ vi.restoreAllMocks();
42
+ });
43
+
44
+ test("creates task with correct initial state", ({ element, expect }) => {
45
+ const task = makeAudioInitSegmentFetchTask(element);
46
+
47
+ expect(task).toBeDefined();
48
+ expect(task.status).toBe(TaskStatus.INITIAL);
49
+ expect(task.value).toBeUndefined();
50
+ expect(task.error).toBeUndefined();
51
+ });
52
+
53
+ test("task integrates with element properties", ({ element, expect }) => {
54
+ const task = makeAudioInitSegmentFetchTask(element);
55
+
56
+ expect(task).toBeDefined();
57
+ expect(task.status).toBe(TaskStatus.INITIAL);
58
+ });
59
+ });
@@ -0,0 +1,23 @@
1
+ import { Task } from "@lit/task";
2
+ import type { MediaEngine } from "../../../transcoding/types";
3
+ import type { EFMedia } from "../../EFMedia";
4
+ import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
5
+
6
+ export const makeAudioInitSegmentFetchTask = (
7
+ host: EFMedia,
8
+ ): Task<readonly [MediaEngine | undefined], ArrayBuffer> => {
9
+ return new Task(host, {
10
+ args: () => [host.mediaEngineTask.value] as const,
11
+ onError: (error) => {
12
+ console.error("audioInitSegmentFetchTask error", error);
13
+ },
14
+ onComplete: (_value) => {},
15
+ task: async ([_mediaEngine], { signal }) => {
16
+ const mediaEngine = await getLatestMediaEngine(host, signal);
17
+ return mediaEngine.fetchInitSegment(
18
+ mediaEngine.getAudioRendition(),
19
+ signal,
20
+ );
21
+ },
22
+ });
23
+ };
@@ -0,0 +1,55 @@
1
+ import { TaskStatus } from "@lit/task";
2
+ import { customElement } from "lit/decorators.js";
3
+ import { afterEach, beforeEach, describe, vi } from "vitest";
4
+ import { test as baseTest } from "../../../../test/useMSW.js";
5
+ import { EFAudio } from "../../EFAudio";
6
+ import { makeAudioInputTask } from "./makeAudioInputTask";
7
+
8
+ @customElement("test-media-audio-input")
9
+ class TestMediaAudioInput extends EFAudio {}
10
+
11
+ declare global {
12
+ interface HTMLElementTagNameMap {
13
+ "test-media-audio-input": TestMediaAudioInput;
14
+ }
15
+ }
16
+
17
+ const test = baseTest.extend<{
18
+ element: TestMediaAudioInput;
19
+ }>({
20
+ element: async ({}, use) => {
21
+ const element = document.createElement("test-media-audio-input");
22
+ await use(element);
23
+ element.remove();
24
+ },
25
+ });
26
+
27
+ describe("makeAudioInputTask", () => {
28
+ beforeEach(() => {
29
+ // MSW setup is now handled by test fixtures
30
+ });
31
+
32
+ afterEach(() => {
33
+ const elements = document.querySelectorAll("test-media-audio-input");
34
+ for (const element of elements) {
35
+ element.remove();
36
+ }
37
+ vi.restoreAllMocks();
38
+ });
39
+
40
+ test("creates task with correct initial state", ({ element, expect }) => {
41
+ const task = makeAudioInputTask(element);
42
+
43
+ expect(task).toBeDefined();
44
+ expect(task.status).toBe(TaskStatus.INITIAL);
45
+ expect(task.value).toBeUndefined();
46
+ expect(task.error).toBeUndefined();
47
+ });
48
+
49
+ test("task integrates with element properties", ({ element, expect }) => {
50
+ const task = makeAudioInputTask(element);
51
+
52
+ expect(task).toBeDefined();
53
+ expect(task.status).toBe(TaskStatus.INITIAL);
54
+ });
55
+ });
@@ -0,0 +1,43 @@
1
+ import { Task } from "@lit/task";
2
+ import { EFMedia } from "../../EFMedia";
3
+ import { BufferedSeekingInput } from "../BufferedSeekingInput";
4
+ import type { InputTask } from "../shared/MediaTaskUtils";
5
+
6
+ export const makeAudioInputTask = (host: EFMedia): InputTask => {
7
+ return new Task<
8
+ readonly [ArrayBuffer | undefined, ArrayBuffer | undefined],
9
+ BufferedSeekingInput
10
+ >(host, {
11
+ args: () =>
12
+ [
13
+ host.audioInitSegmentFetchTask.value,
14
+ host.audioSegmentFetchTask.value,
15
+ ] as const,
16
+ onError: (error) => {
17
+ console.error("audioInputTask error", error);
18
+ },
19
+ onComplete: (_value) => {},
20
+ task: async (_, { signal }) => {
21
+ const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
22
+ signal.throwIfAborted(); // Abort if a new seek started
23
+ const segment = await host.audioSegmentFetchTask.taskComplete;
24
+ signal.throwIfAborted(); // Abort if a new seek started
25
+ if (!initSegment || !segment) {
26
+ throw new Error("Init segment or segment is not available");
27
+ }
28
+
29
+ // Get startTimeOffsetMs from the audio rendition if available
30
+ const mediaEngine = await host.mediaEngineTask.taskComplete;
31
+ const audioRendition = mediaEngine?.audioRendition;
32
+ const startTimeOffsetMs = audioRendition?.startTimeOffsetMs;
33
+
34
+ const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
35
+ signal.throwIfAborted(); // Abort if a new seek started
36
+ return new BufferedSeekingInput(arrayBuffer, {
37
+ videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
38
+ audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
39
+ startTimeOffsetMs,
40
+ });
41
+ },
42
+ });
43
+ };
@@ -0,0 +1,199 @@
1
+ import { afterEach, beforeEach, describe } from "vitest";
2
+ import { test as baseTest } from "../../../../test/useMSW.js";
3
+ import type { EFConfiguration } from "../../../gui/EFConfiguration.js";
4
+ import "../../../gui/EFPreview.js";
5
+ import "../../EFTimegroup.js";
6
+ import type { EFTimegroup } from "../../EFTimegroup.js";
7
+ import "../../EFVideo.js";
8
+ import type { EFVideo } from "../../EFVideo.js";
9
+
10
+ const test = baseTest.extend<{
11
+ timegroup: EFTimegroup;
12
+ video: EFVideo;
13
+ configuration: EFConfiguration;
14
+ }>({
15
+ timegroup: async ({}, use) => {
16
+ const timegroup = document.createElement("ef-timegroup");
17
+ timegroup.setAttribute("mode", "sequence");
18
+ timegroup.setAttribute("id", "test-timegroup"); // Required for localStorage key
19
+ timegroup.style.cssText =
20
+ "position: relative; height: 500px; width: 1000px; overflow: hidden; background-color: rgb(100 116 139);";
21
+ await use(timegroup);
22
+ },
23
+ configuration: async ({ expect }, use) => {
24
+ const configuration = document.createElement("ef-configuration");
25
+ configuration.innerHTML = `<h1 style="font: 10px monospace">${expect.getState().currentTestName}</h1>`;
26
+ // Use integrated proxy server (same host/port as test runner)
27
+ const apiHost = `${window.location.protocol}//${window.location.host}`;
28
+ configuration.setAttribute("api-host", apiHost);
29
+ configuration.apiHost = apiHost;
30
+ document.body.appendChild(configuration);
31
+ await use(configuration);
32
+ },
33
+ video: async ({ configuration, timegroup }, use) => {
34
+ const video = document.createElement("ef-video");
35
+ video.id = "bars-n-tone2";
36
+ video.src = "http://web:3000/head-moov-480p.mp4"; // Real video from working simple-demo
37
+ video.style.cssText =
38
+ "width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0;";
39
+
40
+ // Create the exact structure from simple-demo.html
41
+ const innerTimegroup = document.createElement("ef-timegroup");
42
+ innerTimegroup.mode = "contain";
43
+ innerTimegroup.style.cssText =
44
+ "position: absolute; width: 100%; height: 100%;";
45
+ innerTimegroup.append(video);
46
+ timegroup.append(innerTimegroup);
47
+ configuration.append(timegroup);
48
+
49
+ await use(video);
50
+ },
51
+ });
52
+
53
+ /**
54
+ * Regression test for chunk boundary seeking issue
55
+ *
56
+ * Root cause: 32ms coordination gap between PlaybackController and audio track boundaries
57
+ * - PlaybackController seeks to chunk boundary: 4000ms
58
+ * - Audio track actually starts at: 4032ms
59
+ * - Error: "Seek time 4000ms is outside track range [4032ms, 6016ms]"
60
+ *
61
+ * This occurs during active playbook and browser reloads at 4s mark.
62
+ * Fix: Coordinate chunk boundaries or add tolerance for small gaps.
63
+ */
64
+ describe("Audio Seek Task - Chunk Boundary Regression Test", () => {
65
+ beforeEach(() => {
66
+ // Clean up DOM and localStorage
67
+ while (document.body.children.length) {
68
+ document.body.children[0]?.remove();
69
+ }
70
+ localStorage.clear();
71
+ });
72
+
73
+ afterEach(async () => {
74
+ // Clean up any remaining elements
75
+ const videos = document.querySelectorAll("ef-video");
76
+ for (const video of videos) {
77
+ video.remove();
78
+ }
79
+ });
80
+
81
+ test("should not throw RangeError when seeking to exact 4000ms during playback", async ({
82
+ video,
83
+ timegroup,
84
+ expect,
85
+ }) => {
86
+ await video.mediaEngineTask.taskComplete;
87
+ await video.audioInputTask.taskComplete;
88
+
89
+ // Simulate active playback - start playing from beginning
90
+ timegroup.currentTimeMs = 0;
91
+ await video.audioSeekTask.taskComplete;
92
+
93
+ // Now seek to the exact problematic time that causes:
94
+ // "Seek time 4000ms is outside track range [4032ms, 6016ms]"
95
+ const exactChunkBoundary = 4000;
96
+ timegroup.currentTimeMs = exactChunkBoundary;
97
+
98
+ // Should not throw RangeError due to track range mismatch
99
+ await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
100
+ });
101
+
102
+ test("should not throw RangeError during progressive playback across segments", async ({
103
+ video,
104
+ timegroup,
105
+ expect,
106
+ }) => {
107
+ await video.mediaEngineTask.taskComplete;
108
+ await video.audioInputTask.taskComplete;
109
+
110
+ // Simulate progressive playback that loads segments on demand
111
+ // Start at 3500ms to be just before the 4-second boundary
112
+ timegroup.currentTimeMs = 3500;
113
+ await video.audioSeekTask.taskComplete;
114
+
115
+ // Now cross the 4-second chunk boundary where track range issues occur
116
+ // This should trigger the state where track range is [4032ms, 6016ms]
117
+ // but we're seeking to 4000ms
118
+ timegroup.currentTimeMs = 4000.000000000001; // The exact error from logs
119
+
120
+ // Should not throw "Seek time 4000.000000000001ms is outside track range [4032ms, 6016ms]"
121
+ await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
122
+ });
123
+
124
+ test("should not throw RangeError when localStorage restoration causes 0ms to 4000ms race condition", async ({
125
+ video,
126
+ timegroup,
127
+ expect,
128
+ }) => {
129
+ // REPRODUCE THE RACE CONDITION: Simulate localStorage having "4.0"
130
+ // This mimics the exact simple-demo.html scenario where:
131
+ // 1. Media loads with assumption of currentTimeMs = 0
132
+ // 2. localStorage restores currentTime to 4.0 seconds
133
+ // 3. Seeking 4000ms in segments loaded for 0ms range triggers RangeError
134
+
135
+ // Set localStorage BEFORE media finishes initializing
136
+ if (timegroup.id) {
137
+ localStorage.setItem(`ef-timegroup-${timegroup.id}`, "4.0");
138
+ }
139
+
140
+ // Wait for media engine but NOT for full initialization
141
+ await video.mediaEngineTask.taskComplete;
142
+
143
+ // Now trigger the localStorage restoration that happens in waitForMediaDurations().then()
144
+ // This will load currentTime = 4.0 from localStorage, jumping from 0ms to 4000ms
145
+ timegroup.currentTime = timegroup.maybeLoadTimeFromLocalStorage();
146
+
147
+ // This should trigger: "Seek time 4000ms is outside track range [Yms, Zms]"
148
+ // because segments were loaded for 0ms but we're now seeking 4000ms
149
+ await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
150
+ });
151
+
152
+ test("should not throw RangeError when forced segment coordination mismatch occurs", async ({
153
+ video,
154
+ timegroup,
155
+ expect,
156
+ }) => {
157
+ await video.mediaEngineTask.taskComplete;
158
+
159
+ // FORCE SPECIFIC SEGMENT LOADING: Load a segment for 8000ms (segment 5)
160
+ timegroup.currentTimeMs = 8000;
161
+ await video.audioSegmentIdTask.taskComplete;
162
+ await video.audioSegmentFetchTask.taskComplete;
163
+ await video.audioInputTask.taskComplete;
164
+
165
+ // Verify we have segment 5 loaded (8000ms / 15000ms = segment 1, but 1-based = segment 1...
166
+ // Actually 8000ms maps to segment 5 based on the actual segment calculation)
167
+ const segmentId = video.audioSegmentIdTask.value;
168
+ expect(segmentId).toBe(4);
169
+
170
+ // Now seek to a time in a different segment to test coordination
171
+ timegroup.currentTimeMs = 4000;
172
+
173
+ // This tests the fundamental segment coordination issue:
174
+ // - We loaded segment 5 for 8000ms
175
+ // - Now seeking to 4000ms which should be in a different segment
176
+ // - Tests that seek doesn't fail due to segment boundary coordination
177
+ await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
178
+ });
179
+
180
+ test("should not throw RangeError when rapidly crossing segment boundaries", async ({
181
+ video,
182
+ timegroup,
183
+ expect,
184
+ }) => {
185
+ await video.mediaEngineTask.taskComplete;
186
+
187
+ // RAPID BOUNDARY CROSSING: This tests timing-sensitive segment coordination
188
+ const boundaries = [1000, 4000, 8000, 3000, 7000]; // Jump around within segment 1
189
+
190
+ for (const timeMs of boundaries) {
191
+ timegroup.currentTimeMs = timeMs;
192
+ // Don't await - test rapid succession to trigger coordination issues
193
+ }
194
+
195
+ // Final seek - this should not throw even after rapid boundary crossing
196
+ timegroup.currentTimeMs = 4000;
197
+ await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
198
+ });
199
+ });