@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,250 @@
|
|
|
1
|
+
import { Task } from "@lit/task";
|
|
2
|
+
import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
|
|
3
|
+
import { LRUCache } from "../../../utils/LRUCache.js";
|
|
4
|
+
import type { EFMedia } from "../../EFMedia.js";
|
|
5
|
+
|
|
6
|
+
// DECAY_WEIGHT constant - same as original
|
|
7
|
+
const DECAY_WEIGHT = 0.8;
|
|
8
|
+
|
|
9
|
+
function processFFTData(
|
|
10
|
+
fftData: Uint8Array,
|
|
11
|
+
zeroThresholdPercent = 0.1,
|
|
12
|
+
): Uint8Array {
|
|
13
|
+
// Step 1: Determine the threshold for zeros
|
|
14
|
+
const totalBins = fftData.length;
|
|
15
|
+
const zeroThresholdCount = Math.floor(totalBins * zeroThresholdPercent);
|
|
16
|
+
|
|
17
|
+
// Step 2: Interrogate the FFT output to find the cutoff point
|
|
18
|
+
let zeroCount = 0;
|
|
19
|
+
let cutoffIndex = totalBins; // Default to the end of the array
|
|
20
|
+
|
|
21
|
+
for (let i = totalBins - 1; i >= 0; i--) {
|
|
22
|
+
if (fftData[i] ?? 0 < 10) {
|
|
23
|
+
zeroCount++;
|
|
24
|
+
} else {
|
|
25
|
+
// If we encounter a non-zero value, we can stop
|
|
26
|
+
if (zeroCount >= zeroThresholdCount) {
|
|
27
|
+
cutoffIndex = i + 1; // Include this index
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (cutoffIndex < zeroThresholdCount) {
|
|
34
|
+
return fftData;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Step 3: Resample the "good" portion of the data
|
|
38
|
+
const goodData = fftData.slice(0, cutoffIndex);
|
|
39
|
+
const resampledData = interpolateData(goodData, fftData.length);
|
|
40
|
+
|
|
41
|
+
// Step 4: Attenuate the top 10% of interpolated samples
|
|
42
|
+
const attenuationStartIndex = Math.floor(totalBins * 0.9);
|
|
43
|
+
for (let i = attenuationStartIndex; i < totalBins; i++) {
|
|
44
|
+
// Calculate attenuation factor that goes from 1 to 0 over the top 10%
|
|
45
|
+
const attenuationProgress =
|
|
46
|
+
(i - attenuationStartIndex) / (totalBins - attenuationStartIndex) + 0.2;
|
|
47
|
+
const attenuationFactor = Math.max(0, 1 - attenuationProgress);
|
|
48
|
+
resampledData[i] = Math.floor((resampledData[i] ?? 0) * attenuationFactor);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return resampledData;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function interpolateData(data: Uint8Array, targetSize: number): Uint8Array {
|
|
55
|
+
const resampled = new Uint8Array(targetSize);
|
|
56
|
+
const dataLength = data.length;
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < targetSize; i++) {
|
|
59
|
+
// Calculate the corresponding index in the original data
|
|
60
|
+
const ratio = (i / (targetSize - 1)) * (dataLength - 1);
|
|
61
|
+
const index = Math.floor(ratio);
|
|
62
|
+
const fraction = ratio - index;
|
|
63
|
+
|
|
64
|
+
// Handle edge cases
|
|
65
|
+
if (index >= dataLength - 1) {
|
|
66
|
+
resampled[i] = data[dataLength - 1] ?? 0; // Last value
|
|
67
|
+
} else {
|
|
68
|
+
// Linear interpolation
|
|
69
|
+
resampled[i] = Math.round(
|
|
70
|
+
(data[index] ?? 0) * (1 - fraction) + (data[index + 1] ?? 0) * fraction,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return resampled;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function makeAudioFrequencyAnalysisTask(element: EFMedia) {
|
|
79
|
+
// Internal cache for this task instance (same as original #frequencyDataCache)
|
|
80
|
+
const cache = new LRUCache<string, Uint8Array>(100);
|
|
81
|
+
|
|
82
|
+
return new Task(element, {
|
|
83
|
+
autoRun: EF_INTERACTIVE,
|
|
84
|
+
onError: (error) => {
|
|
85
|
+
console.error("frequencyDataTask error", error);
|
|
86
|
+
},
|
|
87
|
+
args: () =>
|
|
88
|
+
[
|
|
89
|
+
element.audioBufferTask.status,
|
|
90
|
+
element.currentSourceTimeMs,
|
|
91
|
+
element.fftSize,
|
|
92
|
+
element.fftDecay,
|
|
93
|
+
element.fftGain,
|
|
94
|
+
element.shouldInterpolateFrequencies,
|
|
95
|
+
] as const,
|
|
96
|
+
task: async () => {
|
|
97
|
+
await element.audioBufferTask.taskComplete;
|
|
98
|
+
if (!element.audioBufferTask.value) return null;
|
|
99
|
+
if (element.currentSourceTimeMs < 0) return null;
|
|
100
|
+
|
|
101
|
+
const currentTimeMs = element.currentSourceTimeMs;
|
|
102
|
+
|
|
103
|
+
// ONLY CHANGE: Get real audio data for analysis (same technique as playback)
|
|
104
|
+
const analysisWindowMs = 5000; // Get 5 seconds for better analysis
|
|
105
|
+
const fromMs = Math.max(0, currentTimeMs);
|
|
106
|
+
// Clamp toMs to video duration to prevent requesting segments beyond available content
|
|
107
|
+
const maxToMs = fromMs + analysisWindowMs;
|
|
108
|
+
const videoDurationMs = element.intrinsicDurationMs || 0;
|
|
109
|
+
const toMs =
|
|
110
|
+
videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;
|
|
111
|
+
|
|
112
|
+
// If the clamping results in an invalid range (seeking beyond the end), skip analysis silently
|
|
113
|
+
if (fromMs >= toMs) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { fetchAudioSpanningTime: fetchAudioSpan } = await import(
|
|
118
|
+
"../shared/AudioSpanUtils.ts"
|
|
119
|
+
);
|
|
120
|
+
const audioSpan = await fetchAudioSpan(
|
|
121
|
+
element,
|
|
122
|
+
fromMs,
|
|
123
|
+
toMs,
|
|
124
|
+
new AbortController().signal,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (!audioSpan || !audioSpan.blob) {
|
|
128
|
+
console.warn("Frequency analysis skipped: no audio data available");
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Decode the real audio data
|
|
133
|
+
const tempAudioContext = new OfflineAudioContext(2, 48000, 48000);
|
|
134
|
+
const arrayBuffer = await audioSpan.blob.arrayBuffer();
|
|
135
|
+
const audioBuffer = await tempAudioContext.decodeAudioData(arrayBuffer);
|
|
136
|
+
|
|
137
|
+
// Use actual startOffset from audioSpan (relative to requested time)
|
|
138
|
+
const startOffsetMs = audioSpan.startMs;
|
|
139
|
+
|
|
140
|
+
// ORIGINAL ALGORITHM FROM HERE - unchanged customer logic
|
|
141
|
+
const smoothedKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftDecay}:${element.fftGain}:${startOffsetMs}:${currentTimeMs}`;
|
|
142
|
+
|
|
143
|
+
const cachedSmoothedData = cache.get(smoothedKey);
|
|
144
|
+
if (cachedSmoothedData) {
|
|
145
|
+
return cachedSmoothedData;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const framesData = await Promise.all(
|
|
149
|
+
Array.from({ length: element.fftDecay }, async (_, i) => {
|
|
150
|
+
const frameOffset = i * (1000 / 30);
|
|
151
|
+
const startTime = Math.max(
|
|
152
|
+
0,
|
|
153
|
+
(currentTimeMs - frameOffset - startOffsetMs) / 1000,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Cache key for this specific frame
|
|
157
|
+
const cacheKey = `${element.shouldInterpolateFrequencies}:${element.fftSize}:${element.fftGain}:${startOffsetMs}:${startTime}`;
|
|
158
|
+
|
|
159
|
+
// Check cache for this specific frame
|
|
160
|
+
const cachedFrame = cache.get(cacheKey);
|
|
161
|
+
if (cachedFrame) {
|
|
162
|
+
return cachedFrame;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Running 48000 * (1 / 30) = 1600 broke something terrible, it came out as 0,
|
|
166
|
+
// I'm assuming weird floating point nonsense to do with running on rosetta
|
|
167
|
+
const SIZE = 48000 / 30;
|
|
168
|
+
let audioContext: OfflineAudioContext;
|
|
169
|
+
try {
|
|
170
|
+
audioContext = new OfflineAudioContext(2, SIZE, 48000);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`[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.`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const analyser = audioContext.createAnalyser();
|
|
177
|
+
analyser.fftSize = element.fftSize;
|
|
178
|
+
analyser.minDecibels = -90;
|
|
179
|
+
analyser.maxDecibels = -10;
|
|
180
|
+
|
|
181
|
+
const gainNode = audioContext.createGain();
|
|
182
|
+
gainNode.gain.value = element.fftGain;
|
|
183
|
+
|
|
184
|
+
const filter = audioContext.createBiquadFilter();
|
|
185
|
+
filter.type = "bandpass";
|
|
186
|
+
filter.frequency.value = 15000;
|
|
187
|
+
filter.Q.value = 0.05;
|
|
188
|
+
|
|
189
|
+
const audioBufferSource = audioContext.createBufferSource();
|
|
190
|
+
audioBufferSource.buffer = audioBuffer;
|
|
191
|
+
|
|
192
|
+
audioBufferSource.connect(filter);
|
|
193
|
+
filter.connect(gainNode);
|
|
194
|
+
gainNode.connect(analyser);
|
|
195
|
+
analyser.connect(audioContext.destination);
|
|
196
|
+
|
|
197
|
+
audioBufferSource.start(0, startTime, 1 / 30);
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
await audioContext.startRendering();
|
|
201
|
+
const frameData = new Uint8Array(element.fftSize / 2);
|
|
202
|
+
analyser.getByteFrequencyData(frameData);
|
|
203
|
+
|
|
204
|
+
// Cache this frame's analysis
|
|
205
|
+
cache.set(cacheKey, frameData);
|
|
206
|
+
return frameData;
|
|
207
|
+
} finally {
|
|
208
|
+
audioBufferSource.disconnect();
|
|
209
|
+
analyser.disconnect();
|
|
210
|
+
}
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const frameLength = framesData[0]?.length ?? 0;
|
|
215
|
+
|
|
216
|
+
// Combine frames with decay
|
|
217
|
+
const smoothedData = new Uint8Array(frameLength);
|
|
218
|
+
for (let i = 0; i < frameLength; i++) {
|
|
219
|
+
let weightedSum = 0;
|
|
220
|
+
let weightSum = 0;
|
|
221
|
+
|
|
222
|
+
framesData.forEach((frame: Uint8Array, frameIndex: number) => {
|
|
223
|
+
const decayWeight = DECAY_WEIGHT ** frameIndex;
|
|
224
|
+
weightedSum += (frame[i] ?? 0) * decayWeight;
|
|
225
|
+
weightSum += decayWeight;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
smoothedData[i] = Math.min(255, Math.round(weightedSum / weightSum));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Apply frequency weights using instance FREQ_WEIGHTS
|
|
232
|
+
smoothedData.forEach((value, i) => {
|
|
233
|
+
const freqWeight = element.FREQ_WEIGHTS[i] ?? 0;
|
|
234
|
+
smoothedData[i] = Math.min(255, Math.round(value * freqWeight));
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Only return the lower half of the frequency data
|
|
238
|
+
// The top half is zeroed out, which makes for aesthetically unpleasing waveforms
|
|
239
|
+
const slicedData = smoothedData.slice(
|
|
240
|
+
0,
|
|
241
|
+
Math.floor(smoothedData.length / 2),
|
|
242
|
+
);
|
|
243
|
+
const processedData = element.shouldInterpolateFrequencies
|
|
244
|
+
? processFFTData(slicedData)
|
|
245
|
+
: slicedData;
|
|
246
|
+
cache.set(smoothedKey, processedData);
|
|
247
|
+
return processedData;
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { TaskStatus } from "@lit/task";
|
|
2
|
+
import { customElement } from "lit/decorators.js";
|
|
3
|
+
import { afterEach, beforeEach, describe, vi } from "vitest";
|
|
4
|
+
import { test as baseTest } from "../../../../test/useMSW.js";
|
|
5
|
+
import { EFAudio } from "../../EFAudio";
|
|
6
|
+
import { makeAudioInitSegmentFetchTask } from "./makeAudioInitSegmentFetchTask";
|
|
7
|
+
|
|
8
|
+
@customElement("test-media-audio-init-segment-fetch")
|
|
9
|
+
class TestMediaAudioInitSegmentFetch extends EFAudio {}
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
interface HTMLElementTagNameMap {
|
|
13
|
+
"test-media-audio-init-segment-fetch": TestMediaAudioInitSegmentFetch;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const test = baseTest.extend<{
|
|
18
|
+
element: TestMediaAudioInitSegmentFetch;
|
|
19
|
+
}>({
|
|
20
|
+
element: async ({}, use) => {
|
|
21
|
+
const element = document.createElement(
|
|
22
|
+
"test-media-audio-init-segment-fetch",
|
|
23
|
+
);
|
|
24
|
+
await use(element);
|
|
25
|
+
element.remove();
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("makeAudioInitSegmentFetchTask", () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
// MSW setup is now handled by test fixtures
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
const elements = document.querySelectorAll(
|
|
36
|
+
"test-media-audio-init-segment-fetch",
|
|
37
|
+
);
|
|
38
|
+
for (const element of elements) {
|
|
39
|
+
element.remove();
|
|
40
|
+
}
|
|
41
|
+
vi.restoreAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("creates task with correct initial state", ({ element, expect }) => {
|
|
45
|
+
const task = makeAudioInitSegmentFetchTask(element);
|
|
46
|
+
|
|
47
|
+
expect(task).toBeDefined();
|
|
48
|
+
expect(task.status).toBe(TaskStatus.INITIAL);
|
|
49
|
+
expect(task.value).toBeUndefined();
|
|
50
|
+
expect(task.error).toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("task integrates with element properties", ({ element, expect }) => {
|
|
54
|
+
const task = makeAudioInitSegmentFetchTask(element);
|
|
55
|
+
|
|
56
|
+
expect(task).toBeDefined();
|
|
57
|
+
expect(task.status).toBe(TaskStatus.INITIAL);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Task } from "@lit/task";
|
|
2
|
+
import type { MediaEngine } from "../../../transcoding/types";
|
|
3
|
+
import type { EFMedia } from "../../EFMedia";
|
|
4
|
+
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
|
|
5
|
+
|
|
6
|
+
export const makeAudioInitSegmentFetchTask = (
|
|
7
|
+
host: EFMedia,
|
|
8
|
+
): Task<readonly [MediaEngine | undefined], ArrayBuffer> => {
|
|
9
|
+
return new Task(host, {
|
|
10
|
+
args: () => [host.mediaEngineTask.value] as const,
|
|
11
|
+
onError: (error) => {
|
|
12
|
+
console.error("audioInitSegmentFetchTask error", error);
|
|
13
|
+
},
|
|
14
|
+
onComplete: (_value) => {},
|
|
15
|
+
task: async ([_mediaEngine], { signal }) => {
|
|
16
|
+
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
17
|
+
return mediaEngine.fetchInitSegment(
|
|
18
|
+
mediaEngine.getAudioRendition(),
|
|
19
|
+
signal,
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { TaskStatus } from "@lit/task";
|
|
2
|
+
import { customElement } from "lit/decorators.js";
|
|
3
|
+
import { afterEach, beforeEach, describe, vi } from "vitest";
|
|
4
|
+
import { test as baseTest } from "../../../../test/useMSW.js";
|
|
5
|
+
import { EFAudio } from "../../EFAudio";
|
|
6
|
+
import { makeAudioInputTask } from "./makeAudioInputTask";
|
|
7
|
+
|
|
8
|
+
@customElement("test-media-audio-input")
|
|
9
|
+
class TestMediaAudioInput extends EFAudio {}
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
interface HTMLElementTagNameMap {
|
|
13
|
+
"test-media-audio-input": TestMediaAudioInput;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const test = baseTest.extend<{
|
|
18
|
+
element: TestMediaAudioInput;
|
|
19
|
+
}>({
|
|
20
|
+
element: async ({}, use) => {
|
|
21
|
+
const element = document.createElement("test-media-audio-input");
|
|
22
|
+
await use(element);
|
|
23
|
+
element.remove();
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("makeAudioInputTask", () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
// MSW setup is now handled by test fixtures
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
const elements = document.querySelectorAll("test-media-audio-input");
|
|
34
|
+
for (const element of elements) {
|
|
35
|
+
element.remove();
|
|
36
|
+
}
|
|
37
|
+
vi.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("creates task with correct initial state", ({ element, expect }) => {
|
|
41
|
+
const task = makeAudioInputTask(element);
|
|
42
|
+
|
|
43
|
+
expect(task).toBeDefined();
|
|
44
|
+
expect(task.status).toBe(TaskStatus.INITIAL);
|
|
45
|
+
expect(task.value).toBeUndefined();
|
|
46
|
+
expect(task.error).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("task integrates with element properties", ({ element, expect }) => {
|
|
50
|
+
const task = makeAudioInputTask(element);
|
|
51
|
+
|
|
52
|
+
expect(task).toBeDefined();
|
|
53
|
+
expect(task.status).toBe(TaskStatus.INITIAL);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Task } from "@lit/task";
|
|
2
|
+
import { EFMedia } from "../../EFMedia";
|
|
3
|
+
import { BufferedSeekingInput } from "../BufferedSeekingInput";
|
|
4
|
+
import type { InputTask } from "../shared/MediaTaskUtils";
|
|
5
|
+
|
|
6
|
+
export const makeAudioInputTask = (host: EFMedia): InputTask => {
|
|
7
|
+
return new Task<
|
|
8
|
+
readonly [ArrayBuffer | undefined, ArrayBuffer | undefined],
|
|
9
|
+
BufferedSeekingInput
|
|
10
|
+
>(host, {
|
|
11
|
+
args: () =>
|
|
12
|
+
[
|
|
13
|
+
host.audioInitSegmentFetchTask.value,
|
|
14
|
+
host.audioSegmentFetchTask.value,
|
|
15
|
+
] as const,
|
|
16
|
+
onError: (error) => {
|
|
17
|
+
console.error("audioInputTask error", error);
|
|
18
|
+
},
|
|
19
|
+
onComplete: (_value) => {},
|
|
20
|
+
task: async (_, { signal }) => {
|
|
21
|
+
const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
|
|
22
|
+
signal.throwIfAborted(); // Abort if a new seek started
|
|
23
|
+
const segment = await host.audioSegmentFetchTask.taskComplete;
|
|
24
|
+
signal.throwIfAborted(); // Abort if a new seek started
|
|
25
|
+
if (!initSegment || !segment) {
|
|
26
|
+
throw new Error("Init segment or segment is not available");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get startTimeOffsetMs from the audio rendition if available
|
|
30
|
+
const mediaEngine = await host.mediaEngineTask.taskComplete;
|
|
31
|
+
const audioRendition = mediaEngine?.audioRendition;
|
|
32
|
+
const startTimeOffsetMs = audioRendition?.startTimeOffsetMs;
|
|
33
|
+
|
|
34
|
+
const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
|
|
35
|
+
signal.throwIfAborted(); // Abort if a new seek started
|
|
36
|
+
return new BufferedSeekingInput(arrayBuffer, {
|
|
37
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
38
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
39
|
+
startTimeOffsetMs,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
};
|
package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe } from "vitest";
|
|
2
|
+
import { test as baseTest } from "../../../../test/useMSW.js";
|
|
3
|
+
import type { EFConfiguration } from "../../../gui/EFConfiguration.js";
|
|
4
|
+
import "../../../gui/EFPreview.js";
|
|
5
|
+
import "../../EFTimegroup.js";
|
|
6
|
+
import type { EFTimegroup } from "../../EFTimegroup.js";
|
|
7
|
+
import "../../EFVideo.js";
|
|
8
|
+
import type { EFVideo } from "../../EFVideo.js";
|
|
9
|
+
|
|
10
|
+
const test = baseTest.extend<{
|
|
11
|
+
timegroup: EFTimegroup;
|
|
12
|
+
video: EFVideo;
|
|
13
|
+
configuration: EFConfiguration;
|
|
14
|
+
}>({
|
|
15
|
+
timegroup: async ({}, use) => {
|
|
16
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
17
|
+
timegroup.setAttribute("mode", "sequence");
|
|
18
|
+
timegroup.setAttribute("id", "test-timegroup"); // Required for localStorage key
|
|
19
|
+
timegroup.style.cssText =
|
|
20
|
+
"position: relative; height: 500px; width: 1000px; overflow: hidden; background-color: rgb(100 116 139);";
|
|
21
|
+
await use(timegroup);
|
|
22
|
+
},
|
|
23
|
+
configuration: async ({ expect }, use) => {
|
|
24
|
+
const configuration = document.createElement("ef-configuration");
|
|
25
|
+
configuration.innerHTML = `<h1 style="font: 10px monospace">${expect.getState().currentTestName}</h1>`;
|
|
26
|
+
// Use integrated proxy server (same host/port as test runner)
|
|
27
|
+
const apiHost = `${window.location.protocol}//${window.location.host}`;
|
|
28
|
+
configuration.setAttribute("api-host", apiHost);
|
|
29
|
+
configuration.apiHost = apiHost;
|
|
30
|
+
document.body.appendChild(configuration);
|
|
31
|
+
await use(configuration);
|
|
32
|
+
},
|
|
33
|
+
video: async ({ configuration, timegroup }, use) => {
|
|
34
|
+
const video = document.createElement("ef-video");
|
|
35
|
+
video.id = "bars-n-tone2";
|
|
36
|
+
video.src = "http://web:3000/head-moov-480p.mp4"; // Real video from working simple-demo
|
|
37
|
+
video.style.cssText =
|
|
38
|
+
"width: 100%; height: 100%; object-fit: cover; position: absolute; top: 0; left: 0;";
|
|
39
|
+
|
|
40
|
+
// Create the exact structure from simple-demo.html
|
|
41
|
+
const innerTimegroup = document.createElement("ef-timegroup");
|
|
42
|
+
innerTimegroup.mode = "contain";
|
|
43
|
+
innerTimegroup.style.cssText =
|
|
44
|
+
"position: absolute; width: 100%; height: 100%;";
|
|
45
|
+
innerTimegroup.append(video);
|
|
46
|
+
timegroup.append(innerTimegroup);
|
|
47
|
+
configuration.append(timegroup);
|
|
48
|
+
|
|
49
|
+
await use(video);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Regression test for chunk boundary seeking issue
|
|
55
|
+
*
|
|
56
|
+
* Root cause: 32ms coordination gap between PlaybackController and audio track boundaries
|
|
57
|
+
* - PlaybackController seeks to chunk boundary: 4000ms
|
|
58
|
+
* - Audio track actually starts at: 4032ms
|
|
59
|
+
* - Error: "Seek time 4000ms is outside track range [4032ms, 6016ms]"
|
|
60
|
+
*
|
|
61
|
+
* This occurs during active playbook and browser reloads at 4s mark.
|
|
62
|
+
* Fix: Coordinate chunk boundaries or add tolerance for small gaps.
|
|
63
|
+
*/
|
|
64
|
+
describe("Audio Seek Task - Chunk Boundary Regression Test", () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
// Clean up DOM and localStorage
|
|
67
|
+
while (document.body.children.length) {
|
|
68
|
+
document.body.children[0]?.remove();
|
|
69
|
+
}
|
|
70
|
+
localStorage.clear();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterEach(async () => {
|
|
74
|
+
// Clean up any remaining elements
|
|
75
|
+
const videos = document.querySelectorAll("ef-video");
|
|
76
|
+
for (const video of videos) {
|
|
77
|
+
video.remove();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("should not throw RangeError when seeking to exact 4000ms during playback", async ({
|
|
82
|
+
video,
|
|
83
|
+
timegroup,
|
|
84
|
+
expect,
|
|
85
|
+
}) => {
|
|
86
|
+
await video.mediaEngineTask.taskComplete;
|
|
87
|
+
await video.audioInputTask.taskComplete;
|
|
88
|
+
|
|
89
|
+
// Simulate active playback - start playing from beginning
|
|
90
|
+
timegroup.currentTimeMs = 0;
|
|
91
|
+
await video.audioSeekTask.taskComplete;
|
|
92
|
+
|
|
93
|
+
// Now seek to the exact problematic time that causes:
|
|
94
|
+
// "Seek time 4000ms is outside track range [4032ms, 6016ms]"
|
|
95
|
+
const exactChunkBoundary = 4000;
|
|
96
|
+
timegroup.currentTimeMs = exactChunkBoundary;
|
|
97
|
+
|
|
98
|
+
// Should not throw RangeError due to track range mismatch
|
|
99
|
+
await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("should not throw RangeError during progressive playback across segments", async ({
|
|
103
|
+
video,
|
|
104
|
+
timegroup,
|
|
105
|
+
expect,
|
|
106
|
+
}) => {
|
|
107
|
+
await video.mediaEngineTask.taskComplete;
|
|
108
|
+
await video.audioInputTask.taskComplete;
|
|
109
|
+
|
|
110
|
+
// Simulate progressive playback that loads segments on demand
|
|
111
|
+
// Start at 3500ms to be just before the 4-second boundary
|
|
112
|
+
timegroup.currentTimeMs = 3500;
|
|
113
|
+
await video.audioSeekTask.taskComplete;
|
|
114
|
+
|
|
115
|
+
// Now cross the 4-second chunk boundary where track range issues occur
|
|
116
|
+
// This should trigger the state where track range is [4032ms, 6016ms]
|
|
117
|
+
// but we're seeking to 4000ms
|
|
118
|
+
timegroup.currentTimeMs = 4000.000000000001; // The exact error from logs
|
|
119
|
+
|
|
120
|
+
// Should not throw "Seek time 4000.000000000001ms is outside track range [4032ms, 6016ms]"
|
|
121
|
+
await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("should not throw RangeError when localStorage restoration causes 0ms to 4000ms race condition", async ({
|
|
125
|
+
video,
|
|
126
|
+
timegroup,
|
|
127
|
+
expect,
|
|
128
|
+
}) => {
|
|
129
|
+
// REPRODUCE THE RACE CONDITION: Simulate localStorage having "4.0"
|
|
130
|
+
// This mimics the exact simple-demo.html scenario where:
|
|
131
|
+
// 1. Media loads with assumption of currentTimeMs = 0
|
|
132
|
+
// 2. localStorage restores currentTime to 4.0 seconds
|
|
133
|
+
// 3. Seeking 4000ms in segments loaded for 0ms range triggers RangeError
|
|
134
|
+
|
|
135
|
+
// Set localStorage BEFORE media finishes initializing
|
|
136
|
+
if (timegroup.id) {
|
|
137
|
+
localStorage.setItem(`ef-timegroup-${timegroup.id}`, "4.0");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Wait for media engine but NOT for full initialization
|
|
141
|
+
await video.mediaEngineTask.taskComplete;
|
|
142
|
+
|
|
143
|
+
// Now trigger the localStorage restoration that happens in waitForMediaDurations().then()
|
|
144
|
+
// This will load currentTime = 4.0 from localStorage, jumping from 0ms to 4000ms
|
|
145
|
+
timegroup.currentTime = timegroup.maybeLoadTimeFromLocalStorage();
|
|
146
|
+
|
|
147
|
+
// This should trigger: "Seek time 4000ms is outside track range [Yms, Zms]"
|
|
148
|
+
// because segments were loaded for 0ms but we're now seeking 4000ms
|
|
149
|
+
await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("should not throw RangeError when forced segment coordination mismatch occurs", async ({
|
|
153
|
+
video,
|
|
154
|
+
timegroup,
|
|
155
|
+
expect,
|
|
156
|
+
}) => {
|
|
157
|
+
await video.mediaEngineTask.taskComplete;
|
|
158
|
+
|
|
159
|
+
// FORCE SPECIFIC SEGMENT LOADING: Load a segment for 8000ms (segment 5)
|
|
160
|
+
timegroup.currentTimeMs = 8000;
|
|
161
|
+
await video.audioSegmentIdTask.taskComplete;
|
|
162
|
+
await video.audioSegmentFetchTask.taskComplete;
|
|
163
|
+
await video.audioInputTask.taskComplete;
|
|
164
|
+
|
|
165
|
+
// Verify we have segment 5 loaded (8000ms / 15000ms = segment 1, but 1-based = segment 1...
|
|
166
|
+
// Actually 8000ms maps to segment 5 based on the actual segment calculation)
|
|
167
|
+
const segmentId = video.audioSegmentIdTask.value;
|
|
168
|
+
expect(segmentId).toBe(4);
|
|
169
|
+
|
|
170
|
+
// Now seek to a time in a different segment to test coordination
|
|
171
|
+
timegroup.currentTimeMs = 4000;
|
|
172
|
+
|
|
173
|
+
// This tests the fundamental segment coordination issue:
|
|
174
|
+
// - We loaded segment 5 for 8000ms
|
|
175
|
+
// - Now seeking to 4000ms which should be in a different segment
|
|
176
|
+
// - Tests that seek doesn't fail due to segment boundary coordination
|
|
177
|
+
await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("should not throw RangeError when rapidly crossing segment boundaries", async ({
|
|
181
|
+
video,
|
|
182
|
+
timegroup,
|
|
183
|
+
expect,
|
|
184
|
+
}) => {
|
|
185
|
+
await video.mediaEngineTask.taskComplete;
|
|
186
|
+
|
|
187
|
+
// RAPID BOUNDARY CROSSING: This tests timing-sensitive segment coordination
|
|
188
|
+
const boundaries = [1000, 4000, 8000, 3000, 7000]; // Jump around within segment 1
|
|
189
|
+
|
|
190
|
+
for (const timeMs of boundaries) {
|
|
191
|
+
timegroup.currentTimeMs = timeMs;
|
|
192
|
+
// Don't await - test rapid succession to trigger coordination issues
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Final seek - this should not throw even after rapid boundary crossing
|
|
196
|
+
timegroup.currentTimeMs = 4000;
|
|
197
|
+
await expect(video.audioSeekTask.taskComplete).resolves.toBeDefined();
|
|
198
|
+
});
|
|
199
|
+
});
|