@editframe/elements 0.17.6-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/dist/EF_FRAMEGEN.js +1 -1
- package/dist/ScrubTrackManager.d.ts +2 -2
- 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.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/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.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 +75 -111
- package/dist/elements/EFMedia.js +141 -1111
- 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 +88 -13
- package/dist/elements/EFVideo.d.ts +60 -29
- package/dist/elements/EFVideo.js +103 -203
- 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.d.ts +2 -2
- package/dist/getRenderInfo.js +2 -1
- package/dist/gui/ContextMixin.js +17 -70
- 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/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/services/MediaSourceManager.d.ts +62 -0
- package/dist/services/MediaSourceManager.js +211 -0
- 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 -2
- package/src/elements/EFAudio.browsertest.ts +183 -43
- 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.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 +658 -265
- package/src/elements/EFMedia.ts +173 -1763
- package/src/elements/EFTemporal.ts +3 -4
- package/src/elements/EFTimegroup.browsertest.ts +6 -3
- package/src/elements/EFTimegroup.ts +152 -21
- package/src/elements/EFVideo.browsertest.ts +115 -37
- package/src/elements/EFVideo.ts +123 -452
- package/src/elements/EFWaveform.ts +1 -1
- package/src/elements/MediaController.ts +2 -12
- package/src/elements/SampleBuffer.ts +97 -0
- package/src/gui/ContextMixin.ts +23 -104
- 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 +38 -29
- 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 +320 -188
- package/test/recordReplayProxyPlugin.js +302 -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.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/{JitTranscodingClient.browsertest.d.ts → elements/EFMedia/AssetIdMediaEngine.test.d.ts} +0 -0
- /package/dist/{JitTranscodingClient.test.d.ts → elements/EFMedia/BaseMediaEngine.test.d.ts} +0 -0
- /package/dist/{ScrubTrackIntegration.test.d.ts → elements/EFMedia/BufferedSeekingInput.browsertest.d.ts} +0 -0
- /package/dist/{SegmentSwitchLoading.test.d.ts → elements/EFMedia/services/AudioElementFactory.browsertest.d.ts} +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { ReactiveController, ReactiveControllerHost } from "lit";
|
|
2
|
-
import { JitTranscodingClient } from "../JitTranscodingClient.ts";
|
|
3
2
|
|
|
4
3
|
export class MediaController implements ReactiveController {
|
|
5
4
|
#src: string | null = null;
|
|
@@ -29,10 +28,6 @@ export class MediaController implements ReactiveController {
|
|
|
29
28
|
return "asset";
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
if (JitTranscodingClient.isJitTranscodeEligible(this.host.src)) {
|
|
33
|
-
return "jit-transcode";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
31
|
return "asset";
|
|
37
32
|
}
|
|
38
33
|
|
|
@@ -84,7 +79,6 @@ export class MediaController implements ReactiveController {
|
|
|
84
79
|
|
|
85
80
|
handleUpdate(changes: Map<string, { oldValue: any; newValue: any }>) {
|
|
86
81
|
if (changes.has("src") || changes.has("assetId") || changes.has("mode")) {
|
|
87
|
-
console.log("SRC/ASSETID/MODE changed");
|
|
88
82
|
}
|
|
89
83
|
}
|
|
90
84
|
|
|
@@ -98,11 +92,7 @@ export class MediaController implements ReactiveController {
|
|
|
98
92
|
this.assetId = this.host.assetId;
|
|
99
93
|
}
|
|
100
94
|
|
|
101
|
-
hostDisconnected() {
|
|
102
|
-
console.log("hostDisconnected");
|
|
103
|
-
}
|
|
95
|
+
hostDisconnected() {}
|
|
104
96
|
|
|
105
|
-
updated() {
|
|
106
|
-
console.log("updated");
|
|
107
|
-
}
|
|
97
|
+
updated() {}
|
|
108
98
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AudioSample, VideoSample } from "mediabunny";
|
|
2
|
+
export type MediaSample = VideoSample | AudioSample;
|
|
3
|
+
|
|
4
|
+
// Generic sample buffer that works with both VideoSample and AudioSample
|
|
5
|
+
export class SampleBuffer {
|
|
6
|
+
private buffer: MediaSample[] = [];
|
|
7
|
+
private bufferSize: number;
|
|
8
|
+
|
|
9
|
+
constructor(bufferSize = 10) {
|
|
10
|
+
this.bufferSize = bufferSize;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
push(sample: MediaSample) {
|
|
14
|
+
// Defensive copy to avoid concurrent modification during iteration
|
|
15
|
+
const currentBuffer = [...this.buffer];
|
|
16
|
+
currentBuffer.push(sample);
|
|
17
|
+
|
|
18
|
+
if (currentBuffer.length > this.bufferSize) {
|
|
19
|
+
const shifted = currentBuffer.shift();
|
|
20
|
+
if (shifted) {
|
|
21
|
+
try {
|
|
22
|
+
shifted.close();
|
|
23
|
+
} catch (_error) {
|
|
24
|
+
// Sample already closed, continue
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Update buffer atomically
|
|
30
|
+
this.buffer = currentBuffer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
clear() {
|
|
34
|
+
// Get current buffer and clear atomically
|
|
35
|
+
const toClose = this.buffer;
|
|
36
|
+
this.buffer = [];
|
|
37
|
+
|
|
38
|
+
// Close samples after clearing to avoid holding references
|
|
39
|
+
for (const sample of toClose) {
|
|
40
|
+
try {
|
|
41
|
+
sample.close();
|
|
42
|
+
} catch (_error) {
|
|
43
|
+
// Sample already closed, continue
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
peek(): MediaSample | undefined {
|
|
49
|
+
// Defensive read - get current buffer state
|
|
50
|
+
const currentBuffer = this.buffer;
|
|
51
|
+
return currentBuffer[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
find(desiredSeekTimeMs: number): MediaSample | undefined {
|
|
55
|
+
// Take snapshot to avoid concurrent modification during iteration
|
|
56
|
+
const currentBuffer = [...this.buffer];
|
|
57
|
+
|
|
58
|
+
if (currentBuffer.length === 0) return undefined;
|
|
59
|
+
|
|
60
|
+
// Round to microsecond precision to handle floating point issues
|
|
61
|
+
// without introducing timing aliasing problems
|
|
62
|
+
const roundToMicroseconds = (timeMs: number) =>
|
|
63
|
+
Math.round(timeMs * 1000) / 1000;
|
|
64
|
+
const targetTimeMs = roundToMicroseconds(desiredSeekTimeMs);
|
|
65
|
+
|
|
66
|
+
// Find the sample that contains the target time
|
|
67
|
+
for (const sample of currentBuffer) {
|
|
68
|
+
const sampleStartMs = roundToMicroseconds((sample.timestamp || 0) * 1000);
|
|
69
|
+
const sampleDurationMs = roundToMicroseconds(
|
|
70
|
+
(sample.duration || 0) * 1000,
|
|
71
|
+
);
|
|
72
|
+
const sampleEndMs = sampleStartMs + sampleDurationMs;
|
|
73
|
+
|
|
74
|
+
// Check if the desired time falls within this sample's time span [start, end)
|
|
75
|
+
if (targetTimeMs >= sampleStartMs && targetTimeMs < sampleEndMs) {
|
|
76
|
+
return sample;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return undefined; // No sample contains the target time
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get length() {
|
|
84
|
+
return this.buffer.length;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get firstTimestamp() {
|
|
88
|
+
// Defensive read - get current buffer state
|
|
89
|
+
const currentBuffer = this.buffer;
|
|
90
|
+
return currentBuffer[0]?.timestamp || 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getContents(): MediaSample[] {
|
|
94
|
+
// Defensive copy to avoid concurrent modification during iteration
|
|
95
|
+
return [...this.buffer];
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/gui/ContextMixin.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { consume, createContext, provide } from "@lit/context";
|
|
2
2
|
import type { LitElement } from "lit";
|
|
3
3
|
import { property, state } from "lit/decorators.js";
|
|
4
|
-
|
|
5
4
|
import type { EFTimegroup } from "../elements/EFTimegroup.js";
|
|
6
5
|
import {
|
|
7
6
|
type EFConfiguration,
|
|
@@ -12,6 +11,8 @@ import { fetchContext } from "./fetchContext.js";
|
|
|
12
11
|
import { type FocusContext, focusContext } from "./focusContext.js";
|
|
13
12
|
import { focusedElementContext } from "./focusedElementContext.js";
|
|
14
13
|
import { loopContext, playingContext } from "./playingContext.js";
|
|
14
|
+
import { ElementConnectionManager } from "./services/ElementConnectionManager.js";
|
|
15
|
+
import { PlaybackController } from "./services/PlaybackController.js";
|
|
15
16
|
|
|
16
17
|
export const targetTimegroupContext = createContext<EFTimegroup | null>(
|
|
17
18
|
"target-timegroup",
|
|
@@ -135,9 +136,6 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
135
136
|
@state()
|
|
136
137
|
currentTimeMs = 0;
|
|
137
138
|
|
|
138
|
-
#FPS = 30;
|
|
139
|
-
#MS_PER_FRAME = 1000 / this.#FPS;
|
|
140
|
-
|
|
141
139
|
#timegroupObserver = new MutationObserver((mutations) => {
|
|
142
140
|
for (const mutation of mutations) {
|
|
143
141
|
if (mutation.type === "childList") {
|
|
@@ -210,116 +208,37 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
210
208
|
this.playing = false;
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
this.
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
}
|
|
211
|
+
// Services for clean playback management
|
|
212
|
+
// Keep connection manager for potential future use
|
|
213
|
+
// @ts-ignore - Keeping for future use
|
|
214
|
+
private _elementConnectionManager = new ElementConnectionManager(3000);
|
|
215
|
+
private playbackController = new PlaybackController({
|
|
216
|
+
fps: 30,
|
|
217
|
+
onTimeUpdate: (timeMs: number) => {
|
|
218
|
+
this.currentTimeMs = timeMs;
|
|
219
|
+
},
|
|
220
|
+
onPlayStateChange: (playing: boolean) => {
|
|
221
|
+
this.playing = playing;
|
|
222
|
+
},
|
|
223
|
+
onError: (error: Error) => {
|
|
224
|
+
console.error("🎵 [PLAYBACK_ERROR]:", error);
|
|
225
|
+
},
|
|
226
|
+
});
|
|
229
227
|
|
|
230
228
|
private async stopPlayback() {
|
|
231
|
-
|
|
232
|
-
if (this.#playbackAudioContext.state !== "closed") {
|
|
233
|
-
await this.#playbackAudioContext.close();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
if (this.#playbackAnimationFrameRequest) {
|
|
237
|
-
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
238
|
-
}
|
|
239
|
-
this.#playbackAudioContext = null;
|
|
229
|
+
await this.playbackController.stopPlayback();
|
|
240
230
|
}
|
|
241
231
|
|
|
242
232
|
private async startPlayback() {
|
|
243
|
-
await this.stopPlayback();
|
|
244
233
|
const timegroup = this.targetTimegroup;
|
|
245
234
|
if (!timegroup) {
|
|
246
235
|
return;
|
|
247
236
|
}
|
|
248
237
|
|
|
249
|
-
await
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const toMs = timegroup.endTimeMs;
|
|
254
|
-
|
|
255
|
-
if (fromMs >= toMs) {
|
|
256
|
-
this.pause();
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
let bufferCount = 0;
|
|
261
|
-
this.#playbackAudioContext = new AudioContext({
|
|
262
|
-
latencyHint: "playback",
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
if (this.#playbackAnimationFrameRequest) {
|
|
266
|
-
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
267
|
-
}
|
|
268
|
-
this.#syncPlayheadToAudioContext(timegroup, currentMs);
|
|
269
|
-
const playbackContext = this.#playbackAudioContext;
|
|
270
|
-
if (playbackContext.state === "suspended") {
|
|
271
|
-
console.warn(
|
|
272
|
-
"AudioContext is suspended, media playback will not work until user has interacted with page.",
|
|
273
|
-
);
|
|
274
|
-
this.playing = false;
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
await playbackContext.suspend();
|
|
278
|
-
|
|
279
|
-
const fillBuffer = async () => {
|
|
280
|
-
if (bufferCount > 1) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
const canFillBuffer = await queueBufferSource();
|
|
284
|
-
if (canFillBuffer) {
|
|
285
|
-
fillBuffer();
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const queueBufferSource = async () => {
|
|
290
|
-
if (currentMs >= toMs) {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
const startMs = currentMs;
|
|
294
|
-
const endMs = currentMs + this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
295
|
-
currentMs += this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
296
|
-
const audioBuffer = await timegroup.renderAudio(startMs, endMs);
|
|
297
|
-
bufferCount++;
|
|
298
|
-
const source = playbackContext.createBufferSource();
|
|
299
|
-
source.buffer = audioBuffer;
|
|
300
|
-
source.connect(playbackContext.destination);
|
|
301
|
-
source.start((startMs - fromMs) / 1000);
|
|
302
|
-
source.onended = () => {
|
|
303
|
-
bufferCount--;
|
|
304
|
-
if (endMs >= toMs) {
|
|
305
|
-
this.pause();
|
|
306
|
-
if (this.loop) {
|
|
307
|
-
this.updateComplete.then(() => {
|
|
308
|
-
this.currentTimeMs = 0;
|
|
309
|
-
this.updateComplete.then(() => {
|
|
310
|
-
this.play();
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
} else {
|
|
315
|
-
fillBuffer();
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
return true;
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
await fillBuffer();
|
|
322
|
-
await playbackContext.resume();
|
|
238
|
+
await this.playbackController.startPlayback(
|
|
239
|
+
timegroup,
|
|
240
|
+
timegroup.currentTimeMs,
|
|
241
|
+
);
|
|
323
242
|
}
|
|
324
243
|
}
|
|
325
244
|
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { ElementConnectionManager } from "./ElementConnectionManager.js";
|
|
3
|
+
|
|
4
|
+
describe("ElementConnectionManager", () => {
|
|
5
|
+
let manager: ElementConnectionManager;
|
|
6
|
+
let audioContext: AudioContext;
|
|
7
|
+
let mockTimegroup: any;
|
|
8
|
+
let mockMediaElements: any[];
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
manager = new ElementConnectionManager(3000);
|
|
12
|
+
audioContext = new AudioContext();
|
|
13
|
+
|
|
14
|
+
// Create mock media elements
|
|
15
|
+
mockMediaElements = [
|
|
16
|
+
{
|
|
17
|
+
src: "audio1.mp3",
|
|
18
|
+
startTimeMs: 1000,
|
|
19
|
+
endTimeMs: 3000,
|
|
20
|
+
currentSourceTimeMs: 0,
|
|
21
|
+
audioElement: { currentTime: 0, play: vi.fn(), pause: vi.fn() },
|
|
22
|
+
getMediaElementSource: vi.fn(),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
src: "audio2.mp3",
|
|
26
|
+
startTimeMs: 2500,
|
|
27
|
+
endTimeMs: 5000,
|
|
28
|
+
currentSourceTimeMs: 0,
|
|
29
|
+
audioElement: { currentTime: 0, play: vi.fn(), pause: vi.fn() },
|
|
30
|
+
getMediaElementSource: vi.fn(),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
src: "audio3.mp3",
|
|
34
|
+
startTimeMs: 6000,
|
|
35
|
+
endTimeMs: 8000,
|
|
36
|
+
currentSourceTimeMs: 0,
|
|
37
|
+
audioElement: { currentTime: 0, play: vi.fn(), pause: vi.fn() },
|
|
38
|
+
getMediaElementSource: vi.fn(),
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Mock timegroup
|
|
43
|
+
mockTimegroup = {
|
|
44
|
+
querySelectorAll: vi.fn().mockReturnValue(mockMediaElements),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Mock MediaElementAudioSourceNode for each element
|
|
48
|
+
mockMediaElements.forEach((element, _index) => {
|
|
49
|
+
const mockSource = {
|
|
50
|
+
connect: vi.fn(),
|
|
51
|
+
disconnect: vi.fn(),
|
|
52
|
+
context: audioContext,
|
|
53
|
+
} as any;
|
|
54
|
+
element.getMediaElementSource.mockResolvedValue(mockSource);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(async () => {
|
|
59
|
+
manager.clearAll();
|
|
60
|
+
if (audioContext.state !== "closed") {
|
|
61
|
+
await audioContext.close();
|
|
62
|
+
}
|
|
63
|
+
vi.clearAllMocks();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("Constructor and Configuration", () => {
|
|
67
|
+
test("should initialize with default lookahead", () => {
|
|
68
|
+
const defaultManager = new ElementConnectionManager();
|
|
69
|
+
expect(defaultManager.getLookaheadMs()).toBe(3000);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should initialize with custom lookahead", () => {
|
|
73
|
+
const customManager = new ElementConnectionManager(5000);
|
|
74
|
+
expect(customManager.getLookaheadMs()).toBe(5000);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("should allow updating lookahead", () => {
|
|
78
|
+
manager.setLookaheadMs(4000);
|
|
79
|
+
expect(manager.getLookaheadMs()).toBe(4000);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("Element Connection Logic", () => {
|
|
84
|
+
test("should prepare elements that are starting soon", async () => {
|
|
85
|
+
// At time 0ms with 3s lookahead, both audio1 (1000ms) and audio2 (2500ms) should be prepared
|
|
86
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 0);
|
|
87
|
+
|
|
88
|
+
const info = manager.getConnectionInfo();
|
|
89
|
+
expect(info.total).toBe(2); // Both audio1 and audio2 within 3s lookahead
|
|
90
|
+
expect(info.prepared).toBe(2);
|
|
91
|
+
expect(info.connected).toBe(0);
|
|
92
|
+
|
|
93
|
+
// Verify both elements were prepared
|
|
94
|
+
expect(mockMediaElements[0].getMediaElementSource).toHaveBeenCalledWith(
|
|
95
|
+
audioContext,
|
|
96
|
+
);
|
|
97
|
+
expect(mockMediaElements[1].getMediaElementSource).toHaveBeenCalledWith(
|
|
98
|
+
audioContext,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("should activate elements when they become current", async () => {
|
|
103
|
+
// First prepare the element
|
|
104
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 500);
|
|
105
|
+
|
|
106
|
+
// Then activate when it becomes current
|
|
107
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 1500);
|
|
108
|
+
|
|
109
|
+
const info = manager.getConnectionInfo();
|
|
110
|
+
expect(info.connected).toBe(1);
|
|
111
|
+
expect(mockMediaElements[0].audioElement.play).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("should handle multiple active elements simultaneously", async () => {
|
|
115
|
+
// At time 2750ms, both audio1 (1000-3000) and audio2 (2500-5000) should be active
|
|
116
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 2750);
|
|
117
|
+
|
|
118
|
+
const info = manager.getConnectionInfo();
|
|
119
|
+
expect(info.connected).toBe(2);
|
|
120
|
+
|
|
121
|
+
expect(mockMediaElements[0].audioElement.play).toHaveBeenCalled();
|
|
122
|
+
expect(mockMediaElements[1].audioElement.play).toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("should deactivate elements when they end", async () => {
|
|
126
|
+
// Activate audio1
|
|
127
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 2000);
|
|
128
|
+
expect(manager.getConnectionInfo().connected).toBe(1);
|
|
129
|
+
|
|
130
|
+
// Move past audio1's end time
|
|
131
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 3500);
|
|
132
|
+
|
|
133
|
+
const info = manager.getConnectionInfo();
|
|
134
|
+
// audio1 should be deactivated but still prepared, audio2 should be active
|
|
135
|
+
expect(info.connected).toBe(1); // Only audio2 active
|
|
136
|
+
expect(mockMediaElements[0].audioElement.pause).toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("should cleanup old elements outside cleanup threshold", async () => {
|
|
140
|
+
// Activate and then move far past
|
|
141
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 2000);
|
|
142
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 10000); // 7 seconds later
|
|
143
|
+
|
|
144
|
+
const info = manager.getConnectionInfo();
|
|
145
|
+
// All old elements should be cleaned up, only audio3 might be prepared if in lookahead
|
|
146
|
+
expect(info.total).toBeLessThan(mockMediaElements.length);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("Error Handling", () => {
|
|
151
|
+
test("should propagate getMediaElementSource errors", async () => {
|
|
152
|
+
// Mock both elements within lookahead to fail
|
|
153
|
+
mockMediaElements[0].getMediaElementSource.mockRejectedValue(
|
|
154
|
+
new Error("Connection failed"),
|
|
155
|
+
);
|
|
156
|
+
mockMediaElements[1].getMediaElementSource.mockRejectedValue(
|
|
157
|
+
new Error("Connection failed"),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Should throw the built-in error
|
|
161
|
+
await expect(
|
|
162
|
+
manager.updateConnectedElements(audioContext, mockTimegroup, 500),
|
|
163
|
+
).rejects.toThrow("Connection failed");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("should propagate activation errors", async () => {
|
|
167
|
+
mockMediaElements[0].audioElement.play.mockRejectedValue(
|
|
168
|
+
new Error("Play failed"),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 500);
|
|
172
|
+
|
|
173
|
+
// Should throw the built-in error when activating
|
|
174
|
+
await expect(
|
|
175
|
+
manager.updateConnectedElements(audioContext, mockTimegroup, 1500),
|
|
176
|
+
).rejects.toThrow("Play failed");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("should handle closed AudioContext gracefully", async () => {
|
|
180
|
+
await audioContext.close();
|
|
181
|
+
|
|
182
|
+
await expect(
|
|
183
|
+
manager.updateConnectedElements(audioContext, mockTimegroup, 1500),
|
|
184
|
+
).resolves.not.toThrow();
|
|
185
|
+
|
|
186
|
+
expect(manager.getConnectionInfo().total).toBe(0);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("Cleanup and Lifecycle", () => {
|
|
191
|
+
test("should clear all connections", async () => {
|
|
192
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 2000);
|
|
193
|
+
expect(manager.getConnectionInfo().total).toBeGreaterThan(0);
|
|
194
|
+
|
|
195
|
+
manager.clearAll();
|
|
196
|
+
expect(manager.getConnectionInfo().total).toBe(0);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("should disconnect connected elements during clearAll", async () => {
|
|
200
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 2000);
|
|
201
|
+
|
|
202
|
+
const mockSource = await mockMediaElements[0].getMediaElementSource();
|
|
203
|
+
|
|
204
|
+
manager.clearAll();
|
|
205
|
+
|
|
206
|
+
expect(mockSource.disconnect).toHaveBeenCalled();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("should handle clearAll with no elements gracefully", () => {
|
|
210
|
+
expect(() => manager.clearAll()).not.toThrow();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("Connection Info and Debugging", () => {
|
|
215
|
+
test("should provide accurate connection info", async () => {
|
|
216
|
+
const initialInfo = manager.getConnectionInfo();
|
|
217
|
+
expect(initialInfo).toEqual({ total: 0, connected: 0, prepared: 0 });
|
|
218
|
+
|
|
219
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 500);
|
|
220
|
+
const preparedInfo = manager.getConnectionInfo();
|
|
221
|
+
expect(preparedInfo.prepared).toBeGreaterThan(0);
|
|
222
|
+
|
|
223
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 1500);
|
|
224
|
+
const activeInfo = manager.getConnectionInfo();
|
|
225
|
+
expect(activeInfo.connected).toBeGreaterThan(0);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("should track connected vs prepared states accurately", async () => {
|
|
229
|
+
// Prepare multiple elements
|
|
230
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 0);
|
|
231
|
+
|
|
232
|
+
// Activate one
|
|
233
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 2750);
|
|
234
|
+
|
|
235
|
+
const info = manager.getConnectionInfo();
|
|
236
|
+
expect(info.total).toBe(info.connected + info.prepared);
|
|
237
|
+
expect(info.connected).toBeGreaterThan(0);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("Lookahead Behavior", () => {
|
|
242
|
+
test("should respect lookahead window for preparation", async () => {
|
|
243
|
+
manager.setLookaheadMs(1000); // 1 second lookahead
|
|
244
|
+
|
|
245
|
+
// At time 0, only elements starting within 1 second should be prepared
|
|
246
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 0);
|
|
247
|
+
|
|
248
|
+
const info = manager.getConnectionInfo();
|
|
249
|
+
expect(info.total).toBe(1); // Only audio1 at 1000ms
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("should adjust preparation based on lookahead changes", async () => {
|
|
253
|
+
manager.setLookaheadMs(500); // Very short lookahead
|
|
254
|
+
|
|
255
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 0);
|
|
256
|
+
expect(manager.getConnectionInfo().total).toBe(0); // No elements within 500ms
|
|
257
|
+
|
|
258
|
+
manager.setLookaheadMs(2000); // Longer lookahead
|
|
259
|
+
await manager.updateConnectedElements(audioContext, mockTimegroup, 0);
|
|
260
|
+
expect(manager.getConnectionInfo().total).toBe(1); // audio1 now within range
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|