@editframe/elements 0.18.22-beta.0 → 0.18.26-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 (99) hide show
  1. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +2 -1
  2. package/dist/elements/EFMedia/AssetMediaEngine.js +3 -0
  3. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +9 -0
  4. package/dist/elements/EFMedia/BaseMediaEngine.js +31 -0
  5. package/dist/elements/EFMedia/JitMediaEngine.d.ts +1 -0
  6. package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
  7. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +11 -5
  8. package/dist/elements/EFMedia/shared/BufferUtils.d.ts +19 -18
  9. package/dist/elements/EFMedia/shared/BufferUtils.js +24 -44
  10. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +8 -0
  11. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +5 -5
  12. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.d.ts +25 -0
  13. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +42 -0
  14. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.d.ts +8 -0
  15. package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +70 -0
  16. package/dist/elements/EFMedia/videoTasks/{makeVideoInitSegmentFetchTask.d.ts → makeScrubVideoInitSegmentFetchTask.d.ts} +1 -1
  17. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +21 -0
  18. package/dist/elements/EFMedia/videoTasks/{makeVideoInputTask.d.ts → makeScrubVideoInputTask.d.ts} +1 -1
  19. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +27 -0
  20. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.d.ts +6 -0
  21. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +52 -0
  22. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.d.ts +4 -0
  23. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +23 -0
  24. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.d.ts +4 -0
  25. package/dist/elements/EFMedia/videoTasks/{makeVideoSegmentIdTask.js → makeScrubVideoSegmentIdTask.js} +9 -4
  26. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.d.ts +6 -0
  27. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +112 -0
  28. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -5
  29. package/dist/elements/EFMedia.d.ts +0 -10
  30. package/dist/elements/EFMedia.js +1 -17
  31. package/dist/elements/EFVideo.d.ts +11 -9
  32. package/dist/elements/EFVideo.js +31 -23
  33. package/dist/gui/EFConfiguration.d.ts +1 -0
  34. package/dist/gui/EFConfiguration.js +5 -0
  35. package/dist/gui/EFFilmstrip.d.ts +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/transcoding/types/index.d.ts +11 -0
  38. package/package.json +2 -2
  39. package/src/elements/EFCaptions.ts +1 -1
  40. package/src/elements/EFImage.ts +1 -1
  41. package/src/elements/EFMedia/AssetMediaEngine.ts +6 -0
  42. package/src/elements/EFMedia/BaseMediaEngine.ts +60 -0
  43. package/src/elements/EFMedia/JitMediaEngine.ts +18 -0
  44. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +185 -59
  45. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +19 -6
  46. package/src/elements/EFMedia/shared/BufferUtils.ts +71 -85
  47. package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +151 -112
  48. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +12 -5
  49. package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +61 -0
  50. package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +113 -0
  51. package/src/elements/EFMedia/videoTasks/{makeVideoInitSegmentFetchTask.ts → makeScrubVideoInitSegmentFetchTask.ts} +15 -3
  52. package/src/elements/EFMedia/videoTasks/{makeVideoInputTask.ts → makeScrubVideoInputTask.ts} +11 -10
  53. package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +118 -0
  54. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +44 -0
  55. package/src/elements/EFMedia/videoTasks/{makeVideoSegmentIdTask.ts → makeScrubVideoSegmentIdTask.ts} +14 -6
  56. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +258 -0
  57. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +19 -5
  58. package/src/elements/EFMedia.browsertest.ts +74 -11
  59. package/src/elements/EFMedia.ts +1 -23
  60. package/src/elements/EFVideo.browsertest.ts +204 -80
  61. package/src/elements/EFVideo.ts +38 -26
  62. package/src/elements/TargetController.browsertest.ts +1 -1
  63. package/src/gui/EFConfiguration.ts +4 -1
  64. package/src/gui/EFFilmstrip.ts +4 -4
  65. package/src/gui/EFFocusOverlay.ts +1 -1
  66. package/src/gui/EFPreview.ts +3 -4
  67. package/src/gui/EFScrubber.ts +1 -1
  68. package/src/gui/EFTimeDisplay.ts +1 -1
  69. package/src/gui/EFToggleLoop.ts +1 -1
  70. package/src/gui/EFTogglePlay.ts +1 -1
  71. package/src/gui/EFWorkbench.ts +1 -1
  72. package/src/transcoding/types/index.ts +16 -0
  73. package/test/__cache__/GET__api_v1_transcode_scrub_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__6ff5127ebeda578a679474347fbd6137/data.bin +0 -0
  74. package/test/__cache__/GET__api_v1_transcode_scrub_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__6ff5127ebeda578a679474347fbd6137/metadata.json +16 -0
  75. package/test/__cache__/GET__api_v1_transcode_scrub_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__f6d4793fc9ff854ee9a738917fb64a53/data.bin +0 -0
  76. package/test/__cache__/GET__api_v1_transcode_scrub_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__f6d4793fc9ff854ee9a738917fb64a53/metadata.json +16 -0
  77. package/test/cache-integration-verification.browsertest.ts +84 -0
  78. package/types.json +1 -1
  79. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +0 -1
  80. package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +0 -9
  81. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +0 -9
  82. package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +0 -16
  83. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +0 -9
  84. package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +0 -27
  85. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +0 -7
  86. package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +0 -34
  87. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +0 -9
  88. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +0 -4
  89. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +0 -28
  90. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +0 -9
  91. package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +0 -4
  92. package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +0 -233
  93. package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +0 -555
  94. package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +0 -59
  95. package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +0 -55
  96. package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +0 -65
  97. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +0 -57
  98. package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +0 -43
  99. package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +0 -56
