@editframe/elements 0.17.6-beta.0 → 0.18.3-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 (218) hide show
  1. package/dist/EF_FRAMEGEN.js +1 -1
  2. package/dist/ScrubTrackManager.d.ts +2 -2
  3. package/dist/elements/EFAudio.d.ts +21 -2
  4. package/dist/elements/EFAudio.js +41 -11
  5. package/dist/elements/EFImage.d.ts +1 -0
  6. package/dist/elements/EFImage.js +11 -3
  7. package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +18 -0
  8. package/dist/elements/EFMedia/AssetIdMediaEngine.js +41 -0
  9. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +47 -0
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +116 -0
  11. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +55 -0
  12. package/dist/elements/EFMedia/BaseMediaEngine.js +96 -0
  13. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +43 -0
  14. package/dist/elements/EFMedia/BufferedSeekingInput.js +159 -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 +62 -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 +138 -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 +22 -0
  29. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +7 -0
  30. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +24 -0
  31. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +4 -0
  32. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +18 -0
  33. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +4 -0
  34. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +16 -0
  35. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +3 -0
  36. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +104 -0
  37. package/dist/elements/EFMedia/services/AudioElementFactory.d.ts +22 -0
  38. package/dist/elements/EFMedia/services/AudioElementFactory.js +72 -0
  39. package/dist/elements/EFMedia/services/MediaSourceService.browsertest.d.ts +1 -0
  40. package/dist/elements/EFMedia/services/MediaSourceService.d.ts +47 -0
  41. package/dist/elements/EFMedia/services/MediaSourceService.js +73 -0
  42. package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +7 -0
  43. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +54 -0
  44. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +70 -0
  45. package/dist/elements/EFMedia/shared/BufferUtils.js +89 -0
  46. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +23 -0
  47. package/dist/elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts +1 -0
  48. package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +19 -0
  49. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +1 -0
  50. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +18 -0
  51. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +60 -0
  52. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +1 -0
  53. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +9 -0
  54. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +16 -0
  55. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +46 -0
  56. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +9 -0
  57. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.d.ts +4 -0
  58. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +16 -0
  59. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +9 -0
  60. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.d.ts +3 -0
  61. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +27 -0
  62. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +7 -0
  63. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +25 -0
  64. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +9 -0
  65. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +4 -0
  66. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +18 -0
  67. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +9 -0
  68. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +4 -0
  69. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +16 -0
  70. package/dist/elements/EFMedia.browsertest.d.ts +1 -0
  71. package/dist/elements/EFMedia.d.ts +75 -111
  72. package/dist/elements/EFMedia.js +141 -1111
  73. package/dist/elements/EFTemporal.d.ts +1 -1
  74. package/dist/elements/EFTemporal.js +1 -1
  75. package/dist/elements/EFTimegroup.d.ts +11 -0
  76. package/dist/elements/EFTimegroup.js +88 -13
  77. package/dist/elements/EFVideo.d.ts +60 -29
  78. package/dist/elements/EFVideo.js +103 -203
  79. package/dist/elements/EFWaveform.js +2 -2
  80. package/dist/elements/SampleBuffer.d.ts +14 -0
  81. package/dist/elements/SampleBuffer.js +52 -0
  82. package/dist/getRenderInfo.d.ts +2 -2
  83. package/dist/getRenderInfo.js +2 -1
  84. package/dist/gui/ContextMixin.js +17 -70
  85. package/dist/gui/EFFilmstrip.d.ts +3 -3
  86. package/dist/gui/EFFilmstrip.js +1 -1
  87. package/dist/gui/EFFitScale.d.ts +2 -2
  88. package/dist/gui/TWMixin.js +1 -1
  89. package/dist/gui/services/ElementConnectionManager.browsertest.d.ts +1 -0
  90. package/dist/gui/services/ElementConnectionManager.d.ts +59 -0
  91. package/dist/gui/services/ElementConnectionManager.js +128 -0
  92. package/dist/gui/services/PlaybackController.browsertest.d.ts +1 -0
  93. package/dist/gui/services/PlaybackController.d.ts +103 -0
  94. package/dist/gui/services/PlaybackController.js +290 -0
  95. package/dist/services/MediaSourceManager.d.ts +62 -0
  96. package/dist/services/MediaSourceManager.js +211 -0
  97. package/dist/style.css +1 -1
  98. package/dist/transcoding/cache/CacheManager.d.ts +73 -0
  99. package/dist/transcoding/cache/RequestDeduplicator.d.ts +29 -0
  100. package/dist/transcoding/cache/RequestDeduplicator.js +53 -0
  101. package/dist/transcoding/cache/RequestDeduplicator.test.d.ts +1 -0
  102. package/dist/transcoding/types/index.d.ts +242 -0
  103. package/dist/transcoding/utils/MediaUtils.d.ts +9 -0
  104. package/dist/transcoding/utils/UrlGenerator.d.ts +26 -0
  105. package/dist/transcoding/utils/UrlGenerator.js +45 -0
  106. package/dist/transcoding/utils/constants.d.ts +27 -0
  107. package/dist/utils/LRUCache.d.ts +34 -0
  108. package/dist/utils/LRUCache.js +115 -0
  109. package/package.json +3 -2
  110. package/src/elements/EFAudio.browsertest.ts +183 -43
  111. package/src/elements/EFAudio.ts +59 -13
  112. package/src/elements/EFImage.browsertest.ts +42 -0
  113. package/src/elements/EFImage.ts +23 -3
  114. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +222 -0
  115. package/src/elements/EFMedia/AssetIdMediaEngine.ts +70 -0
  116. package/src/elements/EFMedia/AssetMediaEngine.ts +210 -0
  117. package/src/elements/EFMedia/BaseMediaEngine.test.ts +164 -0
  118. package/src/elements/EFMedia/BaseMediaEngine.ts +170 -0
  119. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +400 -0
  120. package/src/elements/EFMedia/BufferedSeekingInput.ts +267 -0
  121. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +165 -0
  122. package/src/elements/EFMedia/JitMediaEngine.ts +110 -0
  123. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +554 -0
  124. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +81 -0
  125. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +241 -0
  126. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +59 -0
  127. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +23 -0
  128. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +55 -0
  129. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +35 -0
  130. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +42 -0
  131. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +34 -0
  132. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +23 -0
  133. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +174 -0
  134. package/src/elements/EFMedia/services/AudioElementFactory.browsertest.ts +325 -0
  135. package/src/elements/EFMedia/services/AudioElementFactory.ts +119 -0
  136. package/src/elements/EFMedia/services/MediaSourceService.browsertest.ts +257 -0
  137. package/src/elements/EFMedia/services/MediaSourceService.ts +102 -0
  138. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +128 -0
  139. package/src/elements/EFMedia/shared/BufferUtils.ts +310 -0
  140. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +44 -0
  141. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +247 -0
  142. package/src/elements/EFMedia/shared/RenditionHelpers.ts +79 -0
  143. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +128 -0
  144. package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +233 -0
  145. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +89 -0
  146. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +555 -0
  147. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +79 -0
  148. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +59 -0
  149. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts +23 -0
  150. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +55 -0
  151. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.ts +45 -0
  152. package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +44 -0
  153. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +57 -0
  154. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +32 -0
  155. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +56 -0
  156. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +23 -0
  157. package/src/elements/EFMedia.browsertest.ts +658 -265
  158. package/src/elements/EFMedia.ts +173 -1763
  159. package/src/elements/EFTemporal.ts +3 -4
  160. package/src/elements/EFTimegroup.browsertest.ts +6 -3
  161. package/src/elements/EFTimegroup.ts +152 -21
  162. package/src/elements/EFVideo.browsertest.ts +115 -37
  163. package/src/elements/EFVideo.ts +123 -452
  164. package/src/elements/EFWaveform.ts +1 -1
  165. package/src/elements/MediaController.ts +2 -12
  166. package/src/elements/SampleBuffer.ts +97 -0
  167. package/src/gui/ContextMixin.ts +23 -104
  168. package/src/gui/services/ElementConnectionManager.browsertest.ts +263 -0
  169. package/src/gui/services/ElementConnectionManager.ts +224 -0
  170. package/src/gui/services/PlaybackController.browsertest.ts +437 -0
  171. package/src/gui/services/PlaybackController.ts +521 -0
  172. package/src/services/MediaSourceManager.ts +333 -0
  173. package/src/transcoding/cache/CacheManager.ts +208 -0
  174. package/src/transcoding/cache/RequestDeduplicator.test.ts +170 -0
  175. package/src/transcoding/cache/RequestDeduplicator.ts +65 -0
  176. package/src/transcoding/types/index.ts +265 -0
  177. package/src/transcoding/utils/MediaUtils.ts +63 -0
  178. package/src/transcoding/utils/UrlGenerator.ts +68 -0
  179. package/src/transcoding/utils/constants.ts +36 -0
  180. package/src/utils/LRUCache.ts +153 -0
  181. package/test/EFVideo.framegen.browsertest.ts +38 -29
  182. 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
  183. 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
  184. 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
  185. 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
  186. 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
  187. 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
  188. 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
  189. 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
  190. 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
  191. 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
  192. 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
  193. 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
  194. 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
  195. 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
  196. 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
  197. 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
  198. 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
  199. 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
  200. 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
  201. 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
  202. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -0
  203. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +19 -0
  204. package/test/createJitTestClips.ts +320 -188
  205. package/test/recordReplayProxyPlugin.js +302 -0
  206. package/test/useAssetMSW.ts +1 -1
  207. package/test/useMSW.ts +35 -22
  208. package/types.json +1 -1
  209. package/dist/JitTranscodingClient.d.ts +0 -167
  210. package/dist/JitTranscodingClient.js +0 -373
  211. package/dist/ScrubTrackManager.js +0 -216
  212. package/dist/elements/printTaskStatus.js +0 -11
  213. package/src/elements/__screenshots__/EFMedia.browsertest.ts/EFMedia-JIT-audio-playback-audioBufferTask-should-work-in-JIT-mode-without-URL-errors-1.png +0 -0
  214. package/test/EFVideo.frame-tasks.browsertest.ts +0 -524
  215. /package/dist/{JitTranscodingClient.browsertest.d.ts → elements/EFMedia/AssetIdMediaEngine.test.d.ts} +0 -0
  216. /package/dist/{JitTranscodingClient.test.d.ts → elements/EFMedia/BaseMediaEngine.test.d.ts} +0 -0
  217. /package/dist/{ScrubTrackIntegration.test.d.ts → elements/EFMedia/BufferedSeekingInput.browsertest.d.ts} +0 -0
  218. /package/dist/{SegmentSwitchLoading.test.d.ts → elements/EFMedia/services/AudioElementFactory.browsertest.d.ts} +0 -0
