@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
|
@@ -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
|
+
}
|