@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.
Files changed (218) hide show
  1. package/dist/EF_FRAMEGEN.js +1 -1
  2. package/dist/ScrubTrackManager.d.ts +2 -2
  3. package/dist/elements/EFAudio.d.ts +21 -2
  4. package/dist/elements/EFAudio.js +41 -11
  5. package/dist/elements/EFImage.d.ts +1 -0
  6. package/dist/elements/EFImage.js +11 -3
  7. package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +18 -0
  8. package/dist/elements/EFMedia/AssetIdMediaEngine.js +41 -0
  9. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +47 -0
  10. package/dist/elements/EFMedia/AssetMediaEngine.js +116 -0
  11. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +55 -0
  12. package/dist/elements/EFMedia/BaseMediaEngine.js +96 -0
  13. package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +43 -0
  14. package/dist/elements/EFMedia/BufferedSeekingInput.js +159 -0
  15. package/dist/elements/EFMedia/JitMediaEngine.browsertest.d.ts +0 -0
  16. package/dist/elements/EFMedia/JitMediaEngine.d.ts +31 -0
  17. package/dist/elements/EFMedia/JitMediaEngine.js +62 -0
  18. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.d.ts +9 -0
  19. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +16 -0
  20. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +48 -0
  21. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +3 -0
  22. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +138 -0
  23. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.d.ts +9 -0
  24. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +4 -0
  25. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +16 -0
  26. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.d.ts +9 -0
  27. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.d.ts +3 -0
  28. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +22 -0
  29. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +7 -0
  30. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +24 -0
  31. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +4 -0
  32. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +18 -0
  33. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +4 -0
  34. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +16 -0
  35. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +3 -0
  36. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +104 -0
  37. package/dist/elements/EFMedia/services/AudioElementFactory.d.ts +22 -0
  38. package/dist/elements/EFMedia/services/AudioElementFactory.js +72 -0
  39. package/dist/elements/EFMedia/services/MediaSourceService.browsertest.d.ts +1 -0
  40. package/dist/elements/EFMedia/services/MediaSourceService.d.ts +47 -0
  41. package/dist/elements/EFMedia/services/MediaSourceService.js +73 -0
  42. package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +7 -0
  43. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +54 -0
  44. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +70 -0
  45. package/dist/elements/EFMedia/shared/BufferUtils.js +89 -0
  46. package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +23 -0
  47. package/dist/elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts +1 -0
  48. package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +19 -0
  49. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +1 -0
  50. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +18 -0
  51. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +60 -0
  52. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +1 -0
  53. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +9 -0
  54. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +16 -0
  55. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +46 -0
  56. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +9 -0
  57. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.d.ts +4 -0
  58. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +16 -0
  59. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +9 -0
  60. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.d.ts +3 -0
  61. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +27 -0
  62. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +7 -0
  63. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +25 -0
  64. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +9 -0
  65. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +4 -0
  66. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +18 -0
  67. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +9 -0
  68. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +4 -0
  69. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +16 -0
  70. package/dist/elements/EFMedia.browsertest.d.ts +1 -0
  71. package/dist/elements/EFMedia.d.ts +75 -111
  72. package/dist/elements/EFMedia.js +141 -1111
  73. package/dist/elements/EFTemporal.d.ts +1 -1
  74. package/dist/elements/EFTemporal.js +1 -1
  75. package/dist/elements/EFTimegroup.d.ts +11 -0
  76. package/dist/elements/EFTimegroup.js +88 -13
  77. package/dist/elements/EFVideo.d.ts +60 -29
  78. package/dist/elements/EFVideo.js +103 -203
  79. package/dist/elements/EFWaveform.js +2 -2
  80. package/dist/elements/SampleBuffer.d.ts +14 -0
  81. package/dist/elements/SampleBuffer.js +52 -0
  82. package/dist/getRenderInfo.d.ts +2 -2
  83. package/dist/getRenderInfo.js +2 -1
  84. package/dist/gui/ContextMixin.js +17 -70
  85. package/dist/gui/EFFilmstrip.d.ts +3 -3
  86. package/dist/gui/EFFilmstrip.js +1 -1
  87. package/dist/gui/EFFitScale.d.ts +2 -2
  88. package/dist/gui/TWMixin.js +1 -1
  89. package/dist/gui/services/ElementConnectionManager.browsertest.d.ts +1 -0
  90. package/dist/gui/services/ElementConnectionManager.d.ts +59 -0
  91. package/dist/gui/services/ElementConnectionManager.js +128 -0
  92. package/dist/gui/services/PlaybackController.browsertest.d.ts +1 -0
  93. package/dist/gui/services/PlaybackController.d.ts +103 -0
  94. package/dist/gui/services/PlaybackController.js +290 -0
  95. package/dist/services/MediaSourceManager.d.ts +62 -0
  96. package/dist/services/MediaSourceManager.js +211 -0
  97. package/dist/style.css +1 -1
  98. package/dist/transcoding/cache/CacheManager.d.ts +73 -0
  99. package/dist/transcoding/cache/RequestDeduplicator.d.ts +29 -0
  100. package/dist/transcoding/cache/RequestDeduplicator.js +53 -0
  101. package/dist/transcoding/cache/RequestDeduplicator.test.d.ts +1 -0
  102. package/dist/transcoding/types/index.d.ts +242 -0
  103. package/dist/transcoding/utils/MediaUtils.d.ts +9 -0
  104. package/dist/transcoding/utils/UrlGenerator.d.ts +26 -0
  105. package/dist/transcoding/utils/UrlGenerator.js +45 -0
  106. package/dist/transcoding/utils/constants.d.ts +27 -0
  107. package/dist/utils/LRUCache.d.ts +34 -0
  108. package/dist/utils/LRUCache.js +115 -0
  109. package/package.json +3 -2
  110. package/src/elements/EFAudio.browsertest.ts +183 -43
  111. package/src/elements/EFAudio.ts +59 -13
  112. package/src/elements/EFImage.browsertest.ts +42 -0
  113. package/src/elements/EFImage.ts +23 -3
  114. package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +222 -0
  115. package/src/elements/EFMedia/AssetIdMediaEngine.ts +70 -0
  116. package/src/elements/EFMedia/AssetMediaEngine.ts +210 -0
  117. package/src/elements/EFMedia/BaseMediaEngine.test.ts +164 -0
  118. package/src/elements/EFMedia/BaseMediaEngine.ts +170 -0
  119. package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +400 -0
  120. package/src/elements/EFMedia/BufferedSeekingInput.ts +267 -0
  121. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +165 -0
  122. package/src/elements/EFMedia/JitMediaEngine.ts +110 -0
  123. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +554 -0
  124. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +81 -0
  125. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +241 -0
  126. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +59 -0
  127. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +23 -0
  128. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +55 -0
  129. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +35 -0
  130. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +42 -0
  131. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +34 -0
  132. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +23 -0
  133. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +174 -0
  134. package/src/elements/EFMedia/services/AudioElementFactory.browsertest.ts +325 -0
  135. package/src/elements/EFMedia/services/AudioElementFactory.ts +119 -0
  136. package/src/elements/EFMedia/services/MediaSourceService.browsertest.ts +257 -0
  137. package/src/elements/EFMedia/services/MediaSourceService.ts +102 -0
  138. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +128 -0
  139. package/src/elements/EFMedia/shared/BufferUtils.ts +310 -0
  140. package/src/elements/EFMedia/shared/MediaTaskUtils.ts +44 -0
  141. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +247 -0
  142. package/src/elements/EFMedia/shared/RenditionHelpers.ts +79 -0
  143. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +128 -0
  144. package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +233 -0
  145. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +89 -0
  146. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +555 -0
  147. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +79 -0
  148. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +59 -0
  149. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts +23 -0
  150. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +55 -0
  151. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.ts +45 -0
  152. package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +44 -0
  153. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +57 -0
  154. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +32 -0
  155. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +56 -0
  156. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +23 -0
  157. package/src/elements/EFMedia.browsertest.ts +658 -265
  158. package/src/elements/EFMedia.ts +173 -1763
  159. package/src/elements/EFTemporal.ts +3 -4
  160. package/src/elements/EFTimegroup.browsertest.ts +6 -3
  161. package/src/elements/EFTimegroup.ts +152 -21
  162. package/src/elements/EFVideo.browsertest.ts +115 -37
  163. package/src/elements/EFVideo.ts +123 -452
  164. package/src/elements/EFWaveform.ts +1 -1
  165. package/src/elements/MediaController.ts +2 -12
  166. package/src/elements/SampleBuffer.ts +97 -0
  167. package/src/gui/ContextMixin.ts +23 -104
  168. package/src/gui/services/ElementConnectionManager.browsertest.ts +263 -0
  169. package/src/gui/services/ElementConnectionManager.ts +224 -0
  170. package/src/gui/services/PlaybackController.browsertest.ts +437 -0
  171. package/src/gui/services/PlaybackController.ts +521 -0
  172. package/src/services/MediaSourceManager.ts +333 -0
  173. package/src/transcoding/cache/CacheManager.ts +208 -0
  174. package/src/transcoding/cache/RequestDeduplicator.test.ts +170 -0
  175. package/src/transcoding/cache/RequestDeduplicator.ts +65 -0
  176. package/src/transcoding/types/index.ts +265 -0
  177. package/src/transcoding/utils/MediaUtils.ts +63 -0
  178. package/src/transcoding/utils/UrlGenerator.ts +68 -0
  179. package/src/transcoding/utils/constants.ts +36 -0
  180. package/src/utils/LRUCache.ts +153 -0
  181. package/test/EFVideo.framegen.browsertest.ts +38 -29
  182. 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
  183. 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
  184. 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
  185. 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
  186. 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
  187. 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
  188. 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
  189. 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
  190. 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
  191. 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
  192. 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
  193. 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
  194. 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
  195. 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
  196. 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
  197. 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
  198. 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
  199. 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
  200. 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
  201. 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
  202. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -0
  203. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +19 -0
  204. package/test/createJitTestClips.ts +320 -188
  205. package/test/recordReplayProxyPlugin.js +302 -0
  206. package/test/useAssetMSW.ts +1 -1
  207. package/test/useMSW.ts +35 -22
  208. package/types.json +1 -1
  209. package/dist/JitTranscodingClient.d.ts +0 -167
  210. package/dist/JitTranscodingClient.js +0 -373
  211. package/dist/ScrubTrackManager.js +0 -216
  212. package/dist/elements/printTaskStatus.js +0 -11
  213. package/src/elements/__screenshots__/EFMedia.browsertest.ts/EFMedia-JIT-audio-playback-audioBufferTask-should-work-in-JIT-mode-without-URL-errors-1.png +0 -0
  214. package/test/EFVideo.frame-tasks.browsertest.ts +0 -524
  215. /package/dist/{JitTranscodingClient.browsertest.d.ts → elements/EFMedia/AssetIdMediaEngine.test.d.ts} +0 -0
  216. /package/dist/{JitTranscodingClient.test.d.ts → elements/EFMedia/BaseMediaEngine.test.d.ts} +0 -0
  217. /package/dist/{ScrubTrackIntegration.test.d.ts → elements/EFMedia/BufferedSeekingInput.browsertest.d.ts} +0 -0
  218. /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
+ });