@@ -0,0 +1,241 @@
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
+ const toMs = fromMs + analysisWindowMs;
107
+
108
+ const { fetchAudioSpanningTime: fetchAudioSpan } = await import(
109
+ "../shared/AudioSpanUtils.ts"
110
+ );
111
+ const audioSpan = await fetchAudioSpan(
112
+ element,
113
+ fromMs,
114
+ toMs,
115
+ new AbortController().signal,
116
+ );
117
+
118
+ if (!audioSpan || !audioSpan.blob) {
119
+ console.warn("Frequency analysis skipped: no audio data available");
120
+ return null;
121
+ }
122
+
123
+ // Decode the real audio data
124
+ const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);
125
+ const arrayBuffer = await audioSpan.blob.arrayBuffer();
126
+ const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
127
+
128
+ // Use actual startOffset from audioSpan (relative to requested time)
129
+ const startOffsetMs = audioSpan.startMs;
130
+
131
+ // ORIGINAL ALGORITHM FROM HERE - unchanged customer logic
132
+ const smoothedKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${startOffsetMs}:${currentTimeMs}`;
133
+
134
+ const cachedSmoothedData = cache.get(smoothedKey);
135
+ if (cachedSmoothedData) {
136
+ return cachedSmoothedData;
137
+ }
138
+
139
+ const framesData = await Promise.all(
140
+ Array.from({ length: element.fftDecay }, async (_, i) => {
141
+ const frameOffset = i * (1000 / 30);
142
+ const startTime = Math.max(
143
+ 0,
144
+ (currentTimeMs - frameOffset - startOffsetMs) / 1000,
145
+ );
146
+
147
+ // Cache key for this specific frame
148
+ const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
149
+
150
+ // Check cache for this specific frame
151
+ const cachedFrame = cache.get(cacheKey);
152
+ if (cachedFrame) {
153
+ return cachedFrame;
154
+ }
155
+
156
+ // Running 48000 * (1 / 30) = 1600 broke something terrible, it came out as 0,
157
+ // I'm assuming weird floating point nonsense to do with running on rosetta
158
+ const SIZE = 48000 / 30;
159
+ let audioContext: OfflineAudioContext;
160
+ try {
161
+ audioContext = new OfflineAudioContext(2, SIZE, 48000);
162
+ } catch (error) {
163
+ throw new Error(
164
+ `[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.`,
165
+ );
166
+ }
167
+ const analyser = audioContext.createAnalyser();
168
+ analyser.fftSize = element.fftSize;
169
+ analyser.minDecibels = -90;
170
+ analyser.maxDecibels = -10;
171
+
172
+ const gainNode = audioContext.createGain();
173
+ gainNode.gain.value = element.fftGain;
174
+
175
+ const filter = audioContext.createBiquadFilter();
176
+ filter.type = "bandpass";
177
+ filter.frequency.value = 15000;
178
+ filter.Q.value = 0.05;
179
+
180
+ const audioBufferSource = audioContext.createBufferSource();
181
+ audioBufferSource.buffer = audioBuffer;
182
+
183
+ audioBufferSource.connect(filter);
184
+ filter.connect(gainNode);
185
+ gainNode.connect(analyser);
186
+ analyser.connect(audioContext.destination);
187
+
188
+ audioBufferSource.start(0, startTime, 1 / 30);
189
+
190
+ try {
191
+ await audioContext.startRendering();
192
+ const frameData = new Uint8Array(element.fftSize / 2);
193
+ analyser.getByteFrequencyData(frameData);
194
+
195
+ // Cache this frame's analysis
196
+ cache.set(cacheKey, frameData);
197
+ return frameData;
198
+ } finally {
199
+ audioBufferSource.disconnect();
200
+ analyser.disconnect();
201
+ }
202
+ }),
203
+ );
204
+
205
+ const frameLength = framesData[0]?.length ?? 0;
206
+
207
+ // Combine frames with decay
208
+ const smoothedData = new Uint8Array(frameLength);
209
+ for (let i = 0; i < frameLength; i++) {
210
+ let weightedSum = 0;
211
+ let weightSum = 0;
212
+
213
+ framesData.forEach((frame: Uint8Array, frameIndex: number) => {
214
+ const decayWeight = DECAY_WEIGHT ** frameIndex;
215
+ weightedSum += (frame[i] ?? 0) * decayWeight;
216
+ weightSum += decayWeight;
217
+ });
218
+
219
+ smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
220
+ }
221
+
222
+ // Apply frequency weights using instance FREQ_WEIGHTS
223
+ smoothedData.forEach((value, i) => {
224
+ const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;
225
+ smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
226
+ });
227
+
228
+ // Only return the lower half of the frequency data
229
+ // The top half is zeroed out, which makes for aesthetically unpleasing waveforms
230
+ const slicedData = smoothedData.slice(
231
+ 0,
232
+ Math.floor(smoothedData.length / 2),
233
+ );
234
+ const processedData = element.shouldInterpolateFrequencies
235
+ ? processFFTData(slicedData)
236
+ : slicedData;
237
+ cache.set(smoothedKey, processedData);
238
+ return processedData;
239
+ },
240
+ });
241
+ }
@@ -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,35 @@
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 () => {
21
+ const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
22
+ const segment = await host.audioSegmentFetchTask.taskComplete;
23
+ if (!initSegment || !segment) {
24
+ throw new Error("Init segment or segment is not available");
25
+ }
26
+ return new BufferedSeekingInput(
27
+ await new Blob([initSegment, segment]).arrayBuffer(),
28
+ {
29
+ videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
30
+ audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
31
+ },
32
+ );
33
+ },
34
+ });
35
+ };
@@ -0,0 +1,42 @@
1
+ import { Task } from "@lit/task";
2
+ import type { VideoSample } from "mediabunny";
3
+ import { type EFMedia, IgnorableError } from "../../EFMedia";
4
+ import type { BufferedSeekingInput } from "../BufferedSeekingInput";
5
+
6
+ type AudioSeekTask = Task<
7
+ readonly [number, BufferedSeekingInput | undefined],
8
+ VideoSample | undefined
9
+ >;
10
+ export const makeAudioSeekTask = (host: EFMedia): AudioSeekTask => {
11
+ return new Task(host, {
12
+ args: () => [host.desiredSeekTimeMs, host.audioInputTask.value] as const,
13
+ onError: (error) => {
14
+ if (error instanceof IgnorableError) {
15
+ console.info("audioSeekTask aborted");
16
+ }
17
+ console.error("audioSeekTask error", error);
18
+ },
19
+ onComplete: (_value) => {},
20
+ task: async (_): Promise<VideoSample | undefined> => {
21
+ await host.audioSegmentIdTask.taskComplete;
22
+ await host.audioSegmentFetchTask.taskComplete;
23
+ await host.audioInitSegmentFetchTask.taskComplete;
24
+
25
+ const audioInput = await host.audioInputTask.taskComplete;
26
+ if (!audioInput) {
27
+ throw new Error("Audio input is not available");
28
+ }
29
+ const audioTrack = await audioInput.getFirstAudioTrack();
30
+ if (!audioTrack) {
31
+ throw new Error("Audio track is not available");
32
+ }
33
+
34
+ const sample = (await audioInput.seek(
35
+ audioTrack.id,
36
+ host.desiredSeekTimeMs,
37
+ )) as unknown as VideoSample;
38
+
39
+ return sample;
40
+ },
41
+ });
42
+ };
@@ -0,0 +1,34 @@
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 makeAudioSegmentFetchTask = (
7
+ host: EFMedia,
8
+ ): Task<
9
+ readonly [MediaEngine | undefined, number | undefined],
10
+ ArrayBuffer
11
+ > => {
12
+ return new Task(host, {
13
+ args: () =>
14
+ [host.mediaEngineTask.value, host.audioSegmentIdTask.value] as const,
15
+ onError: (error) => {
16
+ console.error("audioSegmentFetchTask error", error);
17
+ },
18
+ onComplete: (_value) => {},
19
+ task: async (_, { signal }) => {
20
+ const mediaEngine = await getLatestMediaEngine(host, signal);
21
+ const segmentId = await host.audioSegmentIdTask.taskComplete;
22
+ if (segmentId === undefined) {
23
+ throw new Error("Segment ID is not available");
24
+ }
25
+
26
+ // SIMPLIFIED: Direct call to mediaEngine - deduplication is built-in
27
+ return mediaEngine.fetchMediaSegment(
28
+ segmentId,
29
+ mediaEngine.getAudioRendition(),
30
+ signal,
31
+ );
32
+ },
33
+ });
34
+ };
@@ -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 makeAudioSegmentIdTask = (
7
+ host: EFMedia,
8
+ ): Task<readonly [MediaEngine | undefined, number], number | undefined> => {
9
+ return new Task(host, {
10
+ args: () => [host.mediaEngineTask.value, host.desiredSeekTimeMs] as const,
11
+ onError: (error) => {
12
+ console.error("audioSegmentIdTask error", error);
13
+ },
14
+ onComplete: (_value) => {},
15
+ task: async (_, { signal }) => {
16
+ const mediaEngine = await getLatestMediaEngine(host, signal);
17
+ return mediaEngine.computeSegmentId(
18
+ host.desiredSeekTimeMs,
19
+ mediaEngine.getAudioRendition(),
20
+ );
21
+ },
22
+ });
23
+ };
@@ -0,0 +1,174 @@
1
+ import { Task } from "@lit/task";
2
+
3
+ import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
4
+ import { LRUCache } from "../../../utils/LRUCache.js";
5
+ import type { EFMedia } from "../../EFMedia.js";
6
+
7
+ // DECAY_WEIGHT constant - same as original
8
+ const DECAY_WEIGHT = 0.8;
9
+
10
+ export function makeAudioTimeDomainAnalysisTask(element: EFMedia) {
11
+ // Internal cache for this task instance (same as original #byteTimeDomainCache)
12
+ const cache = new LRUCache<string, Uint8Array>(1000);
13
+
14
+ return new Task(element, {
15
+ autoRun: EF_INTERACTIVE,
16
+ onError: (error) => {
17
+ console.error("byteTimeDomainTask error", error);
18
+ },
19
+ args: () =>
20
+ [
21
+ element.audioBufferTask.status,
22
+ element.currentSourceTimeMs,
23
+ element.fftSize,
24
+ element.fftDecay,
25
+ element.fftGain,
26
+ element.shouldInterpolateFrequencies,
27
+ ] as const,
28
+ task: async () => {
29
+ await element.audioBufferTask.taskComplete;
30
+ if (!element.audioBufferTask.value) return null;
31
+ if (element.currentSourceTimeMs < 0) return null;
32
+
33
+ const currentTimeMs = element.currentSourceTimeMs;
34
+
35
+ // ONLY CHANGE: Get real audio data for analysis (same technique as playback)
36
+ const analysisWindowMs = 5000; // Get 5 seconds for better analysis
37
+ const fromMs = Math.max(0, currentTimeMs);
38
+ const toMs = fromMs + analysisWindowMs;
39
+
40
+ const { fetchAudioSpanningTime: fetchAudioSpan } = await import(
41
+ "../shared/AudioSpanUtils.ts"
42
+ );
43
+ const audioSpan = await fetchAudioSpan(
44
+ element,
45
+ fromMs,
46
+ toMs,
47
+ new AbortController().signal,
48
+ );
49
+
50
+ if (!audioSpan || !audioSpan.blob) {
51
+ console.warn("Time domain analysis skipped: no audio data available");
52
+ return null;
53
+ }
54
+
55
+ // Decode the real audio data
56
+ const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);
57
+ const arrayBuffer = await audioSpan.blob.arrayBuffer();
58
+ const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
59
+
60
+ // Use actual startOffset from audioSpan (relative to requested time)
61
+ const startOffsetMs = audioSpan.startMs;
62
+
63
+ // ORIGINAL ALGORITHM FROM HERE - unchanged customer logic
64
+ const smoothedKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${startOffsetMs}:${currentTimeMs}`;
65
+ const cachedData = cache.get(smoothedKey);
66
+ if (cachedData) {
67
+ return cachedData;
68
+ }
69
+
70
+ // Process multiple frames with decay, similar to the reference code
71
+ const framesData = await Promise.all(
72
+ Array.from({ length: element.fftDecay }, async (_, frameIndex) => {
73
+ const frameOffset = frameIndex * (1000 / 30);
74
+ const startTime = Math.max(
75
+ 0,
76
+ (currentTimeMs - frameOffset - startOffsetMs) / 1000,
77
+ );
78
+
79
+ const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
80
+ const cachedFrame = cache.get(cacheKey);
81
+ if (cachedFrame) {
82
+ return cachedFrame;
83
+ }
84
+
85
+ let audioContext: OfflineAudioContext;
86
+ try {
87
+ audioContext = new OfflineAudioContext(2, 48000 * (1 / 30), 48000);
88
+ } catch (error) {
89
+ throw new Error(
90
+ `[EFMedia.byteTimeDomainTask] Failed to create OfflineAudioContext(2, ${48000 * (1 / 30)}, 48000) for frame ${frameIndex} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio time domain analysis.`,
91
+ );
92
+ }
93
+
94
+ const source = audioContext.createBufferSource();
95
+ source.buffer = audioBuffer;
96
+
97
+ // Create analyzer for PCM data
98
+ const analyser = audioContext.createAnalyser();
99
+ analyser.fftSize = element.fftSize; // Ensure enough samples
100
+ analyser.minDecibels = -90;
101
+ analyser.maxDecibels = -20;
102
+
103
+ const gainNode = audioContext.createGain();
104
+ gainNode.gain.value = element.fftGain; // Amplify the signal
105
+
106
+ source.connect(gainNode);
107
+ gainNode.connect(analyser);
108
+ analyser.connect(audioContext.destination);
109
+
110
+ source.start(0, startTime, 1 / 30);
111
+
112
+ const dataLength = analyser.fftSize / 2;
113
+ try {
114
+ await audioContext.startRendering();
115
+ const frameData = new Uint8Array(dataLength);
116
+ analyser.getByteTimeDomainData(frameData);
117
+
118
+ // const points = frameData;
119
+ // Calculate RMS and midpoint values
120
+ const points = new Uint8Array(dataLength);
121
+ for (let i = 0; i < dataLength; i++) {
122
+ const pointSamples = frameData.slice(
123
+ i * (frameData.length / dataLength),
124
+ (i + 1) * (frameData.length / dataLength),
125
+ );
126
+
127
+ // Calculate RMS while preserving sign
128
+ const rms = Math.sqrt(
129
+ pointSamples.reduce((sum, sample) => {
130
+ const normalized = (sample - 128) / 128;
131
+ return sum + normalized * normalized;
132
+ }, 0) / pointSamples.length,
133
+ );
134
+
135
+ // Get average sign of the samples to determine direction
136
+ const avgSign = Math.sign(
137
+ pointSamples.reduce((sum, sample) => sum + (sample - 128), 0),
138
+ );
139
+
140
+ // Convert RMS back to byte range, preserving direction
141
+ points[i] = Math.min(255, Math.round(128 + avgSign * rms * 128));
142
+ }
143
+
144
+ cache.set(cacheKey, points);
145
+ return points;
146
+ } finally {
147
+ source.disconnect();
148
+ analyser.disconnect();
149
+ }
150
+ }),
151
+ );
152
+
153
+ // Combine frames with decay weighting
154
+ const frameLength = framesData[0]?.length ?? 0;
155
+ const smoothedData = new Uint8Array(frameLength);
156
+
157
+ for (let i = 0; i < frameLength; i++) {
158
+ let weightedSum = 0;
159
+ let weightSum = 0;
160
+
161
+ framesData.forEach((frame: Uint8Array, frameIndex: number) => {
162
+ const decayWeight = DECAY_WEIGHT ** frameIndex;
163
+ weightedSum += (frame[i] ?? 0) * decayWeight;
164
+ weightSum += decayWeight;
165
+ });
166
+
167
+ smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
168
+ }
169
+
170
+ cache.set(smoothedKey, smoothedData);
171
+ return smoothedData;
172
+ },
173
+ });
174
+ }