@@ -1,9 +0,0 @@
1
- import { EFVideo } from '../../EFVideo';
2
- declare class TestMediaVideoBuffer extends EFVideo {
3
- }
4
- declare global {
5
- interface HTMLElementTagNameMap {
6
- "test-media-video-buffer": TestMediaVideoBuffer;
7
- }
8
- }
9
- export {};
@@ -1,9 +0,0 @@
1
- import { EFVideo } from '../../EFVideo';
2
- declare class TestMediaVideoInitSegmentFetch extends EFVideo {
3
- }
4
- declare global {
5
- interface HTMLElementTagNameMap {
6
- "test-media-video-init-segment-fetch": TestMediaVideoInitSegmentFetch;
7
- }
8
- }
9
- export {};
@@ -1,16 +0,0 @@
1
- import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
- import { Task } from "@lit/task";
3
- const makeVideoInitSegmentFetchTask = (host) => {
4
- return new Task(host, {
5
- args: () => [host.mediaEngineTask.value],
6
- onError: (error) => {
7
- console.error("videoInitSegmentFetchTask error", error);
8
- },
9
- onComplete: (_value) => {},
10
- task: async ([_mediaEngine], { signal }) => {
11
- const mediaEngine = await getLatestMediaEngine(host, signal);
12
- return mediaEngine.fetchInitSegment(mediaEngine.getVideoRendition(), signal);
13
- }
14
- });
15
- };
16
- export { makeVideoInitSegmentFetchTask };
@@ -1,9 +0,0 @@
1
- import { EFVideo } from '../../EFVideo';
2
- declare class TestMediaVideoInput extends EFVideo {
3
- }
4
- declare global {
5
- interface HTMLElementTagNameMap {
6
- "test-media-video-input": TestMediaVideoInput;
7
- }
8
- }
9
- export {};
@@ -1,27 +0,0 @@
1
- import { BufferedSeekingInput } from "../BufferedSeekingInput.js";
2
- import { EFMedia } from "../../EFMedia.js";
3
- import { Task } from "@lit/task";
4
- const makeVideoInputTask = (host) => {
5
- return new Task(host, {
6
- args: () => [host.videoInitSegmentFetchTask.value, host.videoSegmentFetchTask.value],
7
- onError: (error) => {
8
- console.error("videoInputTask error", error);
9
- },
10
- onComplete: (_value) => {},
11
- task: async () => {
12
- const initSegment = await host.videoInitSegmentFetchTask.taskComplete;
13
- const segment = await host.videoSegmentFetchTask.taskComplete;
14
- if (!initSegment || !segment) throw new Error("Init segment or segment is not available");
15
- const mediaEngine = await host.mediaEngineTask.taskComplete;
16
- const videoRendition = mediaEngine?.videoRendition;
17
- const startTimeOffsetMs = videoRendition?.startTimeOffsetMs;
18
- const input = new BufferedSeekingInput(await new Blob([initSegment, segment]).arrayBuffer(), {
19
- videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
20
- audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
21
- startTimeOffsetMs
22
- });
23
- return input;
24
- }
25
- });
26
- };
27
- export { makeVideoInputTask };
@@ -1,7 +0,0 @@
1
- import { Task } from '@lit/task';
2
- import { VideoSample } from 'mediabunny';
3
- import { EFVideo } from '../../EFVideo';
4
- import { BufferedSeekingInput } from '../BufferedSeekingInput';
5
- type VideoSeekTask = Task<readonly [number, BufferedSeekingInput | undefined], VideoSample | undefined>;
6
- export declare const makeVideoSeekTask: (host: EFVideo) => VideoSeekTask;
7
- export {};
@@ -1,34 +0,0 @@
1
- import { IgnorableError } from "../../EFMedia.js";
2
- import { Task } from "@lit/task";
3
- const makeVideoSeekTask = (host) => {
4
- return new Task(host, {
5
- args: () => [host.desiredSeekTimeMs, host.videoInputTask.value],
6
- onError: (error) => {
7
- if (error instanceof IgnorableError) console.info("videoSeekTask aborted");
8
- console.error("videoSeekTask error", error);
9
- },
10
- onComplete: (_value) => {},
11
- task: async ([targetSeekTimeMs], { signal }) => {
12
- await host.mediaEngineTask.taskComplete;
13
- signal.throwIfAborted();
14
- await host.videoSegmentIdTask.taskComplete;
15
- signal.throwIfAborted();
16
- await host.videoSegmentFetchTask.taskComplete;
17
- signal.throwIfAborted();
18
- await host.videoInitSegmentFetchTask.taskComplete;
19
- signal.throwIfAborted();
20
- const videoInput = await host.videoInputTask.taskComplete;
21
- signal.throwIfAborted();
22
- if (!videoInput) throw new Error("Video input is not available");
23
- const videoTrack = await videoInput.getFirstVideoTrack();
24
- if (!videoTrack) throw new Error("Video track is not available");
25
- signal.throwIfAborted();
26
- const sample = await videoInput.seek(videoTrack.id, targetSeekTimeMs);
27
- signal.throwIfAborted();
28
- if (sample === void 0 && signal.aborted) return void 0;
29
- if (sample === void 0) throw new Error("Video seek failed to find sample");
30
- return sample;
31
- }
32
- });
33
- };
34
- export { makeVideoSeekTask };
@@ -1,9 +0,0 @@
1
- import { EFVideo } from '../../EFVideo';
2
- declare class TestMediaVideoSegmentFetch extends EFVideo {
3
- }
4
- declare global {
5
- interface HTMLElementTagNameMap {
6
- "test-media-video-segment-fetch": TestMediaVideoSegmentFetch;
7
- }
8
- }
9
- export {};
@@ -1,4 +0,0 @@
1
- import { Task } from '@lit/task';
2
- import { MediaEngine } from '../../../transcoding/types';
3
- import { EFVideo } from '../../EFVideo';
4
- export declare const makeVideoSegmentFetchTask: (host: EFVideo) => Task<readonly [MediaEngine | undefined, number | undefined], ArrayBuffer>;
@@ -1,28 +0,0 @@
1
- import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
- import { Task } from "@lit/task";
3
- const makeVideoSegmentFetchTask = (host) => {
4
- return new Task(host, {
5
- args: () => [host.mediaEngineTask.value, host.videoSegmentIdTask.value],
6
- onError: (error) => {
7
- console.error("videoSegmentFetchTask error", error);
8
- },
9
- onComplete: (_value) => {},
10
- task: async (_, { signal }) => {
11
- const mediaEngine = await getLatestMediaEngine(host, signal);
12
- const segmentId = await host.videoSegmentIdTask.taskComplete;
13
- if (segmentId === void 0) {
14
- const rendition = mediaEngine.videoRendition;
15
- const debugInfo = {
16
- hasRendition: !!rendition,
17
- segmentDurationMs: rendition?.segmentDurationMs,
18
- segmentDurationsMs: rendition?.segmentDurationsMs?.length || 0,
19
- desiredSeekTimeMs: host.desiredSeekTimeMs,
20
- intrinsicDurationMs: host.intrinsicDurationMs
21
- };
22
- throw new Error(`Segment ID is not available for video. Debug info: ${JSON.stringify(debugInfo)}`);
23
- }
24
- return mediaEngine.fetchMediaSegment(segmentId, mediaEngine.getVideoRendition(), signal);
25
- }
26
- });
27
- };
28
- export { makeVideoSegmentFetchTask };
@@ -1,9 +0,0 @@
1
- import { EFVideo } from '../../EFVideo';
2
- declare class TestMediaVideoSegment extends EFVideo {
3
- }
4
- declare global {
5
- interface HTMLElementTagNameMap {
6
- "test-media-video-segment": TestMediaVideoSegment;
7
- }
8
- }
9
- export {};
@@ -1,4 +0,0 @@
1
- import { Task } from '@lit/task';
2
- import { MediaEngine } from '../../../transcoding/types';
3
- import { EFVideo } from '../../EFVideo';
4
- export declare const makeVideoSegmentIdTask: (host: EFVideo) => Task<readonly [MediaEngine | undefined, number], number | undefined>;
@@ -1,233 +0,0 @@
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
- });