@editframe/elements 0.17.6-beta.0 → 0.18.7-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EF_FRAMEGEN.js +1 -1
- package/dist/elements/EFAudio.d.ts +21 -2
- package/dist/elements/EFAudio.js +41 -11
- package/dist/elements/EFImage.d.ts +1 -0
- package/dist/elements/EFImage.js +11 -3
- package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +18 -0
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +41 -0
- package/dist/elements/EFMedia/AssetMediaEngine.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +45 -0
- package/dist/elements/EFMedia/AssetMediaEngine.js +135 -0
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +55 -0
- package/dist/elements/EFMedia/BaseMediaEngine.js +115 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +43 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.js +179 -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 +81 -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 +141 -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 +30 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +7 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +32 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +28 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +17 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +3 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +107 -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/PrecisionUtils.d.ts +28 -0
- package/dist/elements/EFMedia/shared/PrecisionUtils.js +29 -0
- package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +19 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +18 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +60 -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 +34 -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 +28 -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 +17 -0
- package/dist/elements/EFMedia.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia.d.ts +63 -111
- package/dist/elements/EFMedia.js +117 -1113
- package/dist/elements/EFTemporal.d.ts +1 -1
- package/dist/elements/EFTemporal.js +1 -1
- package/dist/elements/EFTimegroup.d.ts +11 -0
- package/dist/elements/EFTimegroup.js +83 -13
- package/dist/elements/EFVideo.d.ts +54 -32
- package/dist/elements/EFVideo.js +100 -207
- package/dist/elements/EFWaveform.js +2 -2
- package/dist/elements/SampleBuffer.d.ts +14 -0
- package/dist/elements/SampleBuffer.js +52 -0
- package/dist/getRenderInfo.js +2 -1
- package/dist/gui/ContextMixin.js +3 -2
- package/dist/gui/EFFilmstrip.d.ts +3 -3
- package/dist/gui/EFFilmstrip.js +1 -1
- package/dist/gui/EFFitScale.d.ts +2 -2
- package/dist/gui/TWMixin.js +1 -1
- package/dist/style.css +1 -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 +3 -3
- package/src/elements/EFAudio.browsertest.ts +189 -49
- package/src/elements/EFAudio.ts +59 -13
- package/src/elements/EFImage.browsertest.ts +42 -0
- 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.browsertest.ts +100 -0
- package/src/elements/EFMedia/AssetMediaEngine.ts +255 -0
- package/src/elements/EFMedia/BaseMediaEngine.test.ts +164 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +219 -0
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +481 -0
- package/src/elements/EFMedia/BufferedSeekingInput.ts +324 -0
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +165 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +166 -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 +250 -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 +43 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +199 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +64 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +45 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +24 -0
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +183 -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/PrecisionUtils.ts +46 -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 +68 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +57 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +43 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +56 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +24 -0
- package/src/elements/EFMedia.browsertest.ts +706 -273
- package/src/elements/EFMedia.ts +136 -1769
- package/src/elements/EFTemporal.ts +3 -4
- package/src/elements/EFTimegroup.browsertest.ts +6 -3
- package/src/elements/EFTimegroup.ts +147 -21
- package/src/elements/EFVideo.browsertest.ts +980 -169
- package/src/elements/EFVideo.ts +113 -458
- package/src/elements/EFWaveform.ts +1 -1
- package/src/elements/MediaController.ts +2 -12
- package/src/elements/SampleBuffer.ts +95 -0
- package/src/gui/ContextMixin.ts +3 -6
- 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 +269 -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 +39 -30
- 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_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/metadata.json +22 -0
- 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_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/metadata.json +22 -0
- 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_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/metadata.json +22 -0
- 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_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +21 -0
- 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 +320 -188
- package/test/recordReplayProxyPlugin.js +352 -0
- package/test/useAssetMSW.ts +1 -1
- package/test/useMSW.ts +35 -22
- package/types.json +1 -1
- package/dist/JitTranscodingClient.d.ts +0 -167
- package/dist/JitTranscodingClient.js +0 -373
- package/dist/ScrubTrackManager.d.ts +0 -96
- package/dist/ScrubTrackManager.js +0 -216
- package/dist/elements/printTaskStatus.js +0 -11
- package/src/elements/__screenshots__/EFMedia.browsertest.ts/EFMedia-JIT-audio-playback-audioBufferTask-should-work-in-JIT-mode-without-URL-errors-1.png +0 -0
- package/test/EFVideo.frame-tasks.browsertest.ts +0 -524
- /package/dist/{DecoderResetFrequency.test.d.ts → elements/EFMedia/AssetIdMediaEngine.test.d.ts} +0 -0
- /package/dist/{DecoderResetRecovery.test.d.ts → elements/EFMedia/BaseMediaEngine.test.d.ts} +0 -0
- /package/dist/{JitTranscodingClient.browsertest.d.ts → elements/EFMedia/BufferedSeekingInput.browsertest.d.ts} +0 -0
- /package/dist/{JitTranscodingClient.test.d.ts → elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts} +0 -0
- /package/dist/{ScrubTrackIntegration.test.d.ts → elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts} +0 -0
- /package/dist/{SegmentSwitchLoading.test.d.ts → elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts} +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { roundToMilliseconds } from "./shared/PrecisionUtils.js";
|
|
2
|
+
import { SampleBuffer } from "../SampleBuffer.js";
|
|
3
|
+
import { AudioSampleSink, BufferSource, Input, MP4, VideoSampleSink } from "mediabunny";
|
|
4
|
+
const defaultOptions = {
|
|
5
|
+
videoBufferSize: 30,
|
|
6
|
+
audioBufferSize: 100,
|
|
7
|
+
startTimeOffsetMs: 0
|
|
8
|
+
};
|
|
9
|
+
var NoSample = class extends RangeError {};
|
|
10
|
+
var BufferedSeekingInput = class {
|
|
11
|
+
constructor(arrayBuffer, options) {
|
|
12
|
+
this.trackIterators = /* @__PURE__ */ new Map();
|
|
13
|
+
this.trackBuffers = /* @__PURE__ */ new Map();
|
|
14
|
+
this.trackIteratorCreationPromises = /* @__PURE__ */ new Map();
|
|
15
|
+
this.trackSeekPromises = /* @__PURE__ */ new Map();
|
|
16
|
+
const bufferSource = new BufferSource(arrayBuffer);
|
|
17
|
+
const input = new Input({
|
|
18
|
+
source: bufferSource,
|
|
19
|
+
formats: [MP4]
|
|
20
|
+
});
|
|
21
|
+
this.input = input;
|
|
22
|
+
this.options = {
|
|
23
|
+
...defaultOptions,
|
|
24
|
+
...options
|
|
25
|
+
};
|
|
26
|
+
this.startTimeOffsetMs = this.options.startTimeOffsetMs ?? 0;
|
|
27
|
+
}
|
|
28
|
+
getBufferSize(trackId) {
|
|
29
|
+
const buffer = this.trackBuffers.get(trackId);
|
|
30
|
+
return buffer ? buffer.length : 0;
|
|
31
|
+
}
|
|
32
|
+
getBufferContents(trackId) {
|
|
33
|
+
const buffer = this.trackBuffers.get(trackId);
|
|
34
|
+
return buffer ? Object.freeze([...buffer.getContents()]) : [];
|
|
35
|
+
}
|
|
36
|
+
getBufferTimestamps(trackId) {
|
|
37
|
+
const contents = this.getBufferContents(trackId);
|
|
38
|
+
return contents.map((sample) => sample.timestamp || 0);
|
|
39
|
+
}
|
|
40
|
+
clearBuffer(trackId) {
|
|
41
|
+
const buffer = this.trackBuffers.get(trackId);
|
|
42
|
+
if (buffer) buffer.clear();
|
|
43
|
+
}
|
|
44
|
+
computeDuration() {
|
|
45
|
+
return this.input.computeDuration();
|
|
46
|
+
}
|
|
47
|
+
async getTrack(trackId) {
|
|
48
|
+
const tracks = await this.input.getTracks();
|
|
49
|
+
const track = tracks.find((track$1) => track$1.id === trackId);
|
|
50
|
+
if (!track) throw new Error(`Track ${trackId} not found`);
|
|
51
|
+
return track;
|
|
52
|
+
}
|
|
53
|
+
async getAudioTrack(trackId) {
|
|
54
|
+
const tracks = await this.input.getAudioTracks();
|
|
55
|
+
const track = tracks.find((track$1) => track$1.id === trackId && track$1.type === "audio");
|
|
56
|
+
if (!track) throw new Error(`Track ${trackId} not found`);
|
|
57
|
+
return track;
|
|
58
|
+
}
|
|
59
|
+
async getVideoTrack(trackId) {
|
|
60
|
+
const tracks = await this.input.getVideoTracks();
|
|
61
|
+
const track = tracks.find((track$1) => track$1.id === trackId && track$1.type === "video");
|
|
62
|
+
if (!track) throw new Error(`Track ${trackId} not found`);
|
|
63
|
+
return track;
|
|
64
|
+
}
|
|
65
|
+
async getFirstVideoTrack() {
|
|
66
|
+
const tracks = await this.input.getVideoTracks();
|
|
67
|
+
return tracks[0];
|
|
68
|
+
}
|
|
69
|
+
async getFirstAudioTrack() {
|
|
70
|
+
const tracks = await this.input.getAudioTracks();
|
|
71
|
+
return tracks[0];
|
|
72
|
+
}
|
|
73
|
+
async getTrackIterator(trackId) {
|
|
74
|
+
if (this.trackIterators.has(trackId)) return this.trackIterators.get(trackId);
|
|
75
|
+
const existingIteratorCreation = this.trackIteratorCreationPromises.get(trackId);
|
|
76
|
+
if (existingIteratorCreation) {
|
|
77
|
+
await existingIteratorCreation;
|
|
78
|
+
if (this.trackIterators.has(trackId)) return this.trackIterators.get(trackId);
|
|
79
|
+
}
|
|
80
|
+
const creationPromise = this.createIteratorSafe(trackId);
|
|
81
|
+
this.trackIteratorCreationPromises.set(trackId, creationPromise);
|
|
82
|
+
try {
|
|
83
|
+
const iterator = await creationPromise;
|
|
84
|
+
return iterator;
|
|
85
|
+
} finally {
|
|
86
|
+
this.trackIteratorCreationPromises.delete(trackId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async createIteratorSafe(trackId) {
|
|
90
|
+
const track = await this.getTrack(trackId);
|
|
91
|
+
if (track.type === "audio") {
|
|
92
|
+
const track$1 = await this.getAudioTrack(trackId);
|
|
93
|
+
const sampleSink = new AudioSampleSink(track$1);
|
|
94
|
+
const iterator = sampleSink.samples();
|
|
95
|
+
this.trackIterators.set(trackId, iterator);
|
|
96
|
+
return iterator;
|
|
97
|
+
}
|
|
98
|
+
{
|
|
99
|
+
const track$1 = await this.getVideoTrack(trackId);
|
|
100
|
+
const sampleSink = new VideoSampleSink(track$1);
|
|
101
|
+
const iterator = sampleSink.samples();
|
|
102
|
+
this.trackIterators.set(trackId, iterator);
|
|
103
|
+
return iterator;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async createTrackBuffer(trackId) {
|
|
107
|
+
const track = await this.getTrack(trackId);
|
|
108
|
+
if (track.type === "audio") {
|
|
109
|
+
const bufferSize = this.options.audioBufferSize;
|
|
110
|
+
this.trackBuffers.set(trackId, new SampleBuffer(bufferSize));
|
|
111
|
+
} else {
|
|
112
|
+
const bufferSize = this.options.videoBufferSize;
|
|
113
|
+
this.trackBuffers.set(trackId, new SampleBuffer(bufferSize));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async seek(trackId, timeMs) {
|
|
117
|
+
const mediaTimeMs = timeMs + this.startTimeOffsetMs;
|
|
118
|
+
const roundedMediaTimeMs = roundToMilliseconds(mediaTimeMs);
|
|
119
|
+
const existingSeek = this.trackSeekPromises.get(trackId);
|
|
120
|
+
if (existingSeek) await existingSeek;
|
|
121
|
+
const seekPromise = this.seekSafe(trackId, roundedMediaTimeMs);
|
|
122
|
+
this.trackSeekPromises.set(trackId, seekPromise);
|
|
123
|
+
try {
|
|
124
|
+
return await seekPromise;
|
|
125
|
+
} finally {
|
|
126
|
+
this.trackSeekPromises.delete(trackId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async resetIterator(trackId) {
|
|
130
|
+
const trackBuffer = this.trackBuffers.get(trackId);
|
|
131
|
+
trackBuffer?.clear();
|
|
132
|
+
const ongoingIteratorCreation = this.trackIteratorCreationPromises.get(trackId);
|
|
133
|
+
if (ongoingIteratorCreation) await ongoingIteratorCreation;
|
|
134
|
+
const iterator = this.trackIterators.get(trackId);
|
|
135
|
+
if (iterator) try {
|
|
136
|
+
await iterator.return?.();
|
|
137
|
+
} catch (_error) {}
|
|
138
|
+
this.trackIterators.delete(trackId);
|
|
139
|
+
}
|
|
140
|
+
async seekSafe(trackId, timeMs) {
|
|
141
|
+
if (!this.trackBuffers.has(trackId)) await this.createTrackBuffer(trackId);
|
|
142
|
+
const trackBuffer = this.trackBuffers.get(trackId);
|
|
143
|
+
const track = await this.getTrack(trackId);
|
|
144
|
+
const firstTimestampMs = roundToMilliseconds(await track.getFirstTimestamp() * 1e3);
|
|
145
|
+
let roundedTimeMs = roundToMilliseconds(timeMs);
|
|
146
|
+
if (roundedTimeMs < firstTimestampMs) {
|
|
147
|
+
const bufferContents$1 = trackBuffer.getContents();
|
|
148
|
+
if (bufferContents$1.length > 0) {
|
|
149
|
+
timeMs = firstTimestampMs;
|
|
150
|
+
roundedTimeMs = roundToMilliseconds(timeMs);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const bufferContents = trackBuffer.getContents();
|
|
154
|
+
if (bufferContents.length > 0) {
|
|
155
|
+
const bufferStartMs = roundToMilliseconds(trackBuffer.firstTimestamp * 1e3);
|
|
156
|
+
const lastSample = bufferContents[bufferContents.length - 1];
|
|
157
|
+
const bufferEndMs = lastSample ? roundToMilliseconds((lastSample.timestamp + (lastSample.duration || 0)) * 1e3) : bufferStartMs;
|
|
158
|
+
if (roundedTimeMs < bufferStartMs || roundedTimeMs > bufferEndMs) await this.resetIterator(trackId);
|
|
159
|
+
}
|
|
160
|
+
const alreadyInBuffer = trackBuffer.find(timeMs);
|
|
161
|
+
if (alreadyInBuffer) return alreadyInBuffer;
|
|
162
|
+
const iterator = await this.getTrackIterator(trackId);
|
|
163
|
+
while (true) {
|
|
164
|
+
const { done, value: decodedSample } = await iterator.next();
|
|
165
|
+
if (decodedSample) trackBuffer.push(decodedSample);
|
|
166
|
+
const foundSample = trackBuffer.find(timeMs);
|
|
167
|
+
if (foundSample) return foundSample;
|
|
168
|
+
if (done) break;
|
|
169
|
+
}
|
|
170
|
+
const finalBufferContents = trackBuffer.getContents();
|
|
171
|
+
if (finalBufferContents.length > 0) {
|
|
172
|
+
const lastSample = finalBufferContents[finalBufferContents.length - 1];
|
|
173
|
+
const lastSampleEndMs = roundToMilliseconds(((lastSample?.timestamp || 0) + (lastSample?.duration || 0)) * 1e3);
|
|
174
|
+
if (roundToMilliseconds(timeMs) >= lastSampleEndMs) return lastSample;
|
|
175
|
+
}
|
|
176
|
+
throw new NoSample(`Sample not found for time ${timeMs} in ${track.type} track ${trackId}`);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
export { BufferedSeekingInput };
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AudioRendition, MediaEngine, RenditionId, VideoRendition } from '../../transcoding/types';
|
|
2
|
+
import { ManifestResponse } from '../../transcoding/types/index.js';
|
|
3
|
+
import { UrlGenerator } from '../../transcoding/utils/UrlGenerator';
|
|
4
|
+
import { EFMedia } from '../EFMedia.js';
|
|
5
|
+
import { BaseMediaEngine } from './BaseMediaEngine';
|
|
6
|
+
export declare class JitMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
7
|
+
host: EFMedia;
|
|
8
|
+
private urlGenerator;
|
|
9
|
+
private data;
|
|
10
|
+
static fetch(host: EFMedia, urlGenerator: UrlGenerator, url: string): Promise<JitMediaEngine>;
|
|
11
|
+
constructor(host: EFMedia, urlGenerator: UrlGenerator, data: ManifestResponse);
|
|
12
|
+
get durationMs(): number;
|
|
13
|
+
get src(): string;
|
|
14
|
+
get audioRendition(): AudioRendition | undefined;
|
|
15
|
+
get videoRendition(): VideoRendition | undefined;
|
|
16
|
+
get templates(): {
|
|
17
|
+
initSegment: string;
|
|
18
|
+
mediaSegment: string;
|
|
19
|
+
};
|
|
20
|
+
fetchInitSegment(rendition: {
|
|
21
|
+
id?: RenditionId;
|
|
22
|
+
trackId: number | undefined;
|
|
23
|
+
src: string;
|
|
24
|
+
}, signal: AbortSignal): Promise<ArrayBuffer>;
|
|
25
|
+
fetchMediaSegmentImpl(segmentId: number, rendition: {
|
|
26
|
+
id?: RenditionId;
|
|
27
|
+
trackId: number | undefined;
|
|
28
|
+
src: string;
|
|
29
|
+
}): Promise<ArrayBuffer>;
|
|
30
|
+
computeSegmentId(desiredSeekTimeMs: number, rendition: VideoRendition | AudioRendition): number | undefined;
|
|
31
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { BaseMediaEngine } from "./BaseMediaEngine.js";
|
|
2
|
+
var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
|
|
3
|
+
static async fetch(host, urlGenerator, url) {
|
|
4
|
+
const response = await host.fetch(url);
|
|
5
|
+
const data = await response.json();
|
|
6
|
+
return new JitMediaEngine(host, urlGenerator, data);
|
|
7
|
+
}
|
|
8
|
+
constructor(host, urlGenerator, data) {
|
|
9
|
+
super();
|
|
10
|
+
this.host = host;
|
|
11
|
+
this.urlGenerator = urlGenerator;
|
|
12
|
+
this.data = data;
|
|
13
|
+
}
|
|
14
|
+
get durationMs() {
|
|
15
|
+
return this.data.durationMs;
|
|
16
|
+
}
|
|
17
|
+
get src() {
|
|
18
|
+
return this.data.sourceUrl;
|
|
19
|
+
}
|
|
20
|
+
get audioRendition() {
|
|
21
|
+
const rendition = this.data.audioRenditions[0];
|
|
22
|
+
if (!rendition) return void 0;
|
|
23
|
+
return {
|
|
24
|
+
id: rendition.id,
|
|
25
|
+
trackId: void 0,
|
|
26
|
+
src: this.data.sourceUrl,
|
|
27
|
+
segmentDurationMs: rendition.segmentDurationMs,
|
|
28
|
+
segmentDurationsMs: rendition.segmentDurationsMs
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
get videoRendition() {
|
|
32
|
+
const rendition = this.data.videoRenditions[0];
|
|
33
|
+
if (!rendition) return void 0;
|
|
34
|
+
return {
|
|
35
|
+
id: rendition.id,
|
|
36
|
+
trackId: void 0,
|
|
37
|
+
src: this.data.sourceUrl,
|
|
38
|
+
segmentDurationMs: rendition.segmentDurationMs,
|
|
39
|
+
segmentDurationsMs: rendition.segmentDurationsMs
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
get templates() {
|
|
43
|
+
return this.data.endpoints;
|
|
44
|
+
}
|
|
45
|
+
async fetchInitSegment(rendition, signal) {
|
|
46
|
+
if (!rendition.id) throw new Error("Rendition ID is required for JIT metadata");
|
|
47
|
+
const url = this.urlGenerator.generateSegmentUrl("init", rendition.id, this);
|
|
48
|
+
const response = await this.host.fetch(url, { signal });
|
|
49
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
50
|
+
return arrayBuffer;
|
|
51
|
+
}
|
|
52
|
+
async fetchMediaSegmentImpl(segmentId, rendition) {
|
|
53
|
+
if (!rendition.id) throw new Error("Rendition ID is required for JIT metadata");
|
|
54
|
+
const url = this.urlGenerator.generateSegmentUrl(segmentId, rendition.id, this);
|
|
55
|
+
return this.fetchMediaCache(url);
|
|
56
|
+
}
|
|
57
|
+
computeSegmentId(desiredSeekTimeMs, rendition) {
|
|
58
|
+
if (desiredSeekTimeMs > this.durationMs) return void 0;
|
|
59
|
+
if (rendition.segmentDurationsMs && rendition.segmentDurationsMs.length > 0) {
|
|
60
|
+
let cumulativeTime = 0;
|
|
61
|
+
for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {
|
|
62
|
+
const segmentDuration = rendition.segmentDurationsMs[i];
|
|
63
|
+
if (segmentDuration === void 0) throw new Error("Segment duration is required for JIT metadata");
|
|
64
|
+
const segmentStartMs$1 = cumulativeTime;
|
|
65
|
+
const segmentEndMs = cumulativeTime + segmentDuration;
|
|
66
|
+
const isLastSegment = i === rendition.segmentDurationsMs.length - 1;
|
|
67
|
+
const includesEndTime = isLastSegment && desiredSeekTimeMs === this.durationMs;
|
|
68
|
+
if (desiredSeekTimeMs >= segmentStartMs$1 && (desiredSeekTimeMs < segmentEndMs || includesEndTime)) return i + 1;
|
|
69
|
+
cumulativeTime += segmentDuration;
|
|
70
|
+
if (cumulativeTime >= this.durationMs) break;
|
|
71
|
+
}
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
if (!rendition.segmentDurationMs) throw new Error("Segment duration is required for JIT metadata");
|
|
75
|
+
const segmentIndex = Math.floor(desiredSeekTimeMs / rendition.segmentDurationMs);
|
|
76
|
+
const segmentStartMs = segmentIndex * rendition.segmentDurationMs;
|
|
77
|
+
if (segmentStartMs >= this.durationMs) return void 0;
|
|
78
|
+
return segmentIndex + 1;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export { JitMediaEngine };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Task } from '@lit/task';
|
|
2
|
+
import { EFMedia } from '../../EFMedia';
|
|
3
|
+
import { MediaBufferConfig, MediaBufferState } from '../shared/BufferUtils';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for audio buffering - extends the generic interface
|
|
6
|
+
*/
|
|
7
|
+
export interface AudioBufferConfig extends MediaBufferConfig {
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* State of the audio buffer - uses the generic interface
|
|
11
|
+
*/
|
|
12
|
+
export interface AudioBufferState extends MediaBufferState {
|
|
13
|
+
}
|
|
14
|
+
type AudioBufferTask = Task<readonly [number], AudioBufferState>;
|
|
15
|
+
export declare const makeAudioBufferTask: (host: EFMedia) => AudioBufferTask;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
|
|
2
|
+
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
3
|
+
import { manageMediaBuffer } from "../shared/BufferUtils.js";
|
|
4
|
+
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
5
|
+
import { Task } from "@lit/task";
|
|
6
|
+
const makeAudioBufferTask = (host) => {
|
|
7
|
+
let currentState = {
|
|
8
|
+
currentSeekTimeMs: 0,
|
|
9
|
+
activeRequests: /* @__PURE__ */ new Set(),
|
|
10
|
+
cachedSegments: /* @__PURE__ */ new Set(),
|
|
11
|
+
requestQueue: []
|
|
12
|
+
};
|
|
13
|
+
return new Task(host, {
|
|
14
|
+
autoRun: EF_INTERACTIVE,
|
|
15
|
+
args: () => [host.desiredSeekTimeMs],
|
|
16
|
+
onError: (error) => {
|
|
17
|
+
console.error("audioBufferTask error", error);
|
|
18
|
+
},
|
|
19
|
+
onComplete: (value) => {
|
|
20
|
+
currentState = value;
|
|
21
|
+
},
|
|
22
|
+
task: async ([seekTimeMs], { signal }) => {
|
|
23
|
+
const currentConfig = {
|
|
24
|
+
bufferDurationMs: host.audioBufferDurationMs,
|
|
25
|
+
maxParallelFetches: host.maxAudioBufferFetches,
|
|
26
|
+
enableBuffering: host.enableAudioBuffering && !EF_RENDERING
|
|
27
|
+
};
|
|
28
|
+
return manageMediaBuffer(seekTimeMs, currentConfig, currentState, host.intrinsicDurationMs || 1e4, signal, {
|
|
29
|
+
computeSegmentId: async (timeMs, rendition) => {
|
|
30
|
+
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
31
|
+
return mediaEngine.computeSegmentId(timeMs, rendition);
|
|
32
|
+
},
|
|
33
|
+
fetchSegment: async (segmentId, rendition) => {
|
|
34
|
+
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
35
|
+
return mediaEngine.fetchMediaSegment(segmentId, rendition);
|
|
36
|
+
},
|
|
37
|
+
getRendition: async () => {
|
|
38
|
+
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
39
|
+
const audioRendition = mediaEngine.audioRendition;
|
|
40
|
+
if (!audioRendition) throw new Error("Audio rendition not available");
|
|
41
|
+
return audioRendition;
|
|
42
|
+
},
|
|
43
|
+
logError: console.error
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
export { makeAudioBufferTask };
|
|
@@ -0,0 +1,141 @@
|
|
|
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 processFFTData(fftData, zeroThresholdPercent = .1) {
|
|
6
|
+
const totalBins = fftData.length;
|
|
7
|
+
const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
|
|
8
|
+
let zeroCount = 0;
|
|
9
|
+
let cutoffIndex = totalBins;
|
|
10
|
+
for (let i = totalBins - 1; i >= 0; i--) if (fftData[i] ?? true) zeroCount++;
|
|
11
|
+
else if (zeroCount >= zeroThresholdCount) {
|
|
12
|
+
cutoffIndex = i + 1;
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
if (cutoffIndex < zeroThresholdCount) return fftData;
|
|
16
|
+
const goodData = fftData.slice(0, cutoffIndex);
|
|
17
|
+
const resampledData = interpolateData(goodData, fftData.length);
|
|
18
|
+
const attenuationStartIndex = Math.floor(totalBins * .9);
|
|
19
|
+
for (let i = attenuationStartIndex; i < totalBins; i++) {
|
|
20
|
+
const attenuationProgress = (i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + .2;
|
|
21
|
+
const attenuationFactor = Math.max(0, 1 - attenuationProgress);
|
|
22
|
+
resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);
|
|
23
|
+
}
|
|
24
|
+
return resampledData;
|
|
25
|
+
}
|
|
26
|
+
function interpolateData(data, targetSize) {
|
|
27
|
+
const resampled = new Uint8Array(targetSize);
|
|
28
|
+
const dataLength = data.length;
|
|
29
|
+
for (let i = 0; i < targetSize; i++) {
|
|
30
|
+
const ratio = i / (targetSize - 1) * (dataLength - 1);
|
|
31
|
+
const index = Math.floor(ratio);
|
|
32
|
+
const fraction = ratio - index;
|
|
33
|
+
if (index >= dataLength - 1) resampled[i] = data[dataLength - 1] ?? 0;
|
|
34
|
+
else resampled[i] = Math.round((data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction);
|
|
35
|
+
}
|
|
36
|
+
return resampled;
|
|
37
|
+
}
|
|
38
|
+
function makeAudioFrequencyAnalysisTask(element) {
|
|
39
|
+
const cache = new LRUCache(100);
|
|
40
|
+
return new Task(element, {
|
|
41
|
+
autoRun: EF_INTERACTIVE,
|
|
42
|
+
onError: (error) => {
|
|
43
|
+
console.error("frequencyDataTask error", error);
|
|
44
|
+
},
|
|
45
|
+
args: () => [
|
|
46
|
+
element.audioBufferTask.status,
|
|
47
|
+
element.currentSourceTimeMs,
|
|
48
|
+
element.fftSize,
|
|
49
|
+
element.fftDecay,
|
|
50
|
+
element.fftGain,
|
|
51
|
+
element.shouldInterpolateFrequencies
|
|
52
|
+
],
|
|
53
|
+
task: async () => {
|
|
54
|
+
await element.audioBufferTask.taskComplete;
|
|
55
|
+
if (!element.audioBufferTask.value) return null;
|
|
56
|
+
if (element.currentSourceTimeMs < 0) return null;
|
|
57
|
+
const currentTimeMs = element.currentSourceTimeMs;
|
|
58
|
+
const analysisWindowMs = 5e3;
|
|
59
|
+
const fromMs = Math.max(0, currentTimeMs);
|
|
60
|
+
const maxToMs = fromMs + analysisWindowMs;
|
|
61
|
+
const videoDurationMs = element.intrinsicDurationMs || 0;
|
|
62
|
+
const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;
|
|
63
|
+
if (fromMs >= toMs) return null;
|
|
64
|
+
const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
|
|
65
|
+
const audioSpan = await fetchAudioSpan(element, fromMs, toMs, new AbortController().signal);
|
|
66
|
+
if (!audioSpan || !audioSpan.blob) {
|
|
67
|
+
console.warn("Frequency analysis skipped: no audio data available");
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const tempAudioContext = new OfflineAudioContext(2, 48e3, 48e3);
|
|
71
|
+
const arrayBuffer = await audioSpan.blob.arrayBuffer();
|
|
72
|
+
const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
|
|
73
|
+
const startOffsetMs = audioSpan.startMs;
|
|
74
|
+
const smoothedKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${startOffsetMs}:${currentTimeMs}`;
|
|
75
|
+
const cachedSmoothedData = cache.get(smoothedKey);
|
|
76
|
+
if (cachedSmoothedData) return cachedSmoothedData;
|
|
77
|
+
const framesData = await Promise.all(Array.from({ length: element.fftDecay }, async (_, i) => {
|
|
78
|
+
const frameOffset = i * (1e3 / 30);
|
|
79
|
+
const startTime = Math.max(0, (currentTimeMs - frameOffset - startOffsetMs) / 1e3);
|
|
80
|
+
const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
|
|
81
|
+
const cachedFrame = cache.get(cacheKey);
|
|
82
|
+
if (cachedFrame) return cachedFrame;
|
|
83
|
+
const SIZE = 48e3 / 30;
|
|
84
|
+
let audioContext;
|
|
85
|
+
try {
|
|
86
|
+
audioContext = new OfflineAudioContext(2, SIZE, 48e3);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw new Error(`[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.`);
|
|
89
|
+
}
|
|
90
|
+
const analyser = audioContext.createAnalyser();
|
|
91
|
+
analyser.fftSize = element.fftSize;
|
|
92
|
+
analyser.minDecibels = -90;
|
|
93
|
+
analyser.maxDecibels = -10;
|
|
94
|
+
const gainNode = audioContext.createGain();
|
|
95
|
+
gainNode.gain.value = element.fftGain;
|
|
96
|
+
const filter = audioContext.createBiquadFilter();
|
|
97
|
+
filter.type = "bandpass";
|
|
98
|
+
filter.frequency.value = 15e3;
|
|
99
|
+
filter.Q.value = .05;
|
|
100
|
+
const audioBufferSource = audioContext.createBufferSource();
|
|
101
|
+
audioBufferSource.buffer = audioBuffer;
|
|
102
|
+
audioBufferSource.connect(filter);
|
|
103
|
+
filter.connect(gainNode);
|
|
104
|
+
gainNode.connect(analyser);
|
|
105
|
+
analyser.connect(audioContext.destination);
|
|
106
|
+
audioBufferSource.start(0, startTime, 1 / 30);
|
|
107
|
+
try {
|
|
108
|
+
await audioContext.startRendering();
|
|
109
|
+
const frameData = new Uint8Array(element.fftSize / 2);
|
|
110
|
+
analyser.getByteFrequencyData(frameData);
|
|
111
|
+
cache.set(cacheKey, frameData);
|
|
112
|
+
return frameData;
|
|
113
|
+
} finally {
|
|
114
|
+
audioBufferSource.disconnect();
|
|
115
|
+
analyser.disconnect();
|
|
116
|
+
}
|
|
117
|
+
}));
|
|
118
|
+
const frameLength = framesData[0]?.length ?? 0;
|
|
119
|
+
const smoothedData = new Uint8Array(frameLength);
|
|
120
|
+
for (let i = 0; i < frameLength; i++) {
|
|
121
|
+
let weightedSum = 0;
|
|
122
|
+
let weightSum = 0;
|
|
123
|
+
framesData.forEach((frame, frameIndex) => {
|
|
124
|
+
const decayWeight = DECAY_WEIGHT ** frameIndex;
|
|
125
|
+
weightedSum += (frame[i] ?? 0) * decayWeight;
|
|
126
|
+
weightSum += decayWeight;
|
|
127
|
+
});
|
|
128
|
+
smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
|
|
129
|
+
}
|
|
130
|
+
smoothedData.forEach((value, i) => {
|
|
131
|
+
const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;
|
|
132
|
+
smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
|
|
133
|
+
});
|
|
134
|
+
const slicedData = smoothedData.slice(0, Math.floor(smoothedData.length / 2));
|
|
135
|
+
const processedData = element.shouldInterpolateFrequencies ? processFFTData(slicedData) : slicedData;
|
|
136
|
+
cache.set(smoothedKey, processedData);
|
|
137
|
+
return processedData;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
export { makeAudioFrequencyAnalysisTask };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
2
|
+
import { Task } from "@lit/task";
|
|
3
|
+
const makeAudioInitSegmentFetchTask = (host) => {
|
|
4
|
+
return new Task(host, {
|
|
5
|
+
args: () => [host.mediaEngineTask.value],
|
|
6
|
+
onError: (error) => {
|
|
7
|
+
console.error("audioInitSegmentFetchTask error", error);
|
|
8
|
+
},
|
|
9
|
+
onComplete: (_value) => {},
|
|
10
|
+
task: async ([_mediaEngine], { signal }) => {
|
|
11
|
+
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
|
+
return mediaEngine.fetchInitSegment(mediaEngine.getAudioRendition(), signal);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
export { makeAudioInitSegmentFetchTask };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BufferedSeekingInput } from "../BufferedSeekingInput.js";
|
|
2
|
+
import { EFMedia } from "../../EFMedia.js";
|
|
3
|
+
import { Task } from "@lit/task";
|
|
4
|
+
const makeAudioInputTask = (host) => {
|
|
5
|
+
return new Task(host, {
|
|
6
|
+
args: () => [host.audioInitSegmentFetchTask.value, host.audioSegmentFetchTask.value],
|
|
7
|
+
onError: (error) => {
|
|
8
|
+
console.error("audioInputTask error", error);
|
|
9
|
+
},
|
|
10
|
+
onComplete: (_value) => {},
|
|
11
|
+
task: async (_, { signal }) => {
|
|
12
|
+
const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
|
|
13
|
+
signal.throwIfAborted();
|
|
14
|
+
const segment = await host.audioSegmentFetchTask.taskComplete;
|
|
15
|
+
signal.throwIfAborted();
|
|
16
|
+
if (!initSegment || !segment) throw new Error("Init segment or segment is not available");
|
|
17
|
+
const mediaEngine = await host.mediaEngineTask.taskComplete;
|
|
18
|
+
const audioRendition = mediaEngine?.audioRendition;
|
|
19
|
+
const startTimeOffsetMs = audioRendition?.startTimeOffsetMs;
|
|
20
|
+
const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
|
|
21
|
+
signal.throwIfAborted();
|
|
22
|
+
return new BufferedSeekingInput(arrayBuffer, {
|
|
23
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
24
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
25
|
+
startTimeOffsetMs
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
export { makeAudioInputTask };
|
package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts
ADDED
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Task } from '@lit/task';
|
|
2
|
+
import { VideoSample } from 'mediabunny';
|
|
3
|
+
import { EFMedia } from '../../EFMedia';
|
|
4
|
+
import { BufferedSeekingInput } from '../BufferedSeekingInput';
|
|
5
|
+
type AudioSeekTask = Task<readonly [number, BufferedSeekingInput | undefined], VideoSample | undefined>;
|
|
6
|
+
export declare const makeAudioSeekTask: (host: EFMedia) => AudioSeekTask;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { IgnorableError } from "../../EFMedia.js";
|
|
2
|
+
import { Task } from "@lit/task";
|
|
3
|
+
const makeAudioSeekTask = (host) => {
|
|
4
|
+
return new Task(host, {
|
|
5
|
+
args: () => [host.desiredSeekTimeMs, host.audioInputTask.value],
|
|
6
|
+
onError: (error) => {
|
|
7
|
+
if (error instanceof IgnorableError) console.info("audioSeekTask aborted");
|
|
8
|
+
console.error("audioSeekTask error", error);
|
|
9
|
+
},
|
|
10
|
+
onComplete: (_value) => {},
|
|
11
|
+
task: async ([targetSeekTimeMs], { signal }) => {
|
|
12
|
+
await host.audioSegmentIdTask.taskComplete;
|
|
13
|
+
signal.throwIfAborted();
|
|
14
|
+
await host.audioSegmentFetchTask.taskComplete;
|
|
15
|
+
signal.throwIfAborted();
|
|
16
|
+
await host.audioInitSegmentFetchTask.taskComplete;
|
|
17
|
+
signal.throwIfAborted();
|
|
18
|
+
const audioInput = await host.audioInputTask.taskComplete;
|
|
19
|
+
signal.throwIfAborted();
|
|
20
|
+
if (!audioInput) throw new Error("Audio input is not available");
|
|
21
|
+
const audioTrack = await audioInput.getFirstAudioTrack();
|
|
22
|
+
if (!audioTrack) throw new Error("Audio track is not available");
|
|
23
|
+
signal.throwIfAborted();
|
|
24
|
+
const sample = await audioInput.seek(audioTrack.id, targetSeekTimeMs);
|
|
25
|
+
signal.throwIfAborted();
|
|
26
|
+
if (sample === void 0 && signal.aborted) return void 0;
|
|
27
|
+
if (sample === void 0) throw new Error("Audio seek failed to find sample");
|
|
28
|
+
return sample;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
export { makeAudioSeekTask };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Task } from '@lit/task';
|
|
2
|
+
import { MediaEngine } from '../../../transcoding/types';
|
|
3
|
+
import { EFMedia } from '../../EFMedia';
|
|
4
|
+
export declare const makeAudioSegmentFetchTask: (host: EFMedia) => Task<readonly [MediaEngine | undefined, number | undefined], ArrayBuffer>;
|