@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,89 @@
1
+ import { Task } from "@lit/task";
2
+ import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE";
3
+ import type { MediaEngine, VideoRendition } from "../../../transcoding/types";
4
+ import type { EFMedia } from "../../EFMedia";
5
+ import { AssetIdMediaEngine } from "../AssetIdMediaEngine";
6
+ import { AssetMediaEngine } from "../AssetMediaEngine";
7
+ import { JitMediaEngine } from "../JitMediaEngine";
8
+
9
+ export const getLatestMediaEngine = async (
10
+ host: EFMedia,
11
+ signal: AbortSignal,
12
+ ): Promise<MediaEngine> => {
13
+ const mediaEngine = await host.mediaEngineTask.taskComplete;
14
+ signal.throwIfAborted();
15
+ if (!mediaEngine) {
16
+ throw new Error("Media engine is not available");
17
+ }
18
+ return mediaEngine;
19
+ };
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
+ * Core logic for creating a MediaEngine with explicit dependencies.
31
+ * Pure function that requires all dependencies to be provided.
32
+ */
33
+ export const createMediaEngine = (host: EFMedia): Promise<MediaEngine> => {
34
+ const { src, assetId, urlGenerator, apiHost } = host;
35
+
36
+ // Check for AssetID mode first
37
+ if (assetId !== null && assetId !== undefined && assetId.trim() !== "") {
38
+ if (!apiHost) {
39
+ return Promise.reject(new Error("API host is required for AssetID mode"));
40
+ }
41
+ return AssetIdMediaEngine.fetchByAssetId(
42
+ host,
43
+ urlGenerator,
44
+ assetId,
45
+ apiHost,
46
+ );
47
+ }
48
+
49
+ // Check for null/undefined/empty/whitespace src
50
+ if (!src || typeof src !== "string" || src.trim() === "") {
51
+ console.error(`Unsupported media source: ${src}, assetId: ${assetId}`);
52
+ return Promise.reject(new Error("Unsupported media source"));
53
+ }
54
+
55
+ // Check for HTTP/HTTPS URLs (exactly "http://" or "https://")
56
+ const lowerSrc = src.toLowerCase();
57
+ if (lowerSrc.startsWith("http://") || lowerSrc.startsWith("https://")) {
58
+ const url = urlGenerator.generateManifestUrl(src);
59
+ return JitMediaEngine.fetch(host, urlGenerator, url);
60
+ }
61
+
62
+ return AssetMediaEngine.fetch(host, urlGenerator, src);
63
+ };
64
+
65
+ /**
66
+ * Handle completion of media engine task - triggers necessary updates.
67
+ * Extracted for testability.
68
+ */
69
+ export const handleMediaEngineComplete = (host: EFMedia): void => {
70
+ host.requestUpdate("intrinsicDurationMs");
71
+ host.requestUpdate("ownCurrentTimeMs");
72
+ host.rootTimegroup?.requestUpdate("ownCurrentTimeMs");
73
+ host.rootTimegroup?.requestUpdate("durationMs");
74
+ };
75
+
76
+ type MediaEngineTask = Task<readonly [string, string | null], MediaEngine>;
77
+
78
+ export const makeMediaEngineTask = (host: EFMedia): MediaEngineTask => {
79
+ return new Task(host, {
80
+ autoRun: EF_INTERACTIVE,
81
+ args: () => [host.src, host.assetId] as const,
82
+ task: async () => {
83
+ return createMediaEngine(host);
84
+ },
85
+ onComplete: (_value) => {
86
+ handleMediaEngineComplete(host);
87
+ },
88
+ });
89
+ };
@@ -0,0 +1,555 @@
1
+ import { TaskStatus } from "@lit/task";
2
+ import { customElement } from "lit/decorators.js";
3
+ import { afterEach, beforeEach, describe, vi } from "vitest";
4
+ import { test as baseTest } from "../../../../test/useMSW.js";
5
+ import type { VideoRendition } from "../../../transcoding/types";
6
+ import { EFVideo } from "../../EFVideo";
7
+ import {
8
+ computeBufferQueue,
9
+ computeSegmentRange,
10
+ handleSeekTimeChange,
11
+ type MediaBufferDependencies,
12
+ manageMediaBuffer,
13
+ } from "../shared/BufferUtils";
14
+ import {
15
+ makeVideoBufferTask,
16
+ type VideoBufferConfig,
17
+ type VideoBufferState,
18
+ } from "./makeVideoBufferTask";
19
+
20
+ @customElement("test-media-video-buffer")
21
+ class TestMediaVideoBuffer extends EFVideo {}
22
+
23
+ declare global {
24
+ interface HTMLElementTagNameMap {
25
+ "test-media-video-buffer": TestMediaVideoBuffer;
26
+ }
27
+ }
28
+
29
+ const test = baseTest.extend<{
30
+ element: TestMediaVideoBuffer;
31
+ mockVideoRendition: VideoRendition;
32
+ mockConfig: VideoBufferConfig;
33
+ mockState: VideoBufferState;
34
+ mockDeps: MediaBufferDependencies<VideoRendition>;
35
+ mockSignal: AbortSignal;
36
+ }>({
37
+ element: async ({}, use) => {
38
+ const element = document.createElement("test-media-video-buffer");
39
+ await use(element);
40
+ element.remove();
41
+ },
42
+
43
+ mockVideoRendition: async ({}, use) => {
44
+ const rendition = {
45
+ trackId: 1,
46
+ src: "test-video.mp4",
47
+ segmentDurationMs: 1000, // 1 second per segment
48
+ };
49
+ await use(rendition);
50
+ },
51
+
52
+ mockConfig: async ({}, use) => {
53
+ const config = {
54
+ bufferDurationMs: 5000, // 5 seconds
55
+ maxParallelFetches: 2,
56
+ enableBuffering: true,
57
+ enableContinuousBuffering: false, // Disable for predictable testing
58
+ };
59
+ await use(config);
60
+ },
61
+
62
+ mockState: async ({}, use) => {
63
+ const state = {
64
+ currentSeekTimeMs: 0,
65
+ activeRequests: new Set<number>(),
66
+ cachedSegments: new Set<number>(),
67
+ requestQueue: [],
68
+ };
69
+ await use(state);
70
+ },
71
+
72
+ mockDeps: async ({ mockVideoRendition }, use) => {
73
+ const deps = {
74
+ computeSegmentId: vi.fn(
75
+ async (timeMs: number, rendition: VideoRendition) => {
76
+ return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
77
+ },
78
+ ),
79
+ fetchSegment: vi.fn().mockResolvedValue(new ArrayBuffer(1024)),
80
+ getRendition: vi.fn().mockResolvedValue(mockVideoRendition),
81
+ logError: vi.fn(),
82
+ };
83
+ await use(deps);
84
+ },
85
+
86
+ mockSignal: async ({}, use) => {
87
+ const mockSignal = new AbortController().signal;
88
+ await use(mockSignal);
89
+ },
90
+ });
91
+
92
+ describe("computeSegmentRange", () => {
93
+ test("computes segment range for time range", ({
94
+ mockVideoRendition,
95
+ expect,
96
+ }) => {
97
+ const syncComputeSegmentId = vi.fn(
98
+ (timeMs: number, rendition: VideoRendition) => {
99
+ return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
100
+ },
101
+ );
102
+
103
+ const startTimeMs = 2000; // 2 seconds
104
+ const endTimeMs = 6000; // 6 seconds
105
+
106
+ const segments = computeSegmentRange(
107
+ startTimeMs,
108
+ endTimeMs,
109
+ mockVideoRendition,
110
+ syncComputeSegmentId,
111
+ );
112
+
113
+ expect(segments).toEqual([2, 3, 4, 5, 6]);
114
+ expect(syncComputeSegmentId).toHaveBeenCalledWith(
115
+ startTimeMs,
116
+ mockVideoRendition,
117
+ );
118
+ expect(syncComputeSegmentId).toHaveBeenCalledWith(
119
+ endTimeMs,
120
+ mockVideoRendition,
121
+ );
122
+ });
123
+
124
+ test("handles zero start time", ({ mockVideoRendition, expect }) => {
125
+ const syncComputeSegmentId = (
126
+ timeMs: number,
127
+ rendition: VideoRendition,
128
+ ) => {
129
+ return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
130
+ };
131
+
132
+ const segments = computeSegmentRange(
133
+ 0,
134
+ 3000,
135
+ mockVideoRendition,
136
+ syncComputeSegmentId,
137
+ );
138
+ expect(segments).toEqual([0, 1, 2, 3]);
139
+ });
140
+
141
+ test("handles single segment range", ({ mockVideoRendition, expect }) => {
142
+ const syncComputeSegmentId = (
143
+ timeMs: number,
144
+ rendition: VideoRendition,
145
+ ) => {
146
+ return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
147
+ };
148
+
149
+ const segments = computeSegmentRange(
150
+ 2500,
151
+ 2800,
152
+ mockVideoRendition,
153
+ syncComputeSegmentId,
154
+ );
155
+ expect(segments).toEqual([2]);
156
+ });
157
+
158
+ test("uses default segment duration when missing", ({ expect }) => {
159
+ const syncComputeSegmentId = (
160
+ timeMs: number,
161
+ rendition: VideoRendition,
162
+ ) => {
163
+ return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
164
+ };
165
+ const renditionWithoutDuration = { trackId: 1, src: "test" };
166
+
167
+ const segments = computeSegmentRange(
168
+ 2000,
169
+ 5000,
170
+ renditionWithoutDuration as any,
171
+ syncComputeSegmentId,
172
+ );
173
+
174
+ // Should use default 1000ms duration and call syncComputeSegmentId for segments 2, 3, 4, 5
175
+ // Since syncComputeSegmentId always returns Math.floor(timeMs / 1000), result is [2, 3, 4, 5]
176
+ expect(segments).toEqual([2, 3, 4, 5]);
177
+ });
178
+
179
+ test("returns empty array when computeSegmentId returns undefined", ({
180
+ mockVideoRendition,
181
+ expect,
182
+ }) => {
183
+ const computeSegmentId = vi.fn().mockReturnValue(undefined);
184
+
185
+ const segments = computeSegmentRange(
186
+ 0,
187
+ 1000,
188
+ mockVideoRendition,
189
+ computeSegmentId,
190
+ );
191
+ expect(segments).toEqual([]);
192
+ });
193
+ });
194
+
195
+ describe("computeBufferQueue", () => {
196
+ test("filters out active requests and cached segments", ({ expect }) => {
197
+ const desiredSegments = [1, 2, 3, 4, 5];
198
+ const activeRequests = new Set([2, 4]);
199
+ const cachedSegments = new Set([1]);
200
+
201
+ const queue = computeBufferQueue(
202
+ desiredSegments,
203
+ activeRequests,
204
+ cachedSegments,
205
+ );
206
+ expect(queue).toEqual([3, 5]);
207
+ });
208
+
209
+ test("returns empty queue when all segments are active or cached", ({
210
+ expect,
211
+ }) => {
212
+ const desiredSegments = [1, 2, 3];
213
+ const activeRequests = new Set([1, 2]);
214
+ const cachedSegments = new Set([3]);
215
+
216
+ const queue = computeBufferQueue(
217
+ desiredSegments,
218
+ activeRequests,
219
+ cachedSegments,
220
+ );
221
+ expect(queue).toEqual([]);
222
+ });
223
+
224
+ test("returns all segments when none are active or cached", ({ expect }) => {
225
+ const desiredSegments = [1, 2, 3, 4];
226
+ const activeRequests = new Set<number>();
227
+ const cachedSegments = new Set<number>();
228
+
229
+ const queue = computeBufferQueue(
230
+ desiredSegments,
231
+ activeRequests,
232
+ cachedSegments,
233
+ );
234
+ expect(queue).toEqual([1, 2, 3, 4]);
235
+ });
236
+ });
237
+
238
+ describe("handleSeekTimeChange", () => {
239
+ test("computes new queue and finds overlapping requests", ({
240
+ mockVideoRendition,
241
+ expect,
242
+ }) => {
243
+ // Use sync version with sync computeSegmentId for this test
244
+ const syncComputeSegmentId = (
245
+ timeMs: number,
246
+ rendition: VideoRendition,
247
+ ) => {
248
+ return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
249
+ };
250
+
251
+ const currentState = {
252
+ currentSeekTimeMs: 1000,
253
+ activeRequests: new Set([2, 3]),
254
+ cachedSegments: new Set([1]),
255
+ requestQueue: [4, 5],
256
+ };
257
+
258
+ const result = handleSeekTimeChange(
259
+ 3000, // new seek time
260
+ 3000, // buffer 3 seconds
261
+ mockVideoRendition,
262
+ currentState,
263
+ syncComputeSegmentId,
264
+ );
265
+
266
+ expect(result.newQueue).toEqual([4, 5, 6]); // segments 3 is active, 4,5,6 needed
267
+ expect(result.overlappingRequests).toEqual([3]); // segment 3 is already being fetched
268
+ });
269
+
270
+ test("handles seek time change with no overlap", ({
271
+ mockVideoRendition,
272
+ expect,
273
+ }) => {
274
+ const syncComputeSegmentId = (
275
+ timeMs: number,
276
+ rendition: VideoRendition,
277
+ ) => {
278
+ return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
279
+ };
280
+
281
+ const currentState = {
282
+ currentSeekTimeMs: 1000,
283
+ activeRequests: new Set([1, 2]),
284
+ cachedSegments: new Set<number>(),
285
+ requestQueue: [],
286
+ };
287
+
288
+ const result = handleSeekTimeChange(
289
+ 10000, // seek far ahead
290
+ 2000, // buffer 2 seconds
291
+ mockVideoRendition,
292
+ currentState,
293
+ syncComputeSegmentId,
294
+ );
295
+
296
+ expect(result.newQueue).toEqual([10, 11, 12]); // completely new segments
297
+ expect(result.overlappingRequests).toEqual([]); // no overlap
298
+ });
299
+
300
+ test("handles backwards seek", ({ mockVideoRendition, expect }) => {
301
+ const syncComputeSegmentId = (
302
+ timeMs: number,
303
+ rendition: VideoRendition,
304
+ ) => {
305
+ return Math.floor(timeMs / (rendition.segmentDurationMs || 1000));
306
+ };
307
+
308
+ const currentState = {
309
+ currentSeekTimeMs: 5000,
310
+ activeRequests: new Set([6, 7]),
311
+ cachedSegments: new Set([5]),
312
+ requestQueue: [],
313
+ };
314
+
315
+ const result = handleSeekTimeChange(
316
+ 2000, // seek backwards
317
+ 3000, // buffer 3 seconds
318
+ mockVideoRendition,
319
+ currentState,
320
+ syncComputeSegmentId,
321
+ );
322
+
323
+ expect(result.newQueue).toEqual([2, 3, 4]); // segments 2,3,4,5 needed, 5 cached
324
+ expect(result.overlappingRequests).toEqual([]); // no overlap with active requests
325
+ });
326
+ });
327
+
328
+ describe("manageMediaBuffer (Video)", () => {
329
+ test("manages buffer state successfully", async ({
330
+ mockConfig,
331
+ mockState,
332
+ mockDeps,
333
+ mockSignal,
334
+ expect,
335
+ }) => {
336
+ const seekTimeMs = 3000;
337
+
338
+ const newState = await manageMediaBuffer(
339
+ seekTimeMs,
340
+ mockConfig,
341
+ mockState,
342
+ 10000, // durationMs
343
+ mockSignal,
344
+ mockDeps,
345
+ );
346
+
347
+ expect(newState.currentSeekTimeMs).toBe(seekTimeMs);
348
+ expect(mockDeps.getRendition).toHaveBeenCalled();
349
+ expect(mockDeps.fetchSegment).toHaveBeenCalledTimes(2); // maxParallelFetches = 2
350
+ });
351
+
352
+ test("respects maxParallelFetches limit", async ({
353
+ mockState,
354
+ mockDeps,
355
+ mockSignal,
356
+ expect,
357
+ }) => {
358
+ const config = {
359
+ bufferDurationMs: 10000, // 10 seconds = 10 segments
360
+ maxParallelFetches: 3,
361
+ enableBuffering: true,
362
+ enableContinuousBuffering: false, // Disable for predictable testing
363
+ };
364
+
365
+ await manageMediaBuffer(
366
+ 0,
367
+ config,
368
+ mockState,
369
+ 10000, // durationMs
370
+ mockSignal,
371
+ mockDeps,
372
+ );
373
+
374
+ expect(mockDeps.fetchSegment).toHaveBeenCalledTimes(3); // Should only fetch 3 despite needing 10
375
+ });
376
+
377
+ test("does nothing when buffering disabled", async ({
378
+ mockState,
379
+ mockDeps,
380
+ mockSignal,
381
+ expect,
382
+ }) => {
383
+ const config = {
384
+ bufferDurationMs: 5000,
385
+ maxParallelFetches: 2,
386
+ enableBuffering: false,
387
+ };
388
+
389
+ const newState = await manageMediaBuffer(
390
+ 1000,
391
+ config,
392
+ mockState,
393
+ 10000, // durationMs
394
+ mockSignal,
395
+ mockDeps,
396
+ );
397
+
398
+ expect(newState).toBe(mockState); // Should return same state
399
+ expect(mockDeps.fetchSegment).not.toHaveBeenCalled();
400
+ });
401
+
402
+ test("skips segments already in active requests", async ({
403
+ mockConfig,
404
+ mockDeps,
405
+ mockSignal,
406
+ expect,
407
+ }) => {
408
+ const stateWithActiveRequests = {
409
+ currentSeekTimeMs: 0,
410
+ activeRequests: new Set([1, 2]), // segments 1,2 already being fetched
411
+ cachedSegments: new Set<number>(),
412
+ requestQueue: [],
413
+ };
414
+
415
+ await manageMediaBuffer(
416
+ 1000, // seeks to time that would want segments 1,2,3,4,5
417
+ mockConfig,
418
+ stateWithActiveRequests,
419
+ 10000, // durationMs
420
+ mockSignal,
421
+ mockDeps,
422
+ );
423
+
424
+ // Should only fetch segments 3,4 (maxParallelFetches=2, skipping 1,2)
425
+ expect(mockDeps.fetchSegment).toHaveBeenCalledWith(3, expect.any(Object));
426
+ expect(mockDeps.fetchSegment).toHaveBeenCalledWith(4, expect.any(Object));
427
+ expect(mockDeps.fetchSegment).toHaveBeenCalledTimes(2);
428
+ });
429
+
430
+ test("skips segments already cached", async ({
431
+ mockConfig,
432
+ mockDeps,
433
+ mockSignal,
434
+ expect,
435
+ }) => {
436
+ const stateWithCache = {
437
+ currentSeekTimeMs: 0,
438
+ activeRequests: new Set<number>(),
439
+ cachedSegments: new Set([1, 3]), // segments 1,3 already cached
440
+ requestQueue: [],
441
+ };
442
+
443
+ await manageMediaBuffer(
444
+ 1000, // seeks to time that would want segments 1,2,3,4,5
445
+ mockConfig,
446
+ stateWithCache,
447
+ 10000, // durationMs
448
+ mockSignal,
449
+ mockDeps,
450
+ );
451
+
452
+ // Should only fetch segments 2,4 (skipping cached 1,3)
453
+ expect(mockDeps.fetchSegment).toHaveBeenCalledWith(2, expect.any(Object));
454
+ expect(mockDeps.fetchSegment).toHaveBeenCalledWith(4, expect.any(Object));
455
+ expect(mockDeps.fetchSegment).toHaveBeenCalledTimes(2);
456
+ });
457
+ });
458
+
459
+ describe("makeVideoBufferTask", () => {
460
+ beforeEach(() => {
461
+ // MSW setup is now handled by test fixtures
462
+ });
463
+
464
+ afterEach(() => {
465
+ const elements = document.querySelectorAll("test-media-video-buffer");
466
+ for (const element of elements) {
467
+ element.remove();
468
+ }
469
+ vi.restoreAllMocks();
470
+ });
471
+
472
+ test("creates task with correct configuration", ({ element, expect }) => {
473
+ const task = makeVideoBufferTask(element);
474
+
475
+ expect(task).toBeDefined();
476
+ expect(task.status).toBe(TaskStatus.INITIAL);
477
+ expect(task.value).toBeUndefined();
478
+ expect(task.error).toBeUndefined();
479
+ });
480
+
481
+ test("task integrates with element seek time", ({ element, expect }) => {
482
+ element.desiredSeekTimeMs = 5000;
483
+
484
+ const task = makeVideoBufferTask(element);
485
+ expect(task).toBeDefined();
486
+ expect(task.status).toBe(TaskStatus.INITIAL);
487
+ });
488
+ });
489
+
490
+ describe("Continuous Buffering", () => {
491
+ test("enables continuous segment loading when enabled", async ({
492
+ mockState,
493
+ mockDeps,
494
+ mockSignal,
495
+ expect,
496
+ }) => {
497
+ const configWithContinuous = {
498
+ bufferDurationMs: 10000, // 10 seconds = 10 segments
499
+ maxParallelFetches: 2,
500
+ enableBuffering: true,
501
+ enableContinuousBuffering: true, // Enable continuous buffering
502
+ };
503
+
504
+ let fetchCount = 0;
505
+ const mockFetchWithDelay = vi.fn().mockImplementation(() => {
506
+ fetchCount++;
507
+ return Promise.resolve(new ArrayBuffer(1000));
508
+ });
509
+
510
+ const mockDepsWithContinuous = {
511
+ ...mockDeps,
512
+ fetchSegment: mockFetchWithDelay,
513
+ };
514
+
515
+ await manageMediaBuffer(
516
+ 0,
517
+ configWithContinuous,
518
+ mockState,
519
+ 10000, // durationMs
520
+ mockSignal,
521
+ mockDepsWithContinuous,
522
+ );
523
+
524
+ // Should start with initial maxParallelFetches (2) and continue with more requests
525
+ // Continuous buffering should fetch more segments as previous ones complete
526
+ expect(mockFetchWithDelay).toHaveBeenCalledTimes(4); // More than initial batch due to continuous buffering
527
+ expect(fetchCount).toBe(4);
528
+ });
529
+
530
+ test("disabled when flag is false", async ({
531
+ mockState,
532
+ mockDeps,
533
+ mockSignal,
534
+ expect,
535
+ }) => {
536
+ const configWithoutContinuous = {
537
+ bufferDurationMs: 10000, // 10 seconds = 10 segments
538
+ maxParallelFetches: 2,
539
+ enableBuffering: true,
540
+ enableContinuousBuffering: false, // Disable continuous buffering
541
+ };
542
+
543
+ await manageMediaBuffer(
544
+ 0,
545
+ configWithoutContinuous,
546
+ mockState,
547
+ 10000, // durationMs
548
+ mockSignal,
549
+ mockDeps,
550
+ );
551
+
552
+ // Should only fetch initial maxParallelFetches and stop
553
+ expect(mockDeps.fetchSegment).toHaveBeenCalledTimes(2);
554
+ });
555
+ });