@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
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { test as baseTest, describe, vi } from "vitest";
|
|
2
|
+
import type {
|
|
3
|
+
AudioRendition,
|
|
4
|
+
MediaEngine,
|
|
5
|
+
VideoRendition,
|
|
6
|
+
} from "../../../transcoding/types";
|
|
7
|
+
import {
|
|
8
|
+
calculateSegmentRange,
|
|
9
|
+
computeSegmentId,
|
|
10
|
+
getAudioRendition,
|
|
11
|
+
getVideoRendition,
|
|
12
|
+
} from "./RenditionHelpers";
|
|
13
|
+
|
|
14
|
+
const test = baseTest.extend<{
|
|
15
|
+
mockMediaEngine: MediaEngine;
|
|
16
|
+
mockVideoRendition: VideoRendition;
|
|
17
|
+
mockAudioRendition: AudioRendition;
|
|
18
|
+
mockMediaEngineWithoutAudio: MediaEngine;
|
|
19
|
+
mockMediaEngineWithoutVideo: MediaEngine;
|
|
20
|
+
}>({
|
|
21
|
+
mockMediaEngine: async ({}, use) => {
|
|
22
|
+
const mockVideoRendition = {
|
|
23
|
+
trackId: 1,
|
|
24
|
+
src: "video-track.mp4",
|
|
25
|
+
segmentDurationMs: 1000,
|
|
26
|
+
} as VideoRendition;
|
|
27
|
+
|
|
28
|
+
const mockAudioRendition = {
|
|
29
|
+
trackId: 2,
|
|
30
|
+
src: "audio-track.mp4",
|
|
31
|
+
segmentDurationMs: 1000,
|
|
32
|
+
} as AudioRendition;
|
|
33
|
+
|
|
34
|
+
const mockMediaEngine = {
|
|
35
|
+
durationMs: 10000,
|
|
36
|
+
src: "https://example.com/media.mp4",
|
|
37
|
+
videoRendition: mockVideoRendition,
|
|
38
|
+
audioRendition: mockAudioRendition,
|
|
39
|
+
fetchMediaSegment: vi.fn(),
|
|
40
|
+
} as unknown as MediaEngine;
|
|
41
|
+
await use(mockMediaEngine);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
mockVideoRendition: async ({}, use) => {
|
|
45
|
+
const mockVideoRendition = {
|
|
46
|
+
trackId: 1,
|
|
47
|
+
src: "video-track.mp4",
|
|
48
|
+
segmentDurationMs: 1000,
|
|
49
|
+
} as VideoRendition;
|
|
50
|
+
await use(mockVideoRendition);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
mockAudioRendition: async ({}, use) => {
|
|
54
|
+
const mockAudioRendition = {
|
|
55
|
+
trackId: 2,
|
|
56
|
+
src: "audio-track.mp4",
|
|
57
|
+
segmentDurationMs: 1000,
|
|
58
|
+
} as AudioRendition;
|
|
59
|
+
await use(mockAudioRendition);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
mockMediaEngineWithoutAudio: async ({}, use) => {
|
|
63
|
+
const mockMediaEngine = {
|
|
64
|
+
durationMs: 10000,
|
|
65
|
+
src: "https://example.com/media.mp4",
|
|
66
|
+
videoRendition: {
|
|
67
|
+
trackId: 1,
|
|
68
|
+
src: "video-track.mp4",
|
|
69
|
+
segmentDurationMs: 1000,
|
|
70
|
+
} as VideoRendition,
|
|
71
|
+
audioRendition: null,
|
|
72
|
+
fetchMediaSegment: vi.fn(),
|
|
73
|
+
} as unknown as MediaEngine;
|
|
74
|
+
await use(mockMediaEngine);
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
mockMediaEngineWithoutVideo: async ({}, use) => {
|
|
78
|
+
const mockMediaEngine = {
|
|
79
|
+
durationMs: 10000,
|
|
80
|
+
src: "https://example.com/media.mp4",
|
|
81
|
+
videoRendition: null,
|
|
82
|
+
audioRendition: {
|
|
83
|
+
trackId: 2,
|
|
84
|
+
src: "audio-track.mp4",
|
|
85
|
+
segmentDurationMs: 1000,
|
|
86
|
+
} as AudioRendition,
|
|
87
|
+
fetchMediaSegment: vi.fn(),
|
|
88
|
+
} as unknown as MediaEngine;
|
|
89
|
+
await use(mockMediaEngine);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("RenditionHelpers", () => {
|
|
94
|
+
describe("getAudioRendition", () => {
|
|
95
|
+
test("returns audio rendition when available", ({
|
|
96
|
+
mockMediaEngine,
|
|
97
|
+
expect,
|
|
98
|
+
}) => {
|
|
99
|
+
const result = getAudioRendition(mockMediaEngine);
|
|
100
|
+
expect(result).toBe(mockMediaEngine.audioRendition);
|
|
101
|
+
expect(result.trackId).toBe(2);
|
|
102
|
+
expect(result.src).toBe("audio-track.mp4");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("throws error when audio rendition is not available", ({
|
|
106
|
+
mockMediaEngineWithoutAudio,
|
|
107
|
+
expect,
|
|
108
|
+
}) => {
|
|
109
|
+
expect(() => getAudioRendition(mockMediaEngineWithoutAudio)).toThrow(
|
|
110
|
+
"Audio rendition is not available",
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("getVideoRendition", () => {
|
|
116
|
+
test("returns video rendition when available", ({
|
|
117
|
+
mockMediaEngine,
|
|
118
|
+
expect,
|
|
119
|
+
}) => {
|
|
120
|
+
const result = getVideoRendition(mockMediaEngine);
|
|
121
|
+
expect(result).toBe(mockMediaEngine.videoRendition);
|
|
122
|
+
expect(result.trackId).toBe(1);
|
|
123
|
+
expect(result.src).toBe("video-track.mp4");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("throws error when video rendition is not available", ({
|
|
127
|
+
mockMediaEngineWithoutVideo,
|
|
128
|
+
expect,
|
|
129
|
+
}) => {
|
|
130
|
+
expect(() => getVideoRendition(mockMediaEngineWithoutVideo)).toThrow(
|
|
131
|
+
"Video rendition is not available",
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("computeSegmentId", () => {
|
|
137
|
+
test("calculates segment ID correctly for audio rendition", ({
|
|
138
|
+
mockAudioRendition,
|
|
139
|
+
expect,
|
|
140
|
+
}) => {
|
|
141
|
+
// Test various time points
|
|
142
|
+
expect(computeSegmentId(0, mockAudioRendition)).toBe(1); // First segment
|
|
143
|
+
expect(computeSegmentId(500, mockAudioRendition)).toBe(1); // Still first segment
|
|
144
|
+
expect(computeSegmentId(999, mockAudioRendition)).toBe(1); // Still first segment
|
|
145
|
+
expect(computeSegmentId(1000, mockAudioRendition)).toBe(2); // Second segment
|
|
146
|
+
expect(computeSegmentId(1500, mockAudioRendition)).toBe(2); // Still second segment
|
|
147
|
+
expect(computeSegmentId(2000, mockAudioRendition)).toBe(3); // Third segment
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("calculates segment ID correctly for video rendition", ({
|
|
151
|
+
mockVideoRendition,
|
|
152
|
+
expect,
|
|
153
|
+
}) => {
|
|
154
|
+
// Test various time points
|
|
155
|
+
expect(computeSegmentId(0, mockVideoRendition)).toBe(1); // First segment
|
|
156
|
+
expect(computeSegmentId(999, mockVideoRendition)).toBe(1); // Still first segment
|
|
157
|
+
expect(computeSegmentId(1000, mockVideoRendition)).toBe(2); // Second segment
|
|
158
|
+
expect(computeSegmentId(2500, mockVideoRendition)).toBe(3); // Third segment
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("returns undefined when segmentDurationMs is not available", ({
|
|
162
|
+
expect,
|
|
163
|
+
}) => {
|
|
164
|
+
const renditionWithoutDuration = {
|
|
165
|
+
trackId: 1,
|
|
166
|
+
src: "test.mp4",
|
|
167
|
+
segmentDurationMs: undefined,
|
|
168
|
+
} as AudioRendition;
|
|
169
|
+
|
|
170
|
+
expect(computeSegmentId(1000, renditionWithoutDuration)).toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("handles edge case of negative time", ({
|
|
174
|
+
mockAudioRendition,
|
|
175
|
+
expect,
|
|
176
|
+
}) => {
|
|
177
|
+
expect(computeSegmentId(-100, mockAudioRendition)).toBe(1); // Should clamp to segment 1
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("calculateSegmentRange", () => {
|
|
182
|
+
test("calculates segment range for single segment", ({
|
|
183
|
+
mockAudioRendition,
|
|
184
|
+
expect,
|
|
185
|
+
}) => {
|
|
186
|
+
const result = calculateSegmentRange(100, 800, mockAudioRendition);
|
|
187
|
+
expect(result).toEqual([1]);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("calculates segment range spanning multiple segments", ({
|
|
191
|
+
mockAudioRendition,
|
|
192
|
+
expect,
|
|
193
|
+
}) => {
|
|
194
|
+
const result = calculateSegmentRange(500, 2500, mockAudioRendition);
|
|
195
|
+
expect(result).toEqual([1, 2, 3]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("calculates segment range for exact segment boundaries", ({
|
|
199
|
+
mockAudioRendition,
|
|
200
|
+
expect,
|
|
201
|
+
}) => {
|
|
202
|
+
const result = calculateSegmentRange(1000, 2000, mockAudioRendition);
|
|
203
|
+
expect(result).toEqual([2, 3]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("handles single time point (start equals end)", ({
|
|
207
|
+
mockAudioRendition,
|
|
208
|
+
expect,
|
|
209
|
+
}) => {
|
|
210
|
+
const result = calculateSegmentRange(1500, 1500, mockAudioRendition);
|
|
211
|
+
expect(result).toEqual([2]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("returns empty array when segmentDurationMs is not available", ({
|
|
215
|
+
expect,
|
|
216
|
+
}) => {
|
|
217
|
+
const renditionWithoutDuration = {
|
|
218
|
+
trackId: 1,
|
|
219
|
+
src: "test.mp4",
|
|
220
|
+
segmentDurationMs: undefined,
|
|
221
|
+
} as AudioRendition;
|
|
222
|
+
|
|
223
|
+
const result = calculateSegmentRange(
|
|
224
|
+
1000,
|
|
225
|
+
2000,
|
|
226
|
+
renditionWithoutDuration,
|
|
227
|
+
);
|
|
228
|
+
expect(result).toEqual([]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("works with video renditions too", ({
|
|
232
|
+
mockVideoRendition,
|
|
233
|
+
expect,
|
|
234
|
+
}) => {
|
|
235
|
+
const result = calculateSegmentRange(1500, 3500, mockVideoRendition);
|
|
236
|
+
expect(result).toEqual([2, 3, 4]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("handles edge case where start time is negative", ({
|
|
240
|
+
mockAudioRendition,
|
|
241
|
+
expect,
|
|
242
|
+
}) => {
|
|
243
|
+
const result = calculateSegmentRange(-500, 1500, mockAudioRendition);
|
|
244
|
+
expect(result).toEqual([1, 2]);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AudioRendition,
|
|
3
|
+
MediaEngine,
|
|
4
|
+
VideoRendition,
|
|
5
|
+
} from "../../../transcoding/types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get audio rendition from media engine, throwing if not available
|
|
9
|
+
*/
|
|
10
|
+
export const getAudioRendition = (mediaEngine: MediaEngine): AudioRendition => {
|
|
11
|
+
const audioRendition = mediaEngine.audioRendition;
|
|
12
|
+
if (!audioRendition) {
|
|
13
|
+
throw new Error("Audio rendition is not available");
|
|
14
|
+
}
|
|
15
|
+
return audioRendition;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get video rendition from media engine, throwing if not available
|
|
20
|
+
*/
|
|
21
|
+
export const getVideoRendition = (mediaEngine: MediaEngine): VideoRendition => {
|
|
22
|
+
const videoRendition = mediaEngine.videoRendition;
|
|
23
|
+
if (!videoRendition) {
|
|
24
|
+
throw new Error("Video rendition is not available");
|
|
25
|
+
}
|
|
26
|
+
return videoRendition;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Calculate which segment contains a given timestamp
|
|
31
|
+
* Returns 1-based segment ID, or undefined if segmentDurationMs is not available
|
|
32
|
+
*/
|
|
33
|
+
export const computeSegmentId = (
|
|
34
|
+
timeMs: number,
|
|
35
|
+
rendition: AudioRendition | VideoRendition,
|
|
36
|
+
): number | undefined => {
|
|
37
|
+
if (!rendition.segmentDurationMs) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Handle negative time by clamping to 0
|
|
42
|
+
const adjustedTimeMs = Math.max(0, timeMs);
|
|
43
|
+
const segmentIndex = Math.floor(adjustedTimeMs / rendition.segmentDurationMs);
|
|
44
|
+
return segmentIndex + 1; // Convert to 1-based segment ID
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Calculate range of segment IDs that overlap with a time range
|
|
49
|
+
* Returns array of 1-based segment IDs, or empty array if segmentDurationMs is not available
|
|
50
|
+
*/
|
|
51
|
+
export const calculateSegmentRange = (
|
|
52
|
+
startTimeMs: number,
|
|
53
|
+
endTimeMs: number,
|
|
54
|
+
rendition: AudioRendition | VideoRendition,
|
|
55
|
+
): number[] => {
|
|
56
|
+
if (!rendition.segmentDurationMs) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Handle edge case where start equals end
|
|
61
|
+
if (startTimeMs === endTimeMs) {
|
|
62
|
+
const segmentId = computeSegmentId(startTimeMs, rendition);
|
|
63
|
+
return segmentId ? [segmentId] : [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const startSegmentId = computeSegmentId(startTimeMs, rendition);
|
|
67
|
+
const endSegmentId = computeSegmentId(endTimeMs, rendition);
|
|
68
|
+
|
|
69
|
+
if (startSegmentId === undefined || endSegmentId === undefined) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const segments: number[] = [];
|
|
74
|
+
for (let segmentId = startSegmentId; segmentId <= endSegmentId; segmentId++) {
|
|
75
|
+
segments.push(segmentId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return segments;
|
|
79
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { test as baseTest, describe, vi } from "vitest";
|
|
2
|
+
import type { UrlGenerator } from "../../../transcoding/utils/UrlGenerator";
|
|
3
|
+
import {
|
|
4
|
+
createMediaEngine,
|
|
5
|
+
handleMediaEngineComplete,
|
|
6
|
+
} from "./makeMediaEngineTask";
|
|
7
|
+
|
|
8
|
+
// Define test fixtures using test.extend
|
|
9
|
+
const test = baseTest.extend<{
|
|
10
|
+
mockUrlGenerator: UrlGenerator;
|
|
11
|
+
mockHost: any;
|
|
12
|
+
mockTimegroup: any;
|
|
13
|
+
}>({
|
|
14
|
+
mockUrlGenerator: async ({}, use) => {
|
|
15
|
+
const mockUrlGenerator = {
|
|
16
|
+
generateManifestUrl: vi
|
|
17
|
+
.fn()
|
|
18
|
+
.mockReturnValue("https://example.com/manifest.m3u8"),
|
|
19
|
+
} as any;
|
|
20
|
+
await use(mockUrlGenerator);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
mockTimegroup: async ({}, use) => {
|
|
24
|
+
const mockTimegroup = {
|
|
25
|
+
currentTimeMs: 0,
|
|
26
|
+
requestUpdate: vi.fn(),
|
|
27
|
+
};
|
|
28
|
+
await use(mockTimegroup);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
mockHost: async ({ mockTimegroup, mockUrlGenerator }, use) => {
|
|
32
|
+
const mockHost = {
|
|
33
|
+
src: "https://example.com/video.mp4",
|
|
34
|
+
assetId: null,
|
|
35
|
+
urlGenerator: mockUrlGenerator,
|
|
36
|
+
apiHost: "https://api.example.com",
|
|
37
|
+
requestUpdate: vi.fn(),
|
|
38
|
+
rootTimegroup: mockTimegroup,
|
|
39
|
+
};
|
|
40
|
+
await use(mockHost);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("createMediaEngine", () => {
|
|
45
|
+
describe("input validation", () => {
|
|
46
|
+
test("should reject when assetId provided but apiHost is missing", async ({
|
|
47
|
+
mockHost,
|
|
48
|
+
expect,
|
|
49
|
+
}) => {
|
|
50
|
+
// Set up host with assetId but no apiHost
|
|
51
|
+
mockHost.src = "test.mp4";
|
|
52
|
+
mockHost.assetId = "asset123";
|
|
53
|
+
mockHost.apiHost = null;
|
|
54
|
+
|
|
55
|
+
await expect(createMediaEngine(mockHost)).rejects.toThrow(
|
|
56
|
+
"API host is required for AssetID mode",
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("should reject with empty src", async ({ mockHost, expect }) => {
|
|
61
|
+
// Set up host with empty src
|
|
62
|
+
mockHost.src = "";
|
|
63
|
+
mockHost.assetId = null;
|
|
64
|
+
|
|
65
|
+
await expect(createMediaEngine(mockHost)).rejects.toThrow(
|
|
66
|
+
"Unsupported media source",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("should reject with null src", async ({ mockHost, expect }) => {
|
|
71
|
+
// Set up host with null src
|
|
72
|
+
mockHost.src = null;
|
|
73
|
+
mockHost.assetId = null;
|
|
74
|
+
|
|
75
|
+
await expect(createMediaEngine(mockHost)).rejects.toThrow(
|
|
76
|
+
"Unsupported media source",
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("should reject with whitespace-only src", async ({
|
|
81
|
+
mockHost,
|
|
82
|
+
expect,
|
|
83
|
+
}) => {
|
|
84
|
+
// Set up host with whitespace-only src
|
|
85
|
+
mockHost.src = " \t\n ";
|
|
86
|
+
mockHost.assetId = null;
|
|
87
|
+
|
|
88
|
+
await expect(createMediaEngine(mockHost)).rejects.toThrow(
|
|
89
|
+
"Unsupported media source",
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("handleMediaEngineComplete", () => {
|
|
96
|
+
test("should call requestUpdate on host", async ({ mockHost, expect }) => {
|
|
97
|
+
handleMediaEngineComplete(mockHost);
|
|
98
|
+
|
|
99
|
+
expect(mockHost.requestUpdate).toHaveBeenCalledWith("intrinsicDurationMs");
|
|
100
|
+
expect(mockHost.requestUpdate).toHaveBeenCalledWith("ownCurrentTimeMs");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("should call requestUpdate on rootTimegroup when present", async ({
|
|
104
|
+
mockHost,
|
|
105
|
+
mockTimegroup,
|
|
106
|
+
expect,
|
|
107
|
+
}) => {
|
|
108
|
+
handleMediaEngineComplete(mockHost);
|
|
109
|
+
|
|
110
|
+
expect(mockTimegroup.requestUpdate).toHaveBeenCalledWith(
|
|
111
|
+
"ownCurrentTimeMs",
|
|
112
|
+
);
|
|
113
|
+
expect(mockTimegroup.requestUpdate).toHaveBeenCalledWith("durationMs");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("should handle missing rootTimegroup gracefully", async ({
|
|
117
|
+
mockHost,
|
|
118
|
+
expect,
|
|
119
|
+
}) => {
|
|
120
|
+
mockHost.rootTimegroup = null;
|
|
121
|
+
|
|
122
|
+
// Should not throw
|
|
123
|
+
handleMediaEngineComplete(mockHost);
|
|
124
|
+
|
|
125
|
+
expect(mockHost.requestUpdate).toHaveBeenCalledWith("intrinsicDurationMs");
|
|
126
|
+
expect(mockHost.requestUpdate).toHaveBeenCalledWith("ownCurrentTimeMs");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { EFMedia } from "../../EFMedia";
|
|
3
|
+
import { AssetIdMediaEngine } from "../AssetIdMediaEngine";
|
|
4
|
+
import { AssetMediaEngine } from "../AssetMediaEngine";
|
|
5
|
+
import { JitMediaEngine } from "../JitMediaEngine";
|
|
6
|
+
import { createMediaEngine } from "./makeMediaEngineTask";
|
|
7
|
+
|
|
8
|
+
// Mock the engine classes
|
|
9
|
+
vi.mock("../AssetIdMediaEngine", () => ({
|
|
10
|
+
AssetIdMediaEngine: {
|
|
11
|
+
fetchByAssetId: vi.fn(),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
vi.mock("../AssetMediaEngine");
|
|
15
|
+
vi.mock("../JitMediaEngine");
|
|
16
|
+
|
|
17
|
+
describe("makeMediaEngineTask", () => {
|
|
18
|
+
const mockUrlGenerator = {
|
|
19
|
+
generateManifestUrl: vi.fn(),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const createMockHost = (overrides: Partial<EFMedia> = {}): EFMedia =>
|
|
23
|
+
({
|
|
24
|
+
src: "",
|
|
25
|
+
assetId: null,
|
|
26
|
+
apiHost: "https://api.example.com",
|
|
27
|
+
urlGenerator: mockUrlGenerator,
|
|
28
|
+
...overrides,
|
|
29
|
+
}) as EFMedia;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("createMediaEngine", () => {
|
|
36
|
+
it("should use AssetIdMediaEngine when assetId is provided", async () => {
|
|
37
|
+
const mockEngine = { durationMs: 15000 };
|
|
38
|
+
vi.mocked(AssetIdMediaEngine.fetchByAssetId).mockResolvedValue(
|
|
39
|
+
mockEngine as any,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const host = createMockHost({
|
|
43
|
+
assetId: "test-asset-123",
|
|
44
|
+
apiHost: "https://api.example.com",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const result = await createMediaEngine(host);
|
|
48
|
+
|
|
49
|
+
expect(AssetIdMediaEngine.fetchByAssetId).toHaveBeenCalledWith(
|
|
50
|
+
host,
|
|
51
|
+
mockUrlGenerator,
|
|
52
|
+
"test-asset-123",
|
|
53
|
+
"https://api.example.com",
|
|
54
|
+
);
|
|
55
|
+
expect(result).toBe(mockEngine);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should throw error when assetId is provided but apiHost is missing", async () => {
|
|
59
|
+
const host = createMockHost({
|
|
60
|
+
assetId: "test-asset-123",
|
|
61
|
+
apiHost: undefined,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await expect(createMediaEngine(host)).rejects.toThrow(
|
|
65
|
+
"API host is required for AssetID mode",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should ignore empty assetId and use src instead", async () => {
|
|
70
|
+
const mockEngine = { durationMs: 15000 };
|
|
71
|
+
vi.mocked(AssetMediaEngine.fetch).mockResolvedValue(mockEngine as any);
|
|
72
|
+
|
|
73
|
+
const host = createMockHost({
|
|
74
|
+
assetId: "",
|
|
75
|
+
src: "/path/to/asset",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const result = await createMediaEngine(host);
|
|
79
|
+
|
|
80
|
+
expect(AssetMediaEngine.fetch).toHaveBeenCalledWith(
|
|
81
|
+
host,
|
|
82
|
+
mockUrlGenerator,
|
|
83
|
+
"/path/to/asset",
|
|
84
|
+
);
|
|
85
|
+
expect(AssetIdMediaEngine.fetchByAssetId).not.toHaveBeenCalled();
|
|
86
|
+
expect(result).toBe(mockEngine);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should ignore whitespace-only assetId and use src instead", async () => {
|
|
90
|
+
const mockEngine = { durationMs: 15000 };
|
|
91
|
+
vi.mocked(AssetMediaEngine.fetch).mockResolvedValue(mockEngine as any);
|
|
92
|
+
|
|
93
|
+
const host = createMockHost({
|
|
94
|
+
assetId: " ",
|
|
95
|
+
src: "/path/to/asset",
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = await createMediaEngine(host);
|
|
99
|
+
|
|
100
|
+
expect(AssetMediaEngine.fetch).toHaveBeenCalledWith(
|
|
101
|
+
host,
|
|
102
|
+
mockUrlGenerator,
|
|
103
|
+
"/path/to/asset",
|
|
104
|
+
);
|
|
105
|
+
expect(AssetIdMediaEngine.fetchByAssetId).not.toHaveBeenCalled();
|
|
106
|
+
expect(result).toBe(mockEngine);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should use JitMediaEngine for HTTP URLs", async () => {
|
|
110
|
+
const mockEngine = { durationMs: 15000 };
|
|
111
|
+
const manifestUrl = "https://api.example.com/manifest.json";
|
|
112
|
+
|
|
113
|
+
vi.mocked(mockUrlGenerator.generateManifestUrl).mockReturnValue(
|
|
114
|
+
manifestUrl,
|
|
115
|
+
);
|
|
116
|
+
vi.mocked(JitMediaEngine.fetch).mockResolvedValue(mockEngine as any);
|
|
117
|
+
|
|
118
|
+
const host = createMockHost({
|
|
119
|
+
src: "https://example.com/video.mp4",
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const result = await createMediaEngine(host);
|
|
123
|
+
|
|
124
|
+
expect(mockUrlGenerator.generateManifestUrl).toHaveBeenCalledWith(
|
|
125
|
+
"https://example.com/video.mp4",
|
|
126
|
+
);
|
|
127
|
+
expect(JitMediaEngine.fetch).toHaveBeenCalledWith(
|
|
128
|
+
host,
|
|
129
|
+
mockUrlGenerator,
|
|
130
|
+
manifestUrl,
|
|
131
|
+
);
|
|
132
|
+
expect(result).toBe(mockEngine);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should use JitMediaEngine for HTTPS URLs", async () => {
|
|
136
|
+
const mockEngine = { durationMs: 15000 };
|
|
137
|
+
const manifestUrl = "https://api.example.com/manifest.json";
|
|
138
|
+
|
|
139
|
+
vi.mocked(mockUrlGenerator.generateManifestUrl).mockReturnValue(
|
|
140
|
+
manifestUrl,
|
|
141
|
+
);
|
|
142
|
+
vi.mocked(JitMediaEngine.fetch).mockResolvedValue(mockEngine as any);
|
|
143
|
+
|
|
144
|
+
const host = createMockHost({
|
|
145
|
+
src: "https://example.com/video.mp4",
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const result = await createMediaEngine(host);
|
|
149
|
+
|
|
150
|
+
expect(JitMediaEngine.fetch).toHaveBeenCalledWith(
|
|
151
|
+
host,
|
|
152
|
+
mockUrlGenerator,
|
|
153
|
+
manifestUrl,
|
|
154
|
+
);
|
|
155
|
+
expect(result).toBe(mockEngine);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should use AssetMediaEngine for local paths", async () => {
|
|
159
|
+
const mockEngine = { durationMs: 15000 };
|
|
160
|
+
vi.mocked(AssetMediaEngine.fetch).mockResolvedValue(mockEngine as any);
|
|
161
|
+
|
|
162
|
+
const host = createMockHost({
|
|
163
|
+
src: "/local/asset.mp4",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const result = await createMediaEngine(host);
|
|
167
|
+
|
|
168
|
+
expect(AssetMediaEngine.fetch).toHaveBeenCalledWith(
|
|
169
|
+
host,
|
|
170
|
+
mockUrlGenerator,
|
|
171
|
+
"/local/asset.mp4",
|
|
172
|
+
);
|
|
173
|
+
expect(result).toBe(mockEngine);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should throw error for empty src when no assetId", async () => {
|
|
177
|
+
const host = createMockHost({
|
|
178
|
+
src: "",
|
|
179
|
+
assetId: null,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await expect(createMediaEngine(host)).rejects.toThrow(
|
|
183
|
+
"Unsupported media source",
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should throw error for whitespace-only src when no assetId", async () => {
|
|
188
|
+
const host = createMockHost({
|
|
189
|
+
src: " ",
|
|
190
|
+
assetId: null,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await expect(createMediaEngine(host)).rejects.toThrow(
|
|
194
|
+
"Unsupported media source",
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should throw error for null src when no assetId", async () => {
|
|
199
|
+
const host = createMockHost({
|
|
200
|
+
src: null as any,
|
|
201
|
+
assetId: null,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await expect(createMediaEngine(host)).rejects.toThrow(
|
|
205
|
+
"Unsupported media source",
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should prioritize assetId over src when both are provided", async () => {
|
|
210
|
+
const mockEngine = { durationMs: 15000 };
|
|
211
|
+
vi.mocked(AssetIdMediaEngine.fetchByAssetId).mockResolvedValue(
|
|
212
|
+
mockEngine as any,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const host = createMockHost({
|
|
216
|
+
assetId: "test-asset-123",
|
|
217
|
+
src: "/local/asset.mp4",
|
|
218
|
+
apiHost: "https://api.example.com",
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const result = await createMediaEngine(host);
|
|
222
|
+
|
|
223
|
+
expect(AssetIdMediaEngine.fetchByAssetId).toHaveBeenCalledWith(
|
|
224
|
+
host,
|
|
225
|
+
mockUrlGenerator,
|
|
226
|
+
"test-asset-123",
|
|
227
|
+
"https://api.example.com",
|
|
228
|
+
);
|
|
229
|
+
expect(AssetMediaEngine.fetch).not.toHaveBeenCalled();
|
|
230
|
+
expect(result).toBe(mockEngine);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|