@editframe/elements 0.16.8-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.
- package/README.md +30 -0
- package/dist/DecoderResetFrequency.test.d.ts +1 -0
- package/dist/DecoderResetRecovery.test.d.ts +1 -0
- package/dist/DelayedLoadingState.d.ts +48 -0
- package/dist/DelayedLoadingState.integration.test.d.ts +1 -0
- package/dist/DelayedLoadingState.js +113 -0
- package/dist/DelayedLoadingState.test.d.ts +1 -0
- package/dist/EF_FRAMEGEN.d.ts +10 -1
- package/dist/EF_FRAMEGEN.js +199 -179
- package/dist/EF_INTERACTIVE.js +2 -6
- package/dist/EF_RENDERING.js +1 -3
- package/dist/LoadingDebounce.test.d.ts +1 -0
- package/dist/LoadingIndicator.browsertest.d.ts +0 -0
- package/dist/ManualScrubTest.test.d.ts +1 -0
- package/dist/ScrubResolvedFlashing.test.d.ts +1 -0
- package/dist/ScrubTrackManager.d.ts +96 -0
- package/dist/ScrubTrackManager.test.d.ts +1 -0
- package/dist/VideoSeekFlashing.browsertest.d.ts +0 -0
- package/dist/VideoStuckDiagnostic.test.d.ts +1 -0
- package/dist/elements/CrossUpdateController.js +13 -15
- package/dist/elements/EFAudio.browsertest.d.ts +0 -0
- package/dist/elements/EFAudio.d.ts +22 -3
- package/dist/elements/EFAudio.js +60 -43
- package/dist/elements/EFCaptions.js +337 -373
- package/dist/elements/EFImage.d.ts +1 -0
- package/dist/elements/EFImage.js +73 -91
- package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +18 -0
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +41 -0
- package/dist/elements/EFMedia/AssetIdMediaEngine.test.d.ts +1 -0
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +47 -0
- package/dist/elements/EFMedia/AssetMediaEngine.js +116 -0
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +55 -0
- package/dist/elements/EFMedia/BaseMediaEngine.js +96 -0
- package/dist/elements/EFMedia/BaseMediaEngine.test.d.ts +1 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +43 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.js +159 -0
- package/dist/elements/EFMedia/JitMediaEngine.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +31 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +62 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +16 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +48 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +3 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +138 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +16 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.d.ts +3 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +22 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +7 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +24 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +18 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +16 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +3 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +104 -0
- package/dist/elements/EFMedia/services/AudioElementFactory.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/services/AudioElementFactory.d.ts +22 -0
- package/dist/elements/EFMedia/services/AudioElementFactory.js +72 -0
- package/dist/elements/EFMedia/services/MediaSourceService.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/services/MediaSourceService.d.ts +47 -0
- package/dist/elements/EFMedia/services/MediaSourceService.js +73 -0
- package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +7 -0
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +54 -0
- package/dist/elements/EFMedia/shared/BufferUtils.d.ts +70 -0
- package/dist/elements/EFMedia/shared/BufferUtils.js +89 -0
- package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +23 -0
- package/dist/elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +19 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +18 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +60 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +1 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +16 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +46 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +16 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.d.ts +3 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +27 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +7 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +25 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +18 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +16 -0
- package/dist/elements/EFMedia.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia.d.ts +95 -66
- package/dist/elements/EFMedia.js +204 -683
- package/dist/elements/EFSourceMixin.js +31 -48
- package/dist/elements/EFTemporal.d.ts +2 -1
- package/dist/elements/EFTemporal.js +266 -360
- package/dist/elements/EFTimegroup.d.ts +14 -1
- package/dist/elements/EFTimegroup.js +337 -323
- package/dist/elements/EFVideo.browsertest.d.ts +0 -0
- package/dist/elements/EFVideo.d.ts +123 -4
- package/dist/elements/EFVideo.js +308 -111
- package/dist/elements/EFWaveform.js +375 -411
- package/dist/elements/FetchMixin.js +14 -24
- package/dist/elements/MediaController.d.ts +30 -0
- package/dist/elements/SampleBuffer.d.ts +14 -0
- package/dist/elements/SampleBuffer.js +52 -0
- package/dist/elements/TargetController.js +130 -156
- package/dist/elements/TimegroupController.js +17 -19
- package/dist/elements/durationConverter.js +15 -4
- package/dist/elements/parseTimeToMs.js +4 -10
- package/dist/elements/printTaskStatus.d.ts +2 -0
- package/dist/elements/updateAnimations.js +39 -59
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/getRenderInfo.js +59 -67
- package/dist/gui/ContextMixin.js +150 -288
- package/dist/gui/EFConfiguration.js +27 -43
- package/dist/gui/EFFilmstrip.d.ts +3 -3
- package/dist/gui/EFFilmstrip.js +440 -620
- package/dist/gui/EFFitScale.d.ts +2 -2
- package/dist/gui/EFFitScale.js +112 -135
- package/dist/gui/EFFocusOverlay.js +45 -61
- package/dist/gui/EFPreview.js +30 -49
- package/dist/gui/EFScrubber.js +78 -99
- package/dist/gui/EFTimeDisplay.js +49 -70
- package/dist/gui/EFToggleLoop.js +17 -34
- package/dist/gui/EFTogglePlay.js +37 -58
- package/dist/gui/EFWorkbench.js +66 -88
- package/dist/gui/TWMixin.js +2 -48
- package/dist/gui/TWMixin2.js +31 -0
- package/dist/gui/efContext.js +2 -6
- package/dist/gui/fetchContext.js +1 -3
- package/dist/gui/focusContext.js +1 -3
- package/dist/gui/focusedElementContext.js +2 -6
- package/dist/gui/playingContext.js +1 -4
- package/dist/gui/services/ElementConnectionManager.browsertest.d.ts +1 -0
- package/dist/gui/services/ElementConnectionManager.d.ts +59 -0
- package/dist/gui/services/ElementConnectionManager.js +128 -0
- package/dist/gui/services/PlaybackController.browsertest.d.ts +1 -0
- package/dist/gui/services/PlaybackController.d.ts +103 -0
- package/dist/gui/services/PlaybackController.js +290 -0
- package/dist/index.js +5 -30
- package/dist/msToTimeCode.js +11 -13
- package/dist/services/MediaSourceManager.d.ts +62 -0
- package/dist/services/MediaSourceManager.js +211 -0
- package/dist/style.css +2 -1
- package/dist/transcoding/cache/CacheManager.d.ts +73 -0
- package/dist/transcoding/cache/RequestDeduplicator.d.ts +29 -0
- package/dist/transcoding/cache/RequestDeduplicator.js +53 -0
- package/dist/transcoding/cache/RequestDeduplicator.test.d.ts +1 -0
- package/dist/transcoding/types/index.d.ts +242 -0
- package/dist/transcoding/utils/MediaUtils.d.ts +9 -0
- package/dist/transcoding/utils/UrlGenerator.d.ts +26 -0
- package/dist/transcoding/utils/UrlGenerator.js +45 -0
- package/dist/transcoding/utils/constants.d.ts +27 -0
- package/dist/utils/LRUCache.d.ts +34 -0
- package/dist/utils/LRUCache.js +115 -0
- package/package.json +4 -3
- package/src/elements/EFAudio.browsertest.ts +709 -0
- package/src/elements/EFAudio.ts +59 -15
- package/src/elements/EFCaptions.browsertest.ts +0 -1
- package/src/elements/EFImage.browsertest.ts +42 -1
- package/src/elements/EFImage.ts +23 -3
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +222 -0
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +70 -0
- package/src/elements/EFMedia/AssetMediaEngine.ts +210 -0
- package/src/elements/EFMedia/BaseMediaEngine.test.ts +164 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +170 -0
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +400 -0
- package/src/elements/EFMedia/BufferedSeekingInput.ts +267 -0
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +165 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +110 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +554 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +81 -0
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +241 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +59 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +23 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +55 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +35 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +42 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +34 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +23 -0
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +174 -0
- package/src/elements/EFMedia/services/AudioElementFactory.browsertest.ts +325 -0
- package/src/elements/EFMedia/services/AudioElementFactory.ts +119 -0
- package/src/elements/EFMedia/services/MediaSourceService.browsertest.ts +257 -0
- package/src/elements/EFMedia/services/MediaSourceService.ts +102 -0
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +128 -0
- package/src/elements/EFMedia/shared/BufferUtils.ts +310 -0
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +44 -0
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +247 -0
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +79 -0
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +128 -0
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +233 -0
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +89 -0
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +555 -0
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +79 -0
- package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +59 -0
- package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts +23 -0
- package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +55 -0
- package/src/elements/EFMedia/videoTasks/makeVideoInputTask.ts +45 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +44 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +57 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +32 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +56 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +23 -0
- package/src/elements/EFMedia.browsertest.ts +696 -271
- package/src/elements/EFMedia.ts +218 -776
- package/src/elements/EFTemporal.browsertest.ts +0 -1
- package/src/elements/EFTemporal.ts +13 -3
- package/src/elements/EFTimegroup.browsertest.ts +6 -3
- package/src/elements/EFTimegroup.ts +221 -27
- package/src/elements/EFVideo.browsertest.ts +758 -0
- package/src/elements/EFVideo.ts +418 -68
- package/src/elements/EFWaveform.ts +5 -5
- package/src/elements/MediaController.ts +98 -0
- package/src/elements/SampleBuffer.ts +97 -0
- package/src/elements/printTaskStatus.ts +16 -0
- package/src/elements/updateAnimations.ts +6 -0
- package/src/gui/ContextMixin.ts +23 -104
- package/src/gui/TWMixin.ts +10 -3
- package/src/gui/services/ElementConnectionManager.browsertest.ts +263 -0
- package/src/gui/services/ElementConnectionManager.ts +224 -0
- package/src/gui/services/PlaybackController.browsertest.ts +437 -0
- package/src/gui/services/PlaybackController.ts +521 -0
- package/src/services/MediaSourceManager.ts +333 -0
- package/src/transcoding/cache/CacheManager.ts +208 -0
- package/src/transcoding/cache/RequestDeduplicator.test.ts +170 -0
- package/src/transcoding/cache/RequestDeduplicator.ts +65 -0
- package/src/transcoding/types/index.ts +265 -0
- package/src/transcoding/utils/MediaUtils.ts +63 -0
- package/src/transcoding/utils/UrlGenerator.ts +68 -0
- package/src/transcoding/utils/constants.ts +36 -0
- package/src/utils/LRUCache.ts +153 -0
- package/test/EFVideo.framegen.browsertest.ts +127 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -0
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +19 -0
- package/test/createJitTestClips.ts +425 -0
- package/test/recordReplayProxyPlugin.js +302 -0
- package/test/useAssetMSW.ts +49 -0
- package/test/useMSW.ts +44 -0
- package/types.json +1 -1
- package/dist/gui/TWMixin.css.js +0 -4
- /package/dist/elements/{TargetController.test.d.ts → TargetController.browsertest.d.ts} +0 -0
- /package/src/elements/{TargetController.test.ts → TargetController.browsertest.ts} +0 -0
package/src/elements/EFMedia.ts
CHANGED
|
@@ -1,56 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
3
|
-
import debug from "debug";
|
|
4
|
-
import { LitElement, type PropertyValueMap, css } from "lit";
|
|
1
|
+
import { css, LitElement, type PropertyValueMap } from "lit";
|
|
5
2
|
import { property, state } from "lit/decorators.js";
|
|
6
|
-
import type * as MP4Box from "mp4box";
|
|
7
3
|
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
4
|
+
import type { AudioSpan } from "../transcoding/types/index.ts";
|
|
5
|
+
import { UrlGenerator } from "../transcoding/utils/UrlGenerator.ts";
|
|
6
|
+
// Audio task imports
|
|
7
|
+
import { makeAudioBufferTask } from "./EFMedia/audioTasks/makeAudioBufferTask.ts";
|
|
8
|
+
import { makeAudioFrequencyAnalysisTask } from "./EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts";
|
|
9
|
+
import { makeAudioInitSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts";
|
|
10
|
+
import { makeAudioInputTask } from "./EFMedia/audioTasks/makeAudioInputTask.ts";
|
|
11
|
+
import { makeAudioSeekTask } from "./EFMedia/audioTasks/makeAudioSeekTask.ts";
|
|
12
|
+
import { makeAudioSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioSegmentFetchTask.ts";
|
|
13
|
+
import { makeAudioSegmentIdTask } from "./EFMedia/audioTasks/makeAudioSegmentIdTask.ts";
|
|
14
|
+
import { makeAudioTimeDomainAnalysisTask } from "./EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts";
|
|
15
|
+
import { AudioElementFactory } from "./EFMedia/services/AudioElementFactory.js";
|
|
16
|
+
// Import extracted services and utilities
|
|
17
|
+
import { MediaSourceService } from "./EFMedia/services/MediaSourceService.js";
|
|
18
|
+
// Common task imports
|
|
19
|
+
import { makeMediaEngineTask } from "./EFMedia/tasks/makeMediaEngineTask.ts";
|
|
13
20
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
14
21
|
import { EFTemporal } from "./EFTemporal.js";
|
|
15
22
|
import { FetchMixin } from "./FetchMixin.js";
|
|
16
23
|
import { EFTargetable } from "./TargetController.ts";
|
|
17
24
|
import { updateAnimations } from "./updateAnimations.ts";
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
|
|
27
|
+
declare global {
|
|
28
|
+
var EF_FRAMEGEN: import("../EF_FRAMEGEN.js").EFFramegen;
|
|
29
|
+
}
|
|
20
30
|
|
|
21
31
|
const freqWeightsCache = new Map<number, Float32Array>();
|
|
22
32
|
|
|
23
|
-
class
|
|
24
|
-
private cache = new Map<K, V>();
|
|
25
|
-
private readonly maxSize: number;
|
|
26
|
-
|
|
27
|
-
constructor(maxSize: number) {
|
|
28
|
-
this.maxSize = maxSize;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
get(key: K): V | undefined {
|
|
32
|
-
const value = this.cache.get(key);
|
|
33
|
-
if (value) {
|
|
34
|
-
// Refresh position by removing and re-adding
|
|
35
|
-
this.cache.delete(key);
|
|
36
|
-
this.cache.set(key, value);
|
|
37
|
-
}
|
|
38
|
-
return value;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
set(key: K, value: V): void {
|
|
42
|
-
if (this.cache.has(key)) {
|
|
43
|
-
this.cache.delete(key);
|
|
44
|
-
} else if (this.cache.size >= this.maxSize) {
|
|
45
|
-
// Remove oldest entry (first item in map)
|
|
46
|
-
const firstKey = this.cache.keys().next().value;
|
|
47
|
-
if (firstKey) {
|
|
48
|
-
this.cache.delete(firstKey);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
this.cache.set(key, value);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
33
|
+
export class IgnorableError extends Error {}
|
|
54
34
|
|
|
55
35
|
export const deepGetMediaElements = (
|
|
56
36
|
element: Element,
|
|
@@ -71,6 +51,37 @@ export class EFMedia extends EFTargetable(
|
|
|
71
51
|
assetType: "isobmff_files",
|
|
72
52
|
}),
|
|
73
53
|
) {
|
|
54
|
+
// Sample buffer size configuration
|
|
55
|
+
static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
|
|
56
|
+
static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
|
|
57
|
+
|
|
58
|
+
static get observedAttributes() {
|
|
59
|
+
// biome-ignore lint/complexity/noThisInStatic: We need to access super
|
|
60
|
+
const parentAttributes = super.observedAttributes || [];
|
|
61
|
+
return [
|
|
62
|
+
...parentAttributes,
|
|
63
|
+
"mute",
|
|
64
|
+
"fft-size",
|
|
65
|
+
"fft-decay",
|
|
66
|
+
"fft-gain",
|
|
67
|
+
"interpolate-frequencies",
|
|
68
|
+
"asset-id",
|
|
69
|
+
"audio-buffer-duration",
|
|
70
|
+
"max-audio-buffer-fetches",
|
|
71
|
+
"enable-audio-buffering",
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Services for media source and audio element management
|
|
76
|
+
private mediaSourceService = new MediaSourceService({
|
|
77
|
+
onError: (error) => {
|
|
78
|
+
console.error("🎵 [EFMedia] MediaSourceService error:", error);
|
|
79
|
+
},
|
|
80
|
+
onReady: () => {},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
private audioElementFactory = new AudioElementFactory();
|
|
84
|
+
|
|
74
85
|
static styles = [
|
|
75
86
|
css`
|
|
76
87
|
:host {
|
|
@@ -84,261 +95,134 @@ export class EFMedia extends EFTargetable(
|
|
|
84
95
|
@property({ type: Number })
|
|
85
96
|
currentTimeMs = 0;
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Duration in milliseconds for audio buffering ahead of current time
|
|
100
|
+
* @domAttribute "audio-buffer-duration"
|
|
101
|
+
*/
|
|
102
|
+
@property({ type: Number, attribute: "audio-buffer-duration" })
|
|
103
|
+
audioBufferDurationMs = 30000; // 30 seconds
|
|
88
104
|
|
|
89
105
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* @domAttribute "asset-id"
|
|
106
|
+
* Maximum number of concurrent audio segment fetches for buffering
|
|
107
|
+
* @domAttribute "max-audio-buffer-fetches"
|
|
93
108
|
*/
|
|
94
|
-
@property({ type:
|
|
95
|
-
|
|
96
|
-
this.#assetId = value;
|
|
97
|
-
}
|
|
109
|
+
@property({ type: Number, attribute: "max-audio-buffer-fetches" })
|
|
110
|
+
maxAudioBufferFetches = 2;
|
|
98
111
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Enable/disable audio buffering system
|
|
114
|
+
* @domAttribute "enable-audio-buffering"
|
|
115
|
+
*/
|
|
116
|
+
@property({ type: Boolean, attribute: "enable-audio-buffering" })
|
|
117
|
+
enableAudioBuffering = true;
|
|
102
118
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Mute/unmute the media element
|
|
121
|
+
* @domAttribute "mute"
|
|
122
|
+
*/
|
|
123
|
+
@property({
|
|
124
|
+
type: Boolean,
|
|
125
|
+
attribute: "mute",
|
|
126
|
+
reflect: true,
|
|
127
|
+
})
|
|
128
|
+
mute = false;
|
|
109
129
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// This is an annoying incosistency that should be fixed.
|
|
117
|
-
return `/@ef-track/${this.src ?? ""}?trackId=${trackId}`;
|
|
118
|
-
}
|
|
130
|
+
/**
|
|
131
|
+
* FFT size for frequency analysis
|
|
132
|
+
* @domAttribute "fft-size"
|
|
133
|
+
*/
|
|
134
|
+
@property({ type: Number, attribute: "fft-size", reflect: true })
|
|
135
|
+
fftSize = 128;
|
|
119
136
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
137
|
+
/**
|
|
138
|
+
* FFT decay rate for frequency analysis
|
|
139
|
+
* @domAttribute "fft-decay"
|
|
140
|
+
*/
|
|
141
|
+
@property({ type: Number, attribute: "fft-decay", reflect: true })
|
|
142
|
+
fftDecay = 8;
|
|
125
143
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
onComplete: () => {
|
|
133
|
-
this.requestUpdate("intrinsicDurationMs");
|
|
134
|
-
this.requestUpdate("ownCurrentTimeMs");
|
|
135
|
-
this.rootTimegroup?.requestUpdate("ownCurrentTimeMs");
|
|
136
|
-
this.rootTimegroup?.requestUpdate("durationMs");
|
|
137
|
-
},
|
|
138
|
-
});
|
|
144
|
+
/**
|
|
145
|
+
* FFT gain for frequency analysis
|
|
146
|
+
* @domAttribute "fft-gain"
|
|
147
|
+
*/
|
|
148
|
+
@property({ type: Number, attribute: "fft-gain", reflect: true })
|
|
149
|
+
fftGain = 3.0;
|
|
139
150
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const start = track.initSegment.offset;
|
|
151
|
-
const end = track.initSegment.offset + track.initSegment.size;
|
|
152
|
-
const response = await fetch(this.fragmentTrackPath(trackId), {
|
|
153
|
-
signal,
|
|
154
|
-
headers: { Range: `bytes=${start}-${end - 1}` },
|
|
155
|
-
});
|
|
156
|
-
const buffer =
|
|
157
|
-
(await response.arrayBuffer()) as MP4Box.MP4ArrayBuffer;
|
|
158
|
-
buffer.fileStart = 0;
|
|
159
|
-
const mp4File = new MP4File();
|
|
160
|
-
mp4File.appendBuffer(buffer, true);
|
|
161
|
-
mp4File.flush();
|
|
162
|
-
await mp4File.readyPromise;
|
|
163
|
-
|
|
164
|
-
return { trackId, buffer, mp4File };
|
|
165
|
-
}),
|
|
166
|
-
);
|
|
167
|
-
},
|
|
168
|
-
});
|
|
151
|
+
/**
|
|
152
|
+
* Enable/disable frequency interpolation
|
|
153
|
+
* @domAttribute "interpolate-frequencies"
|
|
154
|
+
*/
|
|
155
|
+
@property({
|
|
156
|
+
type: Boolean,
|
|
157
|
+
attribute: "interpolate-frequencies",
|
|
158
|
+
reflect: true,
|
|
159
|
+
})
|
|
160
|
+
interpolateFrequencies = false;
|
|
169
161
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
162
|
+
// Update FREQ_WEIGHTS to use the instance fftSize instead of a static value
|
|
163
|
+
get FREQ_WEIGHTS() {
|
|
164
|
+
if (freqWeightsCache.has(this.fftSize)) {
|
|
165
|
+
// biome-ignore lint/style/noNonNullAssertion: We know the value is set due to the guard above
|
|
166
|
+
return freqWeightsCache.get(this.fftSize)!;
|
|
167
|
+
}
|
|
175
168
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
(
|
|
179
|
-
|
|
180
|
-
|
|
169
|
+
const weights = new Float32Array(this.fftSize / 2).map((_, i) => {
|
|
170
|
+
const frequency = (i * 48000) / this.fftSize;
|
|
171
|
+
if (frequency < 60) return 0.3;
|
|
172
|
+
if (frequency < 250) return 0.4;
|
|
173
|
+
if (frequency < 500) return 0.6;
|
|
174
|
+
if (frequency < 2000) return 0.8;
|
|
175
|
+
if (frequency < 4000) return 1.2;
|
|
176
|
+
if (frequency < 8000) return 1.6;
|
|
177
|
+
return 2.0;
|
|
178
|
+
});
|
|
181
179
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
[
|
|
186
|
-
this.desiredSeekTimeMs,
|
|
187
|
-
this.trackFragmentIndexLoader.value,
|
|
188
|
-
this.initSegmentsLoader.value,
|
|
189
|
-
] as const,
|
|
190
|
-
task: async (
|
|
191
|
-
[seekToMs, fragmentIndex, initSegments],
|
|
192
|
-
{ signal: _signal },
|
|
193
|
-
) => {
|
|
194
|
-
if (fragmentIndex === undefined) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (initSegments === undefined) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
180
|
+
freqWeightsCache.set(this.fftSize, weights);
|
|
181
|
+
return weights;
|
|
182
|
+
}
|
|
200
183
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
track: MP4Box.TrackInfo;
|
|
206
|
-
nextSegment?: TrackSegment;
|
|
207
|
-
}
|
|
208
|
-
> = {};
|
|
209
|
-
|
|
210
|
-
for (const index of Object.values(fragmentIndex)) {
|
|
211
|
-
const track = initSegments
|
|
212
|
-
.find((segment) => segment.trackId === String(index.track))
|
|
213
|
-
?.mp4File.getInfo().tracks[0];
|
|
214
|
-
|
|
215
|
-
if (!track) {
|
|
216
|
-
throw new Error("Could not finding matching track");
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const segment = index.segments.toReversed().find((segment) => {
|
|
220
|
-
return (segment.dts / track.timescale) * 1000 <= seekToMs;
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
const nextSegment = index.segments.find((segment) => {
|
|
224
|
-
return (segment.dts / track.timescale) * 1000 > seekToMs;
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
if (!segment) {
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
result[index.track] = { segment, track, nextSegment };
|
|
232
|
-
}
|
|
184
|
+
// Helper getter for backwards compatibility
|
|
185
|
+
get shouldInterpolateFrequencies() {
|
|
186
|
+
return this.interpolateFrequencies;
|
|
187
|
+
}
|
|
233
188
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
189
|
+
get urlGenerator() {
|
|
190
|
+
return new UrlGenerator(() => this.apiHost ?? "");
|
|
191
|
+
}
|
|
237
192
|
|
|
238
|
-
|
|
239
|
-
autoRun: EF_INTERACTIVE,
|
|
240
|
-
argsEqual: deepArrayEquals,
|
|
241
|
-
args: () =>
|
|
242
|
-
[this.initSegmentsLoader.value, this.seekTask.value, this.fetch] as const,
|
|
243
|
-
task: async ([initSegments, seekResult, fetch], { signal }) => {
|
|
244
|
-
if (!initSegments) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (!seekResult) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
193
|
+
mediaEngineTask = makeMediaEngineTask(this);
|
|
250
194
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const start = segment.offset;
|
|
257
|
-
const end = segment.offset + segment.size;
|
|
258
|
-
|
|
259
|
-
const response = await fetch(this.fragmentTrackPath(trackId), {
|
|
260
|
-
signal,
|
|
261
|
-
headers: { Range: `bytes=${start}-${end - 1}` },
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
if (nextSegment) {
|
|
265
|
-
const nextStart = nextSegment.offset;
|
|
266
|
-
const nextEnd = nextSegment.offset + nextSegment.size;
|
|
267
|
-
fetch(this.fragmentTrackPath(trackId), {
|
|
268
|
-
signal,
|
|
269
|
-
headers: { Range: `bytes=${nextStart}-${nextEnd - 1}` },
|
|
270
|
-
})
|
|
271
|
-
.then(() => {
|
|
272
|
-
log("Prefetched next segment");
|
|
273
|
-
})
|
|
274
|
-
.catch((error) => {
|
|
275
|
-
log("Failed to prefetch next segment", error);
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const initSegment = Object.values(initSegments).find(
|
|
280
|
-
(initSegment) => initSegment.trackId === String(track.id),
|
|
281
|
-
);
|
|
282
|
-
if (!initSegment) {
|
|
283
|
-
throw new Error("Could not find matching init segment");
|
|
284
|
-
}
|
|
285
|
-
const initBuffer = initSegment.buffer;
|
|
286
|
-
|
|
287
|
-
const mediaBuffer =
|
|
288
|
-
(await response.arrayBuffer()) as unknown as MP4Box.MP4ArrayBuffer;
|
|
289
|
-
|
|
290
|
-
files[trackId] = new File([initBuffer, mediaBuffer], "video.mp4", {
|
|
291
|
-
type: "video/mp4",
|
|
292
|
-
});
|
|
293
|
-
}
|
|
195
|
+
audioSegmentIdTask = makeAudioSegmentIdTask(this);
|
|
196
|
+
audioInitSegmentFetchTask = makeAudioInitSegmentFetchTask(this);
|
|
197
|
+
audioSegmentFetchTask = makeAudioSegmentFetchTask(this);
|
|
198
|
+
audioInputTask = makeAudioInputTask(this);
|
|
199
|
+
audioSeekTask = makeAudioSeekTask(this);
|
|
294
200
|
|
|
295
|
-
|
|
296
|
-
},
|
|
297
|
-
});
|
|
201
|
+
audioBufferTask = makeAudioBufferTask(this);
|
|
298
202
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
task: async ([files], { signal: _signal }) => {
|
|
303
|
-
if (!files) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
if (!this.defaultVideoTrackId) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
const videoFile = files[this.defaultVideoTrackId];
|
|
310
|
-
if (!videoFile) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
// TODO: Extract to general cleanup function
|
|
314
|
-
for (const frame of this.videoAssetTask.value?.decodedFrames || []) {
|
|
315
|
-
frame.close();
|
|
316
|
-
}
|
|
317
|
-
this.videoAssetTask.value?.videoDecoder?.close();
|
|
318
|
-
return await VideoAsset.createFromReadableStream(
|
|
319
|
-
"video.mp4",
|
|
320
|
-
videoFile.stream(),
|
|
321
|
-
videoFile,
|
|
322
|
-
);
|
|
323
|
-
},
|
|
324
|
-
});
|
|
203
|
+
// Audio analysis tasks for frequency and time domain analysis
|
|
204
|
+
byteTimeDomainTask = makeAudioTimeDomainAnalysisTask(this);
|
|
205
|
+
frequencyDataTask = makeAudioFrequencyAnalysisTask(this);
|
|
325
206
|
|
|
326
|
-
|
|
327
|
-
|
|
207
|
+
/**
|
|
208
|
+
* The unique identifier for the media asset.
|
|
209
|
+
* This property can be set programmatically or via the "asset-id" attribute.
|
|
210
|
+
* @domAttribute "asset-id"
|
|
211
|
+
*/
|
|
212
|
+
@property({ type: String, attribute: "asset-id", reflect: true })
|
|
213
|
+
assetId: string | null = null;
|
|
328
214
|
|
|
329
|
-
|
|
330
|
-
this.
|
|
215
|
+
get intrinsicDurationMs() {
|
|
216
|
+
return this.mediaEngineTask.value?.durationMs ?? 0;
|
|
331
217
|
}
|
|
332
218
|
|
|
333
219
|
protected updated(
|
|
334
220
|
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
335
221
|
): void {
|
|
222
|
+
super.updated(changedProperties);
|
|
336
223
|
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
337
224
|
this.executeSeek(this.currentSourceTimeMs);
|
|
338
225
|
}
|
|
339
|
-
// TODO: this is copied straight from EFTimegroup.ts
|
|
340
|
-
// and should be refactored to be shared/reduce bad duplication of
|
|
341
|
-
// critical logic.
|
|
342
226
|
if (
|
|
343
227
|
changedProperties.has("currentTime") ||
|
|
344
228
|
changedProperties.has("ownCurrentTimeMs")
|
|
@@ -351,534 +235,92 @@ export class EFMedia extends EFTargetable(
|
|
|
351
235
|
return true;
|
|
352
236
|
}
|
|
353
237
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return 0;
|
|
357
|
-
}
|
|
238
|
+
@state()
|
|
239
|
+
private _desiredSeekTimeMs = 0; // Initialize to 0 for proper segment loading
|
|
358
240
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return (track.duration / track.timescale) * 1000;
|
|
362
|
-
},
|
|
363
|
-
);
|
|
364
|
-
if (durations.length === 0) {
|
|
365
|
-
return 0;
|
|
366
|
-
}
|
|
367
|
-
return Math.max(...durations);
|
|
241
|
+
get desiredSeekTimeMs() {
|
|
242
|
+
return this._desiredSeekTimeMs;
|
|
368
243
|
}
|
|
369
244
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
autoRun: EF_INTERACTIVE,
|
|
374
|
-
args: () => [this.fetchSeekTask.value, this.seekTask.value] as const,
|
|
375
|
-
task: async ([files, segments], { signal: _signal }) => {
|
|
376
|
-
if (!files) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
if (!segments) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
if (!this.defaultAudioTrackId) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
const segment = segments[this.defaultAudioTrackId];
|
|
386
|
-
if (!segment) {
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
const audioFile = files[this.defaultAudioTrackId];
|
|
390
|
-
if (!audioFile) {
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
return {
|
|
394
|
-
buffer: await this.#audioContext.decodeAudioData(
|
|
395
|
-
await audioFile.arrayBuffer(),
|
|
396
|
-
),
|
|
397
|
-
startOffsetMs: (segment.segment.cts / segment.track.timescale) * 1000,
|
|
398
|
-
};
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
async fetchAudioSpanningTime(fromMs: number, toMs: number) {
|
|
403
|
-
// Adjust range for track's own time
|
|
404
|
-
if (this.sourceInMs) {
|
|
405
|
-
fromMs -=
|
|
406
|
-
this.startTimeMs - (this.trimStartMs ?? 0) - (this.sourceInMs ?? 0);
|
|
407
|
-
}
|
|
408
|
-
if (this.sourceOutMs) {
|
|
409
|
-
toMs -=
|
|
410
|
-
this.startTimeMs - (this.trimStartMs ?? 0) - (this.sourceOutMs ?? 0);
|
|
411
|
-
}
|
|
412
|
-
fromMs -= this.startTimeMs - (this.trimStartMs ?? 0);
|
|
413
|
-
toMs -= this.startTimeMs - (this.trimStartMs ?? 0);
|
|
414
|
-
|
|
415
|
-
await this.trackFragmentIndexLoader.taskComplete;
|
|
416
|
-
const audioTrackId = this.defaultAudioTrackId;
|
|
417
|
-
if (!audioTrackId) {
|
|
418
|
-
log("No audio track found");
|
|
419
|
-
return;
|
|
245
|
+
set desiredSeekTimeMs(value: number) {
|
|
246
|
+
if (this._desiredSeekTimeMs !== value) {
|
|
247
|
+
this._desiredSeekTimeMs = value;
|
|
420
248
|
}
|
|
421
|
-
|
|
422
|
-
const audioTrackIndex = this.trackFragmentIndexLoader.value?.[audioTrackId];
|
|
423
|
-
if (!audioTrackIndex) {
|
|
424
|
-
log("No audio track found");
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const start = audioTrackIndex.initSegment.offset;
|
|
429
|
-
const end =
|
|
430
|
-
audioTrackIndex.initSegment.offset + audioTrackIndex.initSegment.size;
|
|
431
|
-
const audioInitFragmentRequest = this.fetch(
|
|
432
|
-
this.fragmentTrackPath(String(audioTrackId)),
|
|
433
|
-
{
|
|
434
|
-
headers: { Range: `bytes=${start}-${end - 1}` },
|
|
435
|
-
},
|
|
436
|
-
);
|
|
437
|
-
|
|
438
|
-
const fragments = Object.values(audioTrackIndex.segments).filter(
|
|
439
|
-
(segment) => {
|
|
440
|
-
const segmentStartsBeforeEnd =
|
|
441
|
-
segment.dts <= (toMs * audioTrackIndex.timescale) / 1000;
|
|
442
|
-
const segmentEndsAfterStart =
|
|
443
|
-
segment.dts + segment.duration >=
|
|
444
|
-
(fromMs * audioTrackIndex.timescale) / 1000;
|
|
445
|
-
return segmentStartsBeforeEnd && segmentEndsAfterStart;
|
|
446
|
-
},
|
|
447
|
-
);
|
|
448
|
-
|
|
449
|
-
const firstFragment = fragments[0];
|
|
450
|
-
if (!firstFragment) {
|
|
451
|
-
log("No audio fragments found");
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
const lastFragment = fragments[fragments.length - 1];
|
|
455
|
-
if (!lastFragment) {
|
|
456
|
-
log("No audio fragments found");
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
const fragmentStart = firstFragment.offset;
|
|
460
|
-
const fragmentEnd = lastFragment.offset + lastFragment.size;
|
|
461
|
-
|
|
462
|
-
const audioFragmentRequest = this.fetch(
|
|
463
|
-
this.fragmentTrackPath(String(audioTrackId)),
|
|
464
|
-
{
|
|
465
|
-
headers: { Range: `bytes=${fragmentStart}-${fragmentEnd - 1}` },
|
|
466
|
-
},
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
const initResponse = await audioInitFragmentRequest;
|
|
470
|
-
const dataResponse = await audioFragmentRequest;
|
|
471
|
-
|
|
472
|
-
const initBuffer = await initResponse.arrayBuffer();
|
|
473
|
-
const dataBuffer = await dataResponse.arrayBuffer();
|
|
474
|
-
|
|
475
|
-
const audioBlob = new Blob([initBuffer, dataBuffer], {
|
|
476
|
-
type: "audio/mp4",
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
return {
|
|
480
|
-
blob: audioBlob,
|
|
481
|
-
startMs:
|
|
482
|
-
(firstFragment.dts / audioTrackIndex.timescale) * 1000 -
|
|
483
|
-
(this.trimStartMs ?? 0),
|
|
484
|
-
endMs:
|
|
485
|
-
(lastFragment.dts / audioTrackIndex.timescale) * 1000 +
|
|
486
|
-
(lastFragment.duration / audioTrackIndex.timescale) * 1000 -
|
|
487
|
-
(this.trimEndMs ?? 0),
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
set fftSize(value: number) {
|
|
492
|
-
const oldValue = this.fftSize;
|
|
493
|
-
this.setAttribute("fft-size", String(value));
|
|
494
|
-
this.requestUpdate("fft-size", oldValue);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
set fftDecay(value: number) {
|
|
498
|
-
const oldValue = this.fftDecay;
|
|
499
|
-
this.setAttribute("fft-decay", String(value));
|
|
500
|
-
this.requestUpdate("fft-decay", oldValue);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
get fftSize() {
|
|
504
|
-
return Number.parseInt(this.getAttribute("fft-size") ?? "128", 10);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
get fftDecay() {
|
|
508
|
-
return Number.parseInt(this.getAttribute("fft-decay") ?? "8", 10);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
set interpolateFrequencies(value: boolean) {
|
|
512
|
-
const oldValue = this.interpolateFrequencies;
|
|
513
|
-
this.setAttribute("interpolate-frequencies", String(value));
|
|
514
|
-
this.requestUpdate("interpolate-frequencies", oldValue);
|
|
515
249
|
}
|
|
516
250
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
get shouldInterpolateFrequencies() {
|
|
522
|
-
if (this.hasAttribute("interpolate-frequencies")) {
|
|
523
|
-
return this.getAttribute("interpolate-frequencies") !== "false";
|
|
524
|
-
}
|
|
525
|
-
return false;
|
|
251
|
+
protected async executeSeek(seekToMs: number) {
|
|
252
|
+
this.desiredSeekTimeMs = seekToMs;
|
|
526
253
|
}
|
|
527
254
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (frequency < 8000) return 1.6;
|
|
545
|
-
return 2.0;
|
|
546
|
-
});
|
|
255
|
+
/**
|
|
256
|
+
* Main integration method for EFTimegroup audio playback
|
|
257
|
+
* Now powered by clean, testable utility functions
|
|
258
|
+
*/
|
|
259
|
+
async fetchAudioSpanningTime(
|
|
260
|
+
fromMs: number,
|
|
261
|
+
toMs: number,
|
|
262
|
+
signal: AbortSignal = new AbortController().signal,
|
|
263
|
+
): Promise<AudioSpan> {
|
|
264
|
+
// Reset MediaSourceManager for fresh playback session
|
|
265
|
+
await this.mediaSourceService.initialize();
|
|
266
|
+
|
|
267
|
+
// Use the clean, testable utility function
|
|
268
|
+
const { fetchAudioSpanningTime: fetchAudioSpan } = await import(
|
|
269
|
+
"./EFMedia/shared/AudioSpanUtils.ts"
|
|
270
|
+
);
|
|
547
271
|
|
|
548
|
-
|
|
549
|
-
return weights;
|
|
272
|
+
return fetchAudioSpan(this, fromMs, toMs, signal);
|
|
550
273
|
}
|
|
551
274
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
[
|
|
558
|
-
this.audioBufferTask.status,
|
|
559
|
-
this.currentSourceTimeMs,
|
|
560
|
-
this.fftSize,
|
|
561
|
-
this.fftDecay,
|
|
562
|
-
this.fftGain,
|
|
563
|
-
this.shouldInterpolateFrequencies,
|
|
564
|
-
] as const,
|
|
565
|
-
task: async () => {
|
|
566
|
-
await this.audioBufferTask.taskComplete;
|
|
567
|
-
if (!this.audioBufferTask.value) return null;
|
|
568
|
-
if (this.currentSourceTimeMs <= 0) return null;
|
|
569
|
-
|
|
570
|
-
const currentTimeMs = this.currentSourceTimeMs;
|
|
571
|
-
const startOffsetMs = this.audioBufferTask.value.startOffsetMs;
|
|
572
|
-
const audioBuffer = this.audioBufferTask.value.buffer;
|
|
573
|
-
|
|
574
|
-
const smoothedKey = `${this.shouldInterpolateFrequencies}:${this.fftSize}:${this.fftDecay}:${this.fftGain}:${startOffsetMs}:${currentTimeMs}`;
|
|
575
|
-
const cachedData = this.#byteTimeDomainCache.get(smoothedKey);
|
|
576
|
-
if (cachedData) return cachedData;
|
|
577
|
-
|
|
578
|
-
// Process multiple frames with decay, similar to the reference code
|
|
579
|
-
const framesData = await Promise.all(
|
|
580
|
-
Array.from({ length: this.fftDecay }, async (_, frameIndex) => {
|
|
581
|
-
const frameOffset = frameIndex * (1000 / 30);
|
|
582
|
-
const startTime = Math.max(
|
|
583
|
-
0,
|
|
584
|
-
(currentTimeMs - frameOffset - startOffsetMs) / 1000,
|
|
585
|
-
);
|
|
586
|
-
|
|
587
|
-
const cacheKey = `${this.shouldInterpolateFrequencies}:${this.fftSize}:${this.fftGain}:${startOffsetMs}:${startTime}`;
|
|
588
|
-
const cachedFrame = this.#byteTimeDomainCache.get(cacheKey);
|
|
589
|
-
if (cachedFrame) return cachedFrame;
|
|
590
|
-
|
|
591
|
-
const audioContext = new OfflineAudioContext(
|
|
592
|
-
2,
|
|
593
|
-
48000 * (1 / 30),
|
|
594
|
-
48000,
|
|
595
|
-
);
|
|
596
|
-
|
|
597
|
-
const source = audioContext.createBufferSource();
|
|
598
|
-
source.buffer = audioBuffer;
|
|
599
|
-
|
|
600
|
-
// Create analyzer for PCM data
|
|
601
|
-
const analyser = audioContext.createAnalyser();
|
|
602
|
-
analyser.fftSize = this.fftSize; // Ensure enough samples
|
|
603
|
-
analyser.minDecibels = -90;
|
|
604
|
-
analyser.maxDecibels = -20;
|
|
605
|
-
|
|
606
|
-
const gainNode = audioContext.createGain();
|
|
607
|
-
gainNode.gain.value = this.fftGain; // Amplify the signal
|
|
608
|
-
|
|
609
|
-
source.connect(gainNode);
|
|
610
|
-
gainNode.connect(analyser);
|
|
611
|
-
analyser.connect(audioContext.destination);
|
|
612
|
-
|
|
613
|
-
source.start(0, startTime, 1 / 30);
|
|
614
|
-
|
|
615
|
-
const dataLength = analyser.fftSize / 2;
|
|
616
|
-
try {
|
|
617
|
-
await audioContext.startRendering();
|
|
618
|
-
const frameData = new Uint8Array(dataLength);
|
|
619
|
-
analyser.getByteTimeDomainData(frameData);
|
|
620
|
-
|
|
621
|
-
// const points = frameData;
|
|
622
|
-
// Calculate RMS and midpoint values
|
|
623
|
-
const points = new Uint8Array(dataLength);
|
|
624
|
-
for (let i = 0; i < dataLength; i++) {
|
|
625
|
-
const pointSamples = frameData.slice(
|
|
626
|
-
i * (frameData.length / dataLength),
|
|
627
|
-
(i + 1) * (frameData.length / dataLength),
|
|
628
|
-
);
|
|
629
|
-
|
|
630
|
-
// Calculate RMS while preserving sign
|
|
631
|
-
const rms = Math.sqrt(
|
|
632
|
-
pointSamples.reduce((sum, sample) => {
|
|
633
|
-
const normalized = (sample - 128) / 128;
|
|
634
|
-
return sum + normalized * normalized;
|
|
635
|
-
}, 0) / pointSamples.length,
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
// Get average sign of the samples to determine direction
|
|
639
|
-
const avgSign = Math.sign(
|
|
640
|
-
pointSamples.reduce((sum, sample) => sum + (sample - 128), 0),
|
|
641
|
-
);
|
|
642
|
-
|
|
643
|
-
// Convert RMS back to byte range, preserving direction
|
|
644
|
-
points[i] = Math.min(255, Math.round(128 + avgSign * rms * 128));
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
this.#byteTimeDomainCache.set(cacheKey, points);
|
|
648
|
-
return points;
|
|
649
|
-
} finally {
|
|
650
|
-
source.disconnect();
|
|
651
|
-
analyser.disconnect();
|
|
652
|
-
}
|
|
653
|
-
}),
|
|
654
|
-
);
|
|
655
|
-
|
|
656
|
-
// Combine frames with decay weighting
|
|
657
|
-
const frameLength = framesData[0]?.length ?? 0;
|
|
658
|
-
const smoothedData = new Uint8Array(frameLength);
|
|
659
|
-
|
|
660
|
-
for (let i = 0; i < frameLength; i++) {
|
|
661
|
-
let weightedSum = 0;
|
|
662
|
-
let weightSum = 0;
|
|
663
|
-
|
|
664
|
-
framesData.forEach((frame, frameIndex) => {
|
|
665
|
-
const decayWeight = EFMedia.DECAY_WEIGHT ** frameIndex;
|
|
666
|
-
weightedSum += (frame[i] ?? 0) * decayWeight;
|
|
667
|
-
weightSum += decayWeight;
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
this.#byteTimeDomainCache.set(smoothedKey, smoothedData);
|
|
674
|
-
return smoothedData;
|
|
675
|
-
},
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
#frequencyDataCache = new LRUCache<string, Uint8Array>(100);
|
|
679
|
-
|
|
680
|
-
frequencyDataTask = new Task(this, {
|
|
681
|
-
autoRun: EF_INTERACTIVE,
|
|
682
|
-
args: () =>
|
|
683
|
-
[
|
|
684
|
-
this.audioBufferTask.status,
|
|
685
|
-
this.currentSourceTimeMs,
|
|
686
|
-
this.fftSize,
|
|
687
|
-
this.fftDecay,
|
|
688
|
-
this.fftGain,
|
|
689
|
-
this.shouldInterpolateFrequencies,
|
|
690
|
-
] as const,
|
|
691
|
-
task: async () => {
|
|
692
|
-
await this.audioBufferTask.taskComplete;
|
|
693
|
-
if (!this.audioBufferTask.value) return null;
|
|
694
|
-
if (this.currentSourceTimeMs <= 0) return null;
|
|
695
|
-
|
|
696
|
-
const currentTimeMs = this.currentSourceTimeMs;
|
|
697
|
-
const startOffsetMs = this.audioBufferTask.value.startOffsetMs;
|
|
698
|
-
const audioBuffer = this.audioBufferTask.value.buffer;
|
|
699
|
-
const smoothedKey = `${this.shouldInterpolateFrequencies}:${this.fftSize}:${this.fftDecay}:${this.fftGain}:${startOffsetMs}:${currentTimeMs}`;
|
|
700
|
-
|
|
701
|
-
const cachedSmoothedData = this.#frequencyDataCache.get(smoothedKey);
|
|
702
|
-
if (cachedSmoothedData) {
|
|
703
|
-
return cachedSmoothedData;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const framesData = await Promise.all(
|
|
707
|
-
Array.from({ length: this.fftDecay }, async (_, i) => {
|
|
708
|
-
const frameOffset = i * (1000 / 30);
|
|
709
|
-
const startTime = Math.max(
|
|
710
|
-
0,
|
|
711
|
-
(currentTimeMs - frameOffset - startOffsetMs) / 1000,
|
|
712
|
-
);
|
|
713
|
-
|
|
714
|
-
// Cache key for this specific frame
|
|
715
|
-
const cacheKey = `${this.shouldInterpolateFrequencies}:${this.fftSize}:${this.fftGain}:${startOffsetMs}:${startTime}`;
|
|
716
|
-
|
|
717
|
-
// Check cache for this specific frame
|
|
718
|
-
const cachedFrame = this.#frequencyDataCache.get(cacheKey);
|
|
719
|
-
if (cachedFrame) {
|
|
720
|
-
return cachedFrame;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
const audioContext = new OfflineAudioContext(
|
|
724
|
-
2,
|
|
725
|
-
48000 * (1 / 30),
|
|
726
|
-
48000,
|
|
727
|
-
);
|
|
728
|
-
const analyser = audioContext.createAnalyser();
|
|
729
|
-
analyser.fftSize = this.fftSize;
|
|
730
|
-
analyser.minDecibels = -90;
|
|
731
|
-
analyser.maxDecibels = -10;
|
|
732
|
-
|
|
733
|
-
const gainNode = audioContext.createGain();
|
|
734
|
-
gainNode.gain.value = this.fftGain;
|
|
735
|
-
|
|
736
|
-
const filter = audioContext.createBiquadFilter();
|
|
737
|
-
filter.type = "bandpass";
|
|
738
|
-
filter.frequency.value = 15000;
|
|
739
|
-
filter.Q.value = 0.05;
|
|
740
|
-
|
|
741
|
-
const audioBufferSource = audioContext.createBufferSource();
|
|
742
|
-
audioBufferSource.buffer = audioBuffer;
|
|
743
|
-
|
|
744
|
-
audioBufferSource.connect(filter);
|
|
745
|
-
filter.connect(gainNode);
|
|
746
|
-
gainNode.connect(analyser);
|
|
747
|
-
analyser.connect(audioContext.destination);
|
|
748
|
-
|
|
749
|
-
audioBufferSource.start(0, startTime, 1 / 30);
|
|
750
|
-
|
|
751
|
-
try {
|
|
752
|
-
await audioContext.startRendering();
|
|
753
|
-
const frameData = new Uint8Array(this.fftSize / 2);
|
|
754
|
-
analyser.getByteFrequencyData(frameData);
|
|
755
|
-
|
|
756
|
-
// Cache this frame's analysis
|
|
757
|
-
this.#frequencyDataCache.set(cacheKey, frameData);
|
|
758
|
-
return frameData;
|
|
759
|
-
} finally {
|
|
760
|
-
audioBufferSource.disconnect();
|
|
761
|
-
analyser.disconnect();
|
|
762
|
-
}
|
|
763
|
-
}),
|
|
764
|
-
);
|
|
765
|
-
|
|
766
|
-
const frameLength = framesData[0]?.length ?? 0;
|
|
767
|
-
|
|
768
|
-
// Combine frames with decay
|
|
769
|
-
const smoothedData = new Uint8Array(frameLength);
|
|
770
|
-
for (let i = 0; i < frameLength; i++) {
|
|
771
|
-
let weightedSum = 0;
|
|
772
|
-
let weightSum = 0;
|
|
773
|
-
|
|
774
|
-
framesData.forEach((frame, frameIndex) => {
|
|
775
|
-
const decayWeight = EFMedia.DECAY_WEIGHT ** frameIndex;
|
|
776
|
-
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
777
|
-
weightedSum += frame[i]! * decayWeight;
|
|
778
|
-
weightSum += decayWeight;
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// Apply frequency weights using instance FREQ_WEIGHTS
|
|
785
|
-
smoothedData.forEach((value, i) => {
|
|
786
|
-
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
787
|
-
const freqWeight = this.FREQ_WEIGHTS[i]!;
|
|
788
|
-
smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
// Only return the lower half of the frequency data
|
|
792
|
-
// The top half is zeroed out, which makes for aesthetically unpleasing waveforms
|
|
793
|
-
const slicedData = smoothedData.slice(
|
|
794
|
-
0,
|
|
795
|
-
Math.floor(smoothedData.length / 2),
|
|
796
|
-
);
|
|
797
|
-
const processedData = this.shouldInterpolateFrequencies
|
|
798
|
-
? processFFTData(slicedData)
|
|
799
|
-
: slicedData;
|
|
800
|
-
this.#frequencyDataCache.set(smoothedKey, processedData);
|
|
801
|
-
return processedData;
|
|
802
|
-
},
|
|
803
|
-
});
|
|
804
|
-
|
|
805
|
-
set fftGain(value: number) {
|
|
806
|
-
const oldValue = this.fftGain;
|
|
807
|
-
this.setAttribute("fft-gain", String(value));
|
|
808
|
-
this.requestUpdate("fft-gain", oldValue);
|
|
275
|
+
/**
|
|
276
|
+
* Get the HTML audio element for ContextMixin integration
|
|
277
|
+
*/
|
|
278
|
+
get audioElement(): HTMLAudioElement | null {
|
|
279
|
+
return this.mediaSourceService.getAudioElement();
|
|
809
280
|
}
|
|
810
281
|
|
|
811
|
-
|
|
812
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Check if an audio segment is cached in the unified buffer system
|
|
284
|
+
* Now uses the same caching approach as video for consistency
|
|
285
|
+
*/
|
|
286
|
+
getCachedAudioSegment(segmentId: number): boolean {
|
|
287
|
+
return this.audioBufferTask.value?.cachedSegments.has(segmentId) ?? false;
|
|
813
288
|
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
function processFFTData(fftData: Uint8Array, zeroThresholdPercent = 0.1) {
|
|
817
|
-
// Step 1: Determine the threshold for zeros
|
|
818
|
-
const totalBins = fftData.length;
|
|
819
|
-
const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
|
|
820
|
-
|
|
821
|
-
// Step 2: Interrogate the FFT output to find the cutoff point
|
|
822
|
-
let zeroCount = 0;
|
|
823
|
-
let cutoffIndex = totalBins; // Default to the end of the array
|
|
824
289
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
break;
|
|
834
|
-
}
|
|
290
|
+
/**
|
|
291
|
+
* Get cached audio segments from the unified buffer system
|
|
292
|
+
* Now uses the same caching approach as video for consistency
|
|
293
|
+
*/
|
|
294
|
+
getCachedAudioSegments(segmentIds: number[]): Set<number> {
|
|
295
|
+
const bufferState = this.audioBufferTask.value;
|
|
296
|
+
if (!bufferState) {
|
|
297
|
+
return new Set();
|
|
835
298
|
}
|
|
299
|
+
return new Set(
|
|
300
|
+
segmentIds.filter((id) => bufferState.cachedSegments.has(id)),
|
|
301
|
+
);
|
|
836
302
|
}
|
|
837
303
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
// Calculate attenuation factor that goes from 1 to 0 over the top 10%
|
|
850
|
-
const attenuationProgress =
|
|
851
|
-
(i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;
|
|
852
|
-
const attenuationFactor = Math.max(0, 1 - attenuationProgress);
|
|
853
|
-
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
854
|
-
resampledData[i] = Math.floor(resampledData[i]! * attenuationFactor);
|
|
304
|
+
/**
|
|
305
|
+
* Get MediaElementAudioSourceNode for ContextMixin integration
|
|
306
|
+
* Uses AudioElementFactory for proper caching and lifecycle management
|
|
307
|
+
*/
|
|
308
|
+
async getMediaElementSource(
|
|
309
|
+
audioContext: AudioContext,
|
|
310
|
+
): Promise<MediaElementAudioSourceNode> {
|
|
311
|
+
return this.audioElementFactory.createMediaElementSource(
|
|
312
|
+
audioContext,
|
|
313
|
+
this.mediaSourceService,
|
|
314
|
+
);
|
|
855
315
|
}
|
|
856
316
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
function interpolateData(data: Uint8Array, targetSize: number) {
|
|
861
|
-
const resampled = new Uint8Array(targetSize);
|
|
862
|
-
const dataLength = data.length;
|
|
317
|
+
disconnectedCallback(): void {
|
|
318
|
+
super.disconnectedCallback?.();
|
|
863
319
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const ratio = (i / (targetSize - 1)) * (dataLength - 1);
|
|
867
|
-
const index = Math.floor(ratio);
|
|
868
|
-
const fraction = ratio - index;
|
|
320
|
+
// Clean up MediaSource service
|
|
321
|
+
this.mediaSourceService.cleanup();
|
|
869
322
|
|
|
870
|
-
//
|
|
871
|
-
|
|
872
|
-
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
873
|
-
resampled[i] = data[dataLength - 1]!; // Last value
|
|
874
|
-
} else {
|
|
875
|
-
// Linear interpolation
|
|
876
|
-
resampled[i] = Math.round(
|
|
877
|
-
// biome-ignore lint/style/noNonNullAssertion: Manual bounds check
|
|
878
|
-
data[index]! * (1 - fraction) + data[index + 1]! * fraction,
|
|
879
|
-
);
|
|
880
|
-
}
|
|
323
|
+
// Clear audio element factory cache
|
|
324
|
+
this.audioElementFactory.clearCache();
|
|
881
325
|
}
|
|
882
|
-
|
|
883
|
-
return resampled;
|
|
884
326
|
}
|