@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,104 @@
|
|
|
1
|
+
import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
|
|
2
|
+
import { LRUCache } from "../../../utils/LRUCache.js";
|
|
3
|
+
import { Task } from "@lit/task";
|
|
4
|
+
const DECAY_WEIGHT = .8;
|
|
5
|
+
function makeAudioTimeDomainAnalysisTask(element) {
|
|
6
|
+
const cache = new LRUCache(1e3);
|
|
7
|
+
return new Task(element, {
|
|
8
|
+
autoRun: EF_INTERACTIVE,
|
|
9
|
+
onError: (error) => {
|
|
10
|
+
console.error("byteTimeDomainTask error", error);
|
|
11
|
+
},
|
|
12
|
+
args: () => [
|
|
13
|
+
element.audioBufferTask.status,
|
|
14
|
+
element.currentSourceTimeMs,
|
|
15
|
+
element.fftSize,
|
|
16
|
+
element.fftDecay,
|
|
17
|
+
element.fftGain,
|
|
18
|
+
element.shouldInterpolateFrequencies
|
|
19
|
+
],
|
|
20
|
+
task: async () => {
|
|
21
|
+
await element.audioBufferTask.taskComplete;
|
|
22
|
+
if (!element.audioBufferTask.value) return null;
|
|
23
|
+
if (element.currentSourceTimeMs < 0) return null;
|
|
24
|
+
const currentTimeMs = element.currentSourceTimeMs;
|
|
25
|
+
const analysisWindowMs = 5e3;
|
|
26
|
+
const fromMs = Math.max(0, currentTimeMs);
|
|
27
|
+
const toMs = fromMs + analysisWindowMs;
|
|
28
|
+
const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
|
|
29
|
+
const audioSpan = await fetchAudioSpan(element, fromMs, toMs, new AbortController().signal);
|
|
30
|
+
if (!audioSpan || !audioSpan.blob) {
|
|
31
|
+
console.warn("Time domain analysis skipped: no audio data available");
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
|
|
35
|
+
const arrayBuffer = await audioSpan.blob.arrayBuffer();
|
|
36
|
+
const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
|
|
37
|
+
const startOffsetMs = audioSpan.startMs;
|
|
38
|
+
const smoothedKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${startOffsetMs}:${currentTimeMs}`;
|
|
39
|
+
const cachedData = cache.get(smoothedKey);
|
|
40
|
+
if (cachedData) return cachedData;
|
|
41
|
+
const framesData = await Promise.all(Array.from({ length: element.fftDecay }, async (_, frameIndex) => {
|
|
42
|
+
const frameOffset = frameIndex * (1e3 / 30);
|
|
43
|
+
const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
|
|
44
|
+
const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
|
|
45
|
+
const cachedFrame = cache.get(cacheKey);
|
|
46
|
+
if (cachedFrame) return cachedFrame;
|
|
47
|
+
let audioContext;
|
|
48
|
+
try {
|
|
49
|
+
audioContext = new OfflineAudioContext(2, 48e3 * (1 / 30), 48e3);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`[EFMedia.byteTimeDomainTask] Failed to create OfflineAudioContext(2, ${48e3 * (1 / 30)}, 48000) for frame ${frameIndex} at time ${startTime}s: ${error instanceof Error ? error.message : String(error)}. This is for audio time domain analysis.`);
|
|
52
|
+
}
|
|
53
|
+
const source = audioContext.createBufferSource();
|
|
54
|
+
source.buffer = audioBuffer;
|
|
55
|
+
const analyser = audioContext.createAnalyser();
|
|
56
|
+
analyser.fftSize = element.fftSize;
|
|
57
|
+
analyser.minDecibels = -90;
|
|
58
|
+
analyser.maxDecibels = -20;
|
|
59
|
+
const gainNode = audioContext.createGain();
|
|
60
|
+
gainNode.gain.value = element.fftGain;
|
|
61
|
+
source.connect(gainNode);
|
|
62
|
+
gainNode.connect(analyser);
|
|
63
|
+
analyser.connect(audioContext.destination);
|
|
64
|
+
source.start(0, startTime, 1 / 30);
|
|
65
|
+
const dataLength = analyser.fftSize / 2;
|
|
66
|
+
try {
|
|
67
|
+
await audioContext.startRendering();
|
|
68
|
+
const frameData = new Uint8Array(dataLength);
|
|
69
|
+
analyser.getByteTimeDomainData(frameData);
|
|
70
|
+
const points = new Uint8Array(dataLength);
|
|
71
|
+
for (let i = 0; i < dataLength; i++) {
|
|
72
|
+
const pointSamples = frameData.slice(i * (frameData.length / dataLength), (i + 1) * (frameData.length / dataLength));
|
|
73
|
+
const rms = Math.sqrt(pointSamples.reduce((sum, sample) => {
|
|
74
|
+
const normalized = (sample - 128) / 128;
|
|
75
|
+
return sum + normalized * normalized;
|
|
76
|
+
}, 0) / pointSamples.length);
|
|
77
|
+
const avgSign = Math.sign(pointSamples.reduce((sum, sample) => sum + (sample - 128), 0));
|
|
78
|
+
points[i] = Math.min(255, Math.round(128 + avgSign * rms * 128));
|
|
79
|
+
}
|
|
80
|
+
cache.set(cacheKey, points);
|
|
81
|
+
return points;
|
|
82
|
+
} finally {
|
|
83
|
+
source.disconnect();
|
|
84
|
+
analyser.disconnect();
|
|
85
|
+
}
|
|
86
|
+
}));
|
|
87
|
+
const frameLength = framesData[0]?.length ?? 0;
|
|
88
|
+
const smoothedData = new Uint8Array(frameLength);
|
|
89
|
+
for (let i = 0; i < frameLength; i++) {
|
|
90
|
+
let weightedSum = 0;
|
|
91
|
+
let weightSum = 0;
|
|
92
|
+
framesData.forEach((frame, frameIndex) => {
|
|
93
|
+
const decayWeight = DECAY_WEIGHT ** frameIndex;
|
|
94
|
+
weightedSum += (frame[i] ?? 0) * decayWeight;
|
|
95
|
+
weightSum += decayWeight;
|
|
96
|
+
});
|
|
97
|
+
smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
|
|
98
|
+
}
|
|
99
|
+
cache.set(smoothedKey, smoothedData);
|
|
100
|
+
return smoothedData;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export { makeAudioTimeDomainAnalysisTask };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MediaSourceService } from './MediaSourceService.js';
|
|
2
|
+
/**
|
|
3
|
+
* Factory for creating and caching MediaElementAudioSourceNode instances
|
|
4
|
+
* Handles the complex lifecycle and caching logic previously embedded in EFMedia
|
|
5
|
+
*/
|
|
6
|
+
export declare class AudioElementFactory {
|
|
7
|
+
private cache;
|
|
8
|
+
private currentSource;
|
|
9
|
+
private currentAudioContext;
|
|
10
|
+
/**
|
|
11
|
+
* Create or retrieve cached MediaElementAudioSourceNode for the given AudioContext
|
|
12
|
+
*/
|
|
13
|
+
createMediaElementSource(audioContext: AudioContext, mediaSourceService: MediaSourceService): Promise<MediaElementAudioSourceNode>;
|
|
14
|
+
/**
|
|
15
|
+
* Clear all cached sources (useful for testing or cleanup)
|
|
16
|
+
*/
|
|
17
|
+
clearCache(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Check if we have a cached source for the given AudioContext
|
|
20
|
+
*/
|
|
21
|
+
hasCachedSource(audioContext: AudioContext): boolean;
|
|
22
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating and caching MediaElementAudioSourceNode instances
|
|
3
|
+
* Handles the complex lifecycle and caching logic previously embedded in EFMedia
|
|
4
|
+
*/
|
|
5
|
+
var AudioElementFactory = class {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.cache = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
this.currentSource = null;
|
|
9
|
+
this.currentAudioContext = null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create or retrieve cached MediaElementAudioSourceNode for the given AudioContext
|
|
13
|
+
*/
|
|
14
|
+
async createMediaElementSource(audioContext, mediaSourceService) {
|
|
15
|
+
const cached = this.cache.get(audioContext);
|
|
16
|
+
if (cached && audioContext.state !== "closed" && this.currentAudioContext === audioContext) return cached;
|
|
17
|
+
if (this.currentSource && this.currentAudioContext !== audioContext) {
|
|
18
|
+
this.currentSource.disconnect();
|
|
19
|
+
if (this.currentAudioContext) this.cache.delete(this.currentAudioContext);
|
|
20
|
+
this.currentSource = null;
|
|
21
|
+
this.currentAudioContext = null;
|
|
22
|
+
}
|
|
23
|
+
await mediaSourceService.ensureInitialized();
|
|
24
|
+
const audioElement = mediaSourceService.getAudioElement();
|
|
25
|
+
if (!audioElement) throw new Error("Audio element not available from MediaSourceService");
|
|
26
|
+
let mediaElementSource;
|
|
27
|
+
try {
|
|
28
|
+
mediaElementSource = audioContext.createMediaElementSource(audioElement);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof Error && error.message.includes("already connected")) {
|
|
31
|
+
this.clearCache();
|
|
32
|
+
try {
|
|
33
|
+
mediaElementSource = audioContext.createMediaElementSource(audioElement);
|
|
34
|
+
} catch (retryError) {
|
|
35
|
+
console.warn("AudioElementFactory: Failed to create MediaElementSource even after clearing cache:", retryError);
|
|
36
|
+
throw retryError;
|
|
37
|
+
}
|
|
38
|
+
} else throw error;
|
|
39
|
+
}
|
|
40
|
+
this.currentSource = mediaElementSource;
|
|
41
|
+
this.currentAudioContext = audioContext;
|
|
42
|
+
this.cache.set(audioContext, mediaElementSource);
|
|
43
|
+
const cleanup = () => {
|
|
44
|
+
if (audioContext.state === "closed") {
|
|
45
|
+
this.cache.delete(audioContext);
|
|
46
|
+
if (this.currentAudioContext === audioContext) {
|
|
47
|
+
this.currentSource = null;
|
|
48
|
+
this.currentAudioContext = null;
|
|
49
|
+
}
|
|
50
|
+
audioContext.removeEventListener("statechange", cleanup);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
audioContext.addEventListener("statechange", cleanup);
|
|
54
|
+
return mediaElementSource;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Clear all cached sources (useful for testing or cleanup)
|
|
58
|
+
*/
|
|
59
|
+
clearCache() {
|
|
60
|
+
if (this.currentSource) this.currentSource.disconnect();
|
|
61
|
+
this.cache = /* @__PURE__ */ new WeakMap();
|
|
62
|
+
this.currentSource = null;
|
|
63
|
+
this.currentAudioContext = null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if we have a cached source for the given AudioContext
|
|
67
|
+
*/
|
|
68
|
+
hasCachedSource(audioContext) {
|
|
69
|
+
return this.cache.has(audioContext) && audioContext.state !== "closed";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
export { AudioElementFactory };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface MediaSourceServiceOptions {
|
|
2
|
+
onError?: (error: Error) => void;
|
|
3
|
+
onReady?: () => void;
|
|
4
|
+
onUpdateEnd?: () => void;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Service for managing MediaSource lifecycle and audio element creation
|
|
9
|
+
* Extracted from EFMedia to improve separation of concerns and testability
|
|
10
|
+
*/
|
|
11
|
+
export declare class MediaSourceService {
|
|
12
|
+
private mediaSourceManager;
|
|
13
|
+
private options;
|
|
14
|
+
constructor(options?: MediaSourceServiceOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Initialize MediaSource if not already initialized
|
|
17
|
+
*/
|
|
18
|
+
ensureInitialized(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Initialize fresh MediaSource
|
|
21
|
+
*/
|
|
22
|
+
initialize(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Get audio element for MediaElementSource creation
|
|
25
|
+
*/
|
|
26
|
+
getAudioElement(): HTMLAudioElement | null;
|
|
27
|
+
/**
|
|
28
|
+
* Feed audio segments to MediaSource
|
|
29
|
+
*/
|
|
30
|
+
feedSegment(segmentBuffer: ArrayBuffer): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if MediaSource is ready
|
|
33
|
+
*/
|
|
34
|
+
isReady(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Get buffered time ranges
|
|
37
|
+
*/
|
|
38
|
+
getBuffered(): TimeRanges | null;
|
|
39
|
+
/**
|
|
40
|
+
* Set audio element current time
|
|
41
|
+
*/
|
|
42
|
+
setCurrentTime(timeMs: number): void;
|
|
43
|
+
/**
|
|
44
|
+
* Clean up MediaSource resources
|
|
45
|
+
*/
|
|
46
|
+
cleanup(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { MediaSourceManager } from "../../../services/MediaSourceManager.js";
|
|
2
|
+
/**
|
|
3
|
+
* Service for managing MediaSource lifecycle and audio element creation
|
|
4
|
+
* Extracted from EFMedia to improve separation of concerns and testability
|
|
5
|
+
*/
|
|
6
|
+
var MediaSourceService = class {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.mediaSourceManager = null;
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Initialize MediaSource if not already initialized
|
|
13
|
+
*/
|
|
14
|
+
async ensureInitialized() {
|
|
15
|
+
if (this.mediaSourceManager?.isReady()) return;
|
|
16
|
+
await this.initialize();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Initialize fresh MediaSource
|
|
20
|
+
*/
|
|
21
|
+
async initialize() {
|
|
22
|
+
this.cleanup();
|
|
23
|
+
const managerOptions = {
|
|
24
|
+
onError: this.options.onError,
|
|
25
|
+
onReady: this.options.onReady,
|
|
26
|
+
onUpdateEnd: this.options.onUpdateEnd,
|
|
27
|
+
timeout: this.options.timeout
|
|
28
|
+
};
|
|
29
|
+
this.mediaSourceManager = new MediaSourceManager(managerOptions);
|
|
30
|
+
await this.mediaSourceManager.initialize();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get audio element for MediaElementSource creation
|
|
34
|
+
*/
|
|
35
|
+
getAudioElement() {
|
|
36
|
+
return this.mediaSourceManager?.getAudioElement() || null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Feed audio segments to MediaSource
|
|
40
|
+
*/
|
|
41
|
+
async feedSegment(segmentBuffer) {
|
|
42
|
+
await this.ensureInitialized();
|
|
43
|
+
if (this.mediaSourceManager) await this.mediaSourceManager.feedSegment(segmentBuffer);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if MediaSource is ready
|
|
47
|
+
*/
|
|
48
|
+
isReady() {
|
|
49
|
+
return this.mediaSourceManager?.isReady() ?? false;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get buffered time ranges
|
|
53
|
+
*/
|
|
54
|
+
getBuffered() {
|
|
55
|
+
return this.mediaSourceManager?.getBuffered() || null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Set audio element current time
|
|
59
|
+
*/
|
|
60
|
+
setCurrentTime(timeMs) {
|
|
61
|
+
this.mediaSourceManager?.setCurrentTime(timeMs);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Clean up MediaSource resources
|
|
65
|
+
*/
|
|
66
|
+
cleanup() {
|
|
67
|
+
if (this.mediaSourceManager) {
|
|
68
|
+
this.mediaSourceManager.cleanup();
|
|
69
|
+
this.mediaSourceManager = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
export { MediaSourceService };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AudioSpan } from '../../../transcoding/types';
|
|
2
|
+
import { EFMedia } from '../../EFMedia';
|
|
3
|
+
/**
|
|
4
|
+
* Fetch audio spanning a time range
|
|
5
|
+
* Main function that orchestrates segment calculation, fetching, and blob creation
|
|
6
|
+
*/
|
|
7
|
+
export declare const fetchAudioSpanningTime: (host: EFMedia, fromMs: number, toMs: number, signal: AbortSignal) => Promise<AudioSpan>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch audio segment data using MediaEngine
|
|
3
|
+
* Pure function with explicit dependencies
|
|
4
|
+
*/
|
|
5
|
+
const fetchAudioSegmentData = async (segmentIds, mediaEngine, signal) => {
|
|
6
|
+
const audioRendition = mediaEngine.audioRendition;
|
|
7
|
+
if (!audioRendition) throw new Error("Audio rendition not available");
|
|
8
|
+
const segmentData = /* @__PURE__ */ new Map();
|
|
9
|
+
const fetchPromises = segmentIds.map(async (segmentId) => {
|
|
10
|
+
const arrayBuffer = await mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);
|
|
11
|
+
return [segmentId, arrayBuffer];
|
|
12
|
+
});
|
|
13
|
+
const fetchedSegments = await Promise.all(fetchPromises);
|
|
14
|
+
signal.throwIfAborted();
|
|
15
|
+
for (const [segmentId, arrayBuffer] of fetchedSegments) segmentData.set(segmentId, arrayBuffer);
|
|
16
|
+
return segmentData;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Create audio span blob from init segment and media segments
|
|
20
|
+
* Pure function for blob creation
|
|
21
|
+
*/
|
|
22
|
+
const createAudioSpanBlob = (initSegment, mediaSegments) => {
|
|
23
|
+
const chunks = [initSegment, ...mediaSegments];
|
|
24
|
+
return new Blob(chunks, { type: "audio/mp4" });
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Fetch audio spanning a time range
|
|
28
|
+
* Main function that orchestrates segment calculation, fetching, and blob creation
|
|
29
|
+
*/
|
|
30
|
+
const fetchAudioSpanningTime = async (host, fromMs, toMs, signal) => {
|
|
31
|
+
if (fromMs >= toMs || fromMs < 0) throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);
|
|
32
|
+
const mediaEngine = await host.mediaEngineTask.taskComplete;
|
|
33
|
+
const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
|
|
34
|
+
if (!mediaEngine?.audioRendition) throw new Error("Audio rendition not available");
|
|
35
|
+
if (!initSegment) throw new Error("Audio init segment is not available");
|
|
36
|
+
const segmentRanges = mediaEngine.calculateAudioSegmentRange(fromMs, toMs, mediaEngine.audioRendition, host.intrinsicDurationMs || 1e4);
|
|
37
|
+
if (segmentRanges.length === 0) throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);
|
|
38
|
+
const segmentIds = segmentRanges.map((r) => r.segmentId);
|
|
39
|
+
const segmentData = await fetchAudioSegmentData(segmentIds, mediaEngine, signal);
|
|
40
|
+
const orderedSegments = segmentIds.map((id) => {
|
|
41
|
+
const segment = segmentData.get(id);
|
|
42
|
+
if (!segment) throw new Error(`Missing segment data for segment ID ${id}`);
|
|
43
|
+
return segment;
|
|
44
|
+
});
|
|
45
|
+
const blob = createAudioSpanBlob(initSegment, orderedSegments);
|
|
46
|
+
const actualStartMs = Math.min(...segmentRanges.map((r) => r.startMs));
|
|
47
|
+
const actualEndMs = Math.max(...segmentRanges.map((r) => r.endMs));
|
|
48
|
+
return {
|
|
49
|
+
startMs: actualStartMs,
|
|
50
|
+
endMs: actualEndMs,
|
|
51
|
+
blob
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
export { fetchAudioSpanningTime };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AudioRendition, VideoRendition } from '../../../transcoding/types';
|
|
2
|
+
/**
|
|
3
|
+
* State interface for media buffering - generic for both audio and video
|
|
4
|
+
*/
|
|
5
|
+
export interface MediaBufferState {
|
|
6
|
+
currentSeekTimeMs: number;
|
|
7
|
+
activeRequests: Set<number>;
|
|
8
|
+
cachedSegments: Set<number>;
|
|
9
|
+
requestQueue: number[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Configuration interface for media buffering - generic for both audio and video
|
|
13
|
+
*/
|
|
14
|
+
export interface MediaBufferConfig {
|
|
15
|
+
bufferDurationMs: number;
|
|
16
|
+
maxParallelFetches: number;
|
|
17
|
+
enableBuffering: boolean;
|
|
18
|
+
enableContinuousBuffering?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Dependencies interface for media buffering - generic for both audio and video
|
|
22
|
+
*/
|
|
23
|
+
export interface MediaBufferDependencies<T extends AudioRendition | VideoRendition> {
|
|
24
|
+
computeSegmentId: (timeMs: number, rendition: T) => Promise<number | undefined>;
|
|
25
|
+
fetchSegment: (segmentId: number, rendition: T) => Promise<ArrayBuffer>;
|
|
26
|
+
getRendition: () => Promise<T>;
|
|
27
|
+
logError: (message: string, error: any) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Compute segment range for a time window
|
|
31
|
+
* Pure function - determines which segments are needed for a time range
|
|
32
|
+
*/
|
|
33
|
+
export declare const computeSegmentRange: <T extends AudioRendition | VideoRendition>(startTimeMs: number, endTimeMs: number, rendition: T, computeSegmentId: (timeMs: number, rendition: T) => number | undefined) => number[];
|
|
34
|
+
/**
|
|
35
|
+
* Async version of computeSegmentRange for when computeSegmentId is async
|
|
36
|
+
*/
|
|
37
|
+
export declare const computeSegmentRangeAsync: <T extends AudioRendition | VideoRendition>(startTimeMs: number, endTimeMs: number, durationMs: number, rendition: T, computeSegmentId: (timeMs: number, rendition: T) => Promise<number | undefined>) => Promise<number[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Compute buffer queue based on current state and desired segments
|
|
40
|
+
* Pure function - determines what segments should be fetched
|
|
41
|
+
*/
|
|
42
|
+
export declare const computeBufferQueue: (desiredSegments: number[], activeRequests: Set<number>, cachedSegments: Set<number>) => number[];
|
|
43
|
+
/**
|
|
44
|
+
* Handle seek time change and recompute buffer queue
|
|
45
|
+
* Pure function - computes new queue when seek time changes
|
|
46
|
+
*/
|
|
47
|
+
export declare const handleSeekTimeChange: <T extends AudioRendition | VideoRendition>(newSeekTimeMs: number, bufferDurationMs: number, rendition: T, currentState: MediaBufferState, computeSegmentId: (timeMs: number, rendition: T) => number | undefined) => {
|
|
48
|
+
newQueue: number[];
|
|
49
|
+
overlappingRequests: number[];
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Check if a specific segment is cached in the buffer
|
|
53
|
+
* Pure function for accessing buffer cache state
|
|
54
|
+
*/
|
|
55
|
+
export declare const getCachedSegment: (segmentId: number, bufferState: MediaBufferState | undefined) => boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Get cached segments from a list of segment IDs
|
|
58
|
+
* Pure function that returns which segments are available in cache
|
|
59
|
+
*/
|
|
60
|
+
export declare const getCachedSegments: (segmentIds: number[], bufferState: MediaBufferState | undefined) => Set<number>;
|
|
61
|
+
/**
|
|
62
|
+
* Get missing segments from a list of segment IDs
|
|
63
|
+
* Pure function that returns which segments need to be fetched
|
|
64
|
+
*/
|
|
65
|
+
export declare const getMissingSegments: (segmentIds: number[], bufferState: MediaBufferState | undefined) => number[];
|
|
66
|
+
/**
|
|
67
|
+
* Core media buffering logic with explicit dependencies
|
|
68
|
+
* Generic implementation that works for both audio and video
|
|
69
|
+
*/
|
|
70
|
+
export declare const manageMediaBuffer: <T extends AudioRendition | VideoRendition>(seekTimeMs: number, config: MediaBufferConfig, currentState: MediaBufferState, durationMs: number, signal: AbortSignal, deps: MediaBufferDependencies<T>) => Promise<MediaBufferState>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async version of computeSegmentRange for when computeSegmentId is async
|
|
3
|
+
*/
|
|
4
|
+
const computeSegmentRangeAsync = async (startTimeMs, endTimeMs, durationMs, rendition, computeSegmentId) => {
|
|
5
|
+
const segments = [];
|
|
6
|
+
const segmentDurationMs = rendition.segmentDurationMs || 1e3;
|
|
7
|
+
const startSegmentIndex = Math.floor(startTimeMs / segmentDurationMs);
|
|
8
|
+
const endSegmentIndex = Math.floor(Math.min(endTimeMs, durationMs) / segmentDurationMs);
|
|
9
|
+
for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {
|
|
10
|
+
const timeMs = i * segmentDurationMs;
|
|
11
|
+
if (timeMs < durationMs) {
|
|
12
|
+
const segmentId = await computeSegmentId(timeMs, rendition);
|
|
13
|
+
if (segmentId !== void 0) segments.push(segmentId);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return segments.filter((id, index, arr) => arr.indexOf(id) === index);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Compute buffer queue based on current state and desired segments
|
|
20
|
+
* Pure function - determines what segments should be fetched
|
|
21
|
+
*/
|
|
22
|
+
const computeBufferQueue = (desiredSegments, activeRequests, cachedSegments) => {
|
|
23
|
+
return desiredSegments.filter((segmentId) => !activeRequests.has(segmentId) && !cachedSegments.has(segmentId));
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Core media buffering logic with explicit dependencies
|
|
27
|
+
* Generic implementation that works for both audio and video
|
|
28
|
+
*/
|
|
29
|
+
const manageMediaBuffer = async (seekTimeMs, config, currentState, durationMs, signal, deps) => {
|
|
30
|
+
if (!config.enableBuffering) return currentState;
|
|
31
|
+
const rendition = await deps.getRendition();
|
|
32
|
+
const endTimeMs = seekTimeMs + config.bufferDurationMs;
|
|
33
|
+
const desiredSegments = await computeSegmentRangeAsync(seekTimeMs, endTimeMs, durationMs, rendition, deps.computeSegmentId);
|
|
34
|
+
const newQueue = computeBufferQueue(desiredSegments, currentState.activeRequests, currentState.cachedSegments);
|
|
35
|
+
const segmentsToFetch = newQueue.slice(0, config.maxParallelFetches);
|
|
36
|
+
const newActiveRequests = new Set(currentState.activeRequests);
|
|
37
|
+
const newCachedSegments = new Set(currentState.cachedSegments);
|
|
38
|
+
const startNextSegment = (remainingQueue) => {
|
|
39
|
+
if (remainingQueue.length === 0 || signal.aborted) return;
|
|
40
|
+
const availableSlots = config.maxParallelFetches - newActiveRequests.size;
|
|
41
|
+
if (availableSlots <= 0) return;
|
|
42
|
+
const nextSegmentId = remainingQueue[0];
|
|
43
|
+
if (nextSegmentId === void 0) return;
|
|
44
|
+
if (newActiveRequests.has(nextSegmentId) || newCachedSegments.has(nextSegmentId)) {
|
|
45
|
+
startNextSegment(remainingQueue.slice(1));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
newActiveRequests.add(nextSegmentId);
|
|
49
|
+
deps.fetchSegment(nextSegmentId, rendition).then(() => {
|
|
50
|
+
if (signal.aborted) return;
|
|
51
|
+
newActiveRequests.delete(nextSegmentId);
|
|
52
|
+
newCachedSegments.add(nextSegmentId);
|
|
53
|
+
startNextSegment(remainingQueue.slice(1));
|
|
54
|
+
}).catch((error) => {
|
|
55
|
+
if (signal.aborted) return;
|
|
56
|
+
newActiveRequests.delete(nextSegmentId);
|
|
57
|
+
deps.logError(`Failed to fetch segment ${nextSegmentId}`, error);
|
|
58
|
+
startNextSegment(remainingQueue.slice(1));
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
for (const segmentId of segmentsToFetch) {
|
|
62
|
+
if (signal.aborted) break;
|
|
63
|
+
newActiveRequests.add(segmentId);
|
|
64
|
+
deps.fetchSegment(segmentId, rendition).then(() => {
|
|
65
|
+
if (signal.aborted) return;
|
|
66
|
+
newActiveRequests.delete(segmentId);
|
|
67
|
+
newCachedSegments.add(segmentId);
|
|
68
|
+
if (config.enableContinuousBuffering ?? true) {
|
|
69
|
+
const remainingQueue = newQueue.slice(segmentsToFetch.length);
|
|
70
|
+
startNextSegment(remainingQueue);
|
|
71
|
+
}
|
|
72
|
+
}).catch((error) => {
|
|
73
|
+
if (signal.aborted) return;
|
|
74
|
+
newActiveRequests.delete(segmentId);
|
|
75
|
+
deps.logError(`Failed to fetch segment ${segmentId}`, error);
|
|
76
|
+
if (config.enableContinuousBuffering ?? true) {
|
|
77
|
+
const remainingQueue = newQueue.slice(segmentsToFetch.length);
|
|
78
|
+
startNextSegment(remainingQueue);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
currentSeekTimeMs: seekTimeMs,
|
|
84
|
+
activeRequests: newActiveRequests,
|
|
85
|
+
cachedSegments: newCachedSegments,
|
|
86
|
+
requestQueue: newQueue.slice(segmentsToFetch.length)
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
export { manageMediaBuffer };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Task } from '@lit/task';
|
|
2
|
+
import { AudioRendition, MediaEngine, VideoRendition } from '../../../transcoding/types';
|
|
3
|
+
import { BufferedSeekingInput } from '../BufferedSeekingInput';
|
|
4
|
+
/**
|
|
5
|
+
* Generic rendition type that can be either audio or video
|
|
6
|
+
*/
|
|
7
|
+
export type MediaRendition = AudioRendition | VideoRendition;
|
|
8
|
+
/**
|
|
9
|
+
* Generic task type for init segment fetch
|
|
10
|
+
*/
|
|
11
|
+
export type InitSegmentFetchTask = Task<readonly [MediaEngine | undefined], ArrayBuffer>;
|
|
12
|
+
/**
|
|
13
|
+
* Generic task type for segment ID calculation
|
|
14
|
+
*/
|
|
15
|
+
export type SegmentIdTask = Task<readonly [MediaEngine | undefined, number], number | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* Generic task type for segment fetch
|
|
18
|
+
*/
|
|
19
|
+
export type SegmentFetchTask = Task<readonly [MediaEngine | undefined, number | undefined], ArrayBuffer>;
|
|
20
|
+
/**
|
|
21
|
+
* Generic task type for input creation
|
|
22
|
+
*/
|
|
23
|
+
export type InputTask = Task<readonly [ArrayBuffer, ArrayBuffer], BufferedSeekingInput>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AudioRendition, MediaEngine, VideoRendition } from '../../../transcoding/types';
|
|
2
|
+
/**
|
|
3
|
+
* Get audio rendition from media engine, throwing if not available
|
|
4
|
+
*/
|
|
5
|
+
export declare const getAudioRendition: (mediaEngine: MediaEngine) => AudioRendition;
|
|
6
|
+
/**
|
|
7
|
+
* Get video rendition from media engine, throwing if not available
|
|
8
|
+
*/
|
|
9
|
+
export declare const getVideoRendition: (mediaEngine: MediaEngine) => VideoRendition;
|
|
10
|
+
/**
|
|
11
|
+
* Calculate which segment contains a given timestamp
|
|
12
|
+
* Returns 1-based segment ID, or undefined if segmentDurationMs is not available
|
|
13
|
+
*/
|
|
14
|
+
export declare const computeSegmentId: (timeMs: number, rendition: AudioRendition | VideoRendition) => number | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Calculate range of segment IDs that overlap with a time range
|
|
17
|
+
* Returns array of 1-based segment IDs, or empty array if segmentDurationMs is not available
|
|
18
|
+
*/
|
|
19
|
+
export declare const calculateSegmentRange: (startTimeMs: number, endTimeMs: number, rendition: AudioRendition | VideoRendition) => number[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Task } from '@lit/task';
|
|
2
|
+
import { MediaEngine, VideoRendition } from '../../../transcoding/types';
|
|
3
|
+
import { EFMedia } from '../../EFMedia';
|
|
4
|
+
export declare const getLatestMediaEngine: (host: EFMedia, signal: AbortSignal) => Promise<MediaEngine>;
|
|
5
|
+
export declare const getVideoRendition: (mediaEngine: MediaEngine) => VideoRendition;
|
|
6
|
+
/**
|
|
7
|
+
* Core logic for creating a MediaEngine with explicit dependencies.
|
|
8
|
+
* Pure function that requires all dependencies to be provided.
|
|
9
|
+
*/
|
|
10
|
+
export declare const createMediaEngine: (host: EFMedia) => Promise<MediaEngine>;
|
|
11
|
+
/**
|
|
12
|
+
* Handle completion of media engine task - triggers necessary updates.
|
|
13
|
+
* Extracted for testability.
|
|
14
|
+
*/
|
|
15
|
+
export declare const handleMediaEngineComplete: (host: EFMedia) => void;
|
|
16
|
+
type MediaEngineTask = Task<readonly [string, string | null], MediaEngine>;
|
|
17
|
+
export declare const makeMediaEngineTask: (host: EFMedia) => MediaEngineTask;
|
|
18
|
+
export {};
|