@editframe/elements 0.18.23-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.
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +2 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +3 -0
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +9 -0
- package/dist/elements/EFMedia/BaseMediaEngine.js +31 -0
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +11 -5
- package/dist/elements/EFMedia/shared/BufferUtils.d.ts +19 -18
- package/dist/elements/EFMedia/shared/BufferUtils.js +24 -44
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +8 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +5 -5
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.d.ts +25 -0
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +42 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.d.ts +8 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +70 -0
- package/dist/elements/EFMedia/videoTasks/{makeVideoInitSegmentFetchTask.d.ts → makeScrubVideoInitSegmentFetchTask.d.ts} +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +21 -0
- package/dist/elements/EFMedia/videoTasks/{makeVideoInputTask.d.ts → makeScrubVideoInputTask.d.ts} +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +27 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.d.ts +6 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +52 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +23 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/{makeVideoSegmentIdTask.js → makeScrubVideoSegmentIdTask.js} +9 -4
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.d.ts +6 -0
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +112 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -5
- package/dist/elements/EFMedia.d.ts +0 -10
- package/dist/elements/EFMedia.js +1 -17
- package/dist/elements/EFVideo.d.ts +11 -9
- package/dist/elements/EFVideo.js +31 -23
- package/dist/gui/EFConfiguration.d.ts +1 -0
- package/dist/gui/EFConfiguration.js +5 -0
- package/dist/gui/EFFilmstrip.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/transcoding/types/index.d.ts +11 -0
- package/package.json +2 -2
- package/src/elements/EFCaptions.ts +1 -1
- package/src/elements/EFImage.ts +1 -1
- package/src/elements/EFMedia/AssetMediaEngine.ts +6 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +60 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +18 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +185 -59
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +19 -6
- package/src/elements/EFMedia/shared/BufferUtils.ts +71 -85
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +151 -112
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +12 -5
- package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +61 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +113 -0
- package/src/elements/EFMedia/videoTasks/{makeVideoInitSegmentFetchTask.ts → makeScrubVideoInitSegmentFetchTask.ts} +15 -3
- package/src/elements/EFMedia/videoTasks/{makeVideoInputTask.ts → makeScrubVideoInputTask.ts} +11 -10
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +118 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +44 -0
- package/src/elements/EFMedia/videoTasks/{makeVideoSegmentIdTask.ts → makeScrubVideoSegmentIdTask.ts} +14 -6
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +258 -0
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +19 -5
- package/src/elements/EFMedia.browsertest.ts +74 -11
- package/src/elements/EFMedia.ts +1 -23
- package/src/elements/EFVideo.browsertest.ts +204 -80
- package/src/elements/EFVideo.ts +38 -26
- package/src/elements/TargetController.browsertest.ts +1 -1
- package/src/gui/EFConfiguration.ts +4 -1
- package/src/gui/EFFilmstrip.ts +4 -4
- package/src/gui/EFFocusOverlay.ts +1 -1
- package/src/gui/EFPreview.ts +3 -4
- package/src/gui/EFScrubber.ts +1 -1
- package/src/gui/EFTimeDisplay.ts +1 -1
- package/src/gui/EFToggleLoop.ts +1 -1
- package/src/gui/EFTogglePlay.ts +1 -1
- package/src/gui/EFWorkbench.ts +1 -1
- package/src/transcoding/types/index.ts +16 -0
- 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
- 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
- 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
- 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
- package/test/cache-integration-verification.browsertest.ts +84 -0
- package/types.json +1 -1
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +0 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +0 -16
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +0 -27
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +0 -7
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +0 -34
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +0 -4
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +0 -28
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +0 -4
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +0 -233
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +0 -555
- package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +0 -59
- package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +0 -55
- package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +0 -65
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +0 -57
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +0 -43
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +0 -56
|
@@ -1,555 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
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 { EFVideo } from "../../EFVideo";
|
|
6
|
-
import { makeVideoInitSegmentFetchTask } from "./makeVideoInitSegmentFetchTask";
|
|
7
|
-
|
|
8
|
-
@customElement("test-media-video-init-segment-fetch")
|
|
9
|
-
class TestMediaVideoInitSegmentFetch extends EFVideo {}
|
|
10
|
-
|
|
11
|
-
declare global {
|
|
12
|
-
interface HTMLElementTagNameMap {
|
|
13
|
-
"test-media-video-init-segment-fetch": TestMediaVideoInitSegmentFetch;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const test = baseTest.extend<{
|
|
18
|
-
element: TestMediaVideoInitSegmentFetch;
|
|
19
|
-
}>({
|
|
20
|
-
element: async ({}, use) => {
|
|
21
|
-
const element = document.createElement(
|
|
22
|
-
"test-media-video-init-segment-fetch",
|
|
23
|
-
);
|
|
24
|
-
await use(element);
|
|
25
|
-
element.remove();
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe("makeVideoInitSegmentFetchTask", () => {
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
// MSW setup is now handled by test fixtures
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
afterEach(() => {
|
|
35
|
-
const elements = document.querySelectorAll(
|
|
36
|
-
"test-media-video-init-segment-fetch",
|
|
37
|
-
);
|
|
38
|
-
for (const element of elements) {
|
|
39
|
-
element.remove();
|
|
40
|
-
}
|
|
41
|
-
vi.restoreAllMocks();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("creates task with correct initial state", ({ element, expect }) => {
|
|
45
|
-
const task = makeVideoInitSegmentFetchTask(element);
|
|
46
|
-
|
|
47
|
-
expect(task).toBeDefined();
|
|
48
|
-
expect(task.status).toBe(TaskStatus.INITIAL);
|
|
49
|
-
expect(task.value).toBeUndefined();
|
|
50
|
-
expect(task.error).toBeUndefined();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("task integrates with element properties", ({ element, expect }) => {
|
|
54
|
-
const task = makeVideoInitSegmentFetchTask(element);
|
|
55
|
-
|
|
56
|
-
expect(task).toBeDefined();
|
|
57
|
-
expect(task.status).toBe(TaskStatus.INITIAL);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,55 +0,0 @@
|
|
|
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 { EFVideo } from "../../EFVideo";
|
|
6
|
-
import { makeVideoInputTask } from "./makeVideoInputTask";
|
|
7
|
-
|
|
8
|
-
@customElement("test-media-video-input")
|
|
9
|
-
class TestMediaVideoInput extends EFVideo {}
|
|
10
|
-
|
|
11
|
-
declare global {
|
|
12
|
-
interface HTMLElementTagNameMap {
|
|
13
|
-
"test-media-video-input": TestMediaVideoInput;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const test = baseTest.extend<{
|
|
18
|
-
element: TestMediaVideoInput;
|
|
19
|
-
}>({
|
|
20
|
-
element: async ({}, use) => {
|
|
21
|
-
const element = document.createElement("test-media-video-input");
|
|
22
|
-
await use(element);
|
|
23
|
-
element.remove();
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("makeVideoInputTask", () => {
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
// MSW setup is now handled by test fixtures
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
const elements = document.querySelectorAll("test-media-video-input");
|
|
34
|
-
for (const element of elements) {
|
|
35
|
-
element.remove();
|
|
36
|
-
}
|
|
37
|
-
vi.restoreAllMocks();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("creates task with correct initial state", ({ element, expect }) => {
|
|
41
|
-
const task = makeVideoInputTask(element);
|
|
42
|
-
|
|
43
|
-
expect(task).toBeDefined();
|
|
44
|
-
expect(task.status).toBe(TaskStatus.INITIAL);
|
|
45
|
-
expect(task.value).toBeUndefined();
|
|
46
|
-
expect(task.error).toBeUndefined();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("task integrates with element properties", ({ element, expect }) => {
|
|
50
|
-
const task = makeVideoInputTask(element);
|
|
51
|
-
|
|
52
|
-
expect(task).toBeDefined();
|
|
53
|
-
expect(task.status).toBe(TaskStatus.INITIAL);
|
|
54
|
-
});
|
|
55
|
-
});
|