@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
@@ -28,13 +28,13 @@ type VideoBufferTask = Task<readonly [number], VideoBufferState>;
28
28
  export const makeVideoBufferTask = (host: EFVideo): VideoBufferTask => {
29
29
  let currentState: VideoBufferState = {
30
30
  currentSeekTimeMs: 0,
31
+ requestedSegments: new Set(),
31
32
  activeRequests: new Set(),
32
- cachedSegments: new Set(),
33
33
  requestQueue: [],
34
34
  };
35
35
 
36
36
  return new Task(host, {
37
- autoRun: EF_INTERACTIVE, // Start buffering automatically when media is ready
37
+ autoRun: EF_INTERACTIVE && !EF_RENDERING(), // Start buffering only in interactive mode, not rendering
38
38
  args: () => [host.desiredSeekTimeMs] as const,
39
39
  onError: (error) => {
40
40
  console.error("videoBufferTask error", error);
@@ -43,11 +43,16 @@ export const makeVideoBufferTask = (host: EFVideo): VideoBufferTask => {
43
43
  currentState = value;
44
44
  },
45
45
  task: async ([seekTimeMs], { signal }) => {
46
+ // Skip buffering entirely in rendering mode
47
+ if (EF_RENDERING()) {
48
+ return currentState; // Return existing state without any buffering activity
49
+ }
50
+
46
51
  // Use EFMedia properties directly - no hardcoded duplication!
47
52
  const currentConfig: VideoBufferConfig = {
48
53
  bufferDurationMs: host.videoBufferDurationMs,
49
54
  maxParallelFetches: host.maxVideoBufferFetches,
50
- enableBuffering: host.enableVideoBuffering && !EF_RENDERING,
55
+ enableBuffering: host.enableVideoBuffering,
51
56
  };
52
57
 
53
58
  return manageMediaBuffer<VideoRendition>(
@@ -62,9 +67,18 @@ export const makeVideoBufferTask = (host: EFVideo): VideoBufferTask => {
62
67
  const mediaEngine = await getLatestMediaEngine(host, signal);
63
68
  return mediaEngine.computeSegmentId(timeMs, rendition);
64
69
  },
65
- fetchSegment: async (segmentId, rendition) => {
70
+ prefetchSegment: async (segmentId, rendition) => {
71
+ // Trigger prefetch through BaseMediaEngine - let it handle caching
66
72
  const mediaEngine = await getLatestMediaEngine(host, signal);
67
- return mediaEngine.fetchMediaSegment(segmentId, rendition);
73
+ await mediaEngine.fetchMediaSegment(segmentId, rendition);
74
+ // Don't return data - just ensure it's cached in BaseMediaEngine
75
+ },
76
+ isSegmentCached: (segmentId, rendition) => {
77
+ // Check if segment is already cached in BaseMediaEngine
78
+ const mediaEngine = host.mediaEngineTask.value;
79
+ if (!mediaEngine) return false;
80
+
81
+ return mediaEngine.isSegmentCached(segmentId, rendition);
68
82
  },
69
83
  getRendition: async () => {
70
84
  // Get real video rendition from media engine
@@ -13,6 +13,7 @@ import "./EFTimegroup.js";
13
13
  import type { EFTimegroup } from "./EFTimegroup.js";
14
14
  import "./EFVideo.js";
15
15
  import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
16
+ import { AssetMediaEngine } from "./EFMedia/AssetMediaEngine.js";
16
17
  import type { EFVideo } from "./EFVideo.js";
17
18
 
18
19
  @customElement("test-media")
@@ -100,7 +101,7 @@ describe("JIT Media Engine", () => {
100
101
  await timegroup.waitForMediaDurations();
101
102
  timegroup.currentTimeMs = 2200;
102
103
  await timegroup.seekTask.taskComplete;
103
- const sample = await jitVideo.videoSeekTask.taskComplete;
104
+ const sample = await jitVideo.unifiedVideoSeekTask.taskComplete;
104
105
  expect(sample?.timestamp).toBeCloseTo(2.2, 1);
105
106
  });
106
107
  });
@@ -124,10 +125,10 @@ describe("JIT Media Engine", () => {
124
125
  await timegroup.seekTask.taskComplete;
125
126
 
126
127
  // Check what segment actually got loaded
127
- const actualSegmentId = (jitVideo as any).videoSegmentIdTask.value;
128
+ const actualSegmentId = (jitVideo as any).unifiedVideoSeekTask.value;
128
129
  console.log(`videoSegmentIdTask.value = ${actualSegmentId}`);
129
130
 
130
- const frame = await (jitVideo as any).videoSeekTask.taskComplete;
131
+ const frame = await (jitVideo as any).unifiedVideoSeekTask.taskComplete;
131
132
  console.log(`Frame timestamp when seeking to 0ms: ${frame?.timestamp}`);
132
133
 
133
134
  expect(frame).toBeDefined();
@@ -142,7 +143,7 @@ describe("JIT Media Engine", () => {
142
143
  await timegroup.waitForMediaDurations();
143
144
  timegroup.currentTimeMs = 3_000;
144
145
  await timegroup.seekTask.taskComplete;
145
- const frame = await jitVideo.videoSeekTask.taskComplete;
146
+ const frame = await jitVideo.unifiedVideoSeekTask.taskComplete;
146
147
  expect(frame?.timestamp).toBeCloseTo(3, 1);
147
148
  });
148
149
 
@@ -154,25 +155,30 @@ describe("JIT Media Engine", () => {
154
155
  await timegroup.waitForMediaDurations();
155
156
  timegroup.currentTimeMs = 5_000;
156
157
  await timegroup.seekTask.taskComplete;
157
- const frame = await jitVideo.videoSeekTask.taskComplete;
158
+ const frame = await jitVideo.unifiedVideoSeekTask.taskComplete;
158
159
  expect(frame?.timestamp).toBeCloseTo(5, 1);
159
160
  });
160
161
 
161
- test("seeks ahead in 50ms increments", async ({
162
+ test("seeks ahead in increments", async ({
162
163
  timegroup,
163
164
  jitVideo,
164
165
  expect,
165
166
  }) => {
166
167
  await timegroup.waitForMediaDurations();
167
168
  timegroup.currentTimeMs = 0;
169
+
170
+ // Test seeking in larger increments to avoid CI timeouts
171
+ // while still validating incremental seeking works
172
+ const testPoints = [0, 500, 1000, 1500, 2000, 2500, 3000];
168
173
  let frame: VideoSample | undefined;
169
- for (let i = 0; i <= 3000; i += 50) {
170
- timegroup.currentTimeMs = i;
174
+
175
+ for (const timeMs of testPoints) {
176
+ timegroup.currentTimeMs = timeMs;
171
177
  await timegroup.seekTask.taskComplete;
172
- frame = await jitVideo.videoSeekTask.taskComplete;
178
+ frame = await jitVideo.unifiedVideoSeekTask.taskComplete;
173
179
  expect(frame).toBeDefined();
180
+ expect(frame?.timestamp).toBeCloseTo(timeMs / 1000, 1);
174
181
  }
175
- expect(frame?.timestamp).toBeCloseTo(3, 1);
176
182
  });
177
183
  });
178
184
 
@@ -189,7 +195,7 @@ describe("JIT Media Engine", () => {
189
195
 
190
196
  timegroup.currentTimeMs = 2026.6666666666663;
191
197
  await timegroup.frameTask.taskComplete;
192
- const sample = await jitVideo.videoSeekTask.taskComplete;
198
+ const sample = await jitVideo.unifiedVideoSeekTask.taskComplete;
193
199
  expect(sample?.timestamp).toBeCloseTo(2, 1);
194
200
  });
195
201
 
@@ -224,6 +230,63 @@ describe("JIT Media Engine", () => {
224
230
  });
225
231
  });
226
232
 
233
+ describe("Media Engine Selection", () => {
234
+ const remoteSrc = "http://web:3000/head-moov-480p.mp4";
235
+ const localSrc = "10s-bars.mp4";
236
+
237
+ test("defaults to JitMediaEngine for remote URLs without a configuration element", async ({
238
+ expect,
239
+ }) => {
240
+ const video = document.createElement("ef-video");
241
+ video.src = remoteSrc;
242
+ document.body.appendChild(video);
243
+ await video.mediaEngineTask.run();
244
+ expect(video.mediaEngineTask.value).toBeInstanceOf(JitMediaEngine);
245
+ video.remove();
246
+ });
247
+
248
+ test("uses JitMediaEngine for remote URLs when wrapped in a default configuration", async ({
249
+ configuration,
250
+ expect,
251
+ }) => {
252
+ const video = document.createElement("ef-video");
253
+ video.src = remoteSrc;
254
+ configuration.appendChild(video); // Fixture `configuration` is already on the page.
255
+ await video.mediaEngineTask.run();
256
+ expect(video.mediaEngineTask.value).toBeInstanceOf(JitMediaEngine);
257
+ video.remove();
258
+ });
259
+
260
+ test("uses JitMediaEngine for remote URLs when configured with media-engine='cloud'", async ({
261
+ configuration,
262
+ expect,
263
+ }) => {
264
+ configuration.setAttribute("media-engine", "cloud");
265
+ const video = document.createElement("ef-video");
266
+ video.src = remoteSrc;
267
+ configuration.appendChild(video);
268
+ await video.mediaEngineTask.run();
269
+ expect(video.mediaEngineTask.value).toBeInstanceOf(JitMediaEngine);
270
+ video.remove();
271
+ });
272
+
273
+ // Note: media-engine='local' with remote URLs is not supported
274
+ // AssetMediaEngine is designed for local files and track fragment indexes only
275
+
276
+ test("always uses AssetMediaEngine for local src paths", async ({
277
+ configuration,
278
+ expect,
279
+ }) => {
280
+ configuration.setAttribute("media-engine", "cloud"); // Explicitly set to cloud
281
+ const video = document.createElement("ef-video");
282
+ video.src = localSrc;
283
+ configuration.appendChild(video);
284
+ await video.mediaEngineTask.run();
285
+ expect(video.mediaEngineTask.value).toBeInstanceOf(AssetMediaEngine);
286
+ video.remove();
287
+ });
288
+ });
289
+
227
290
  describe("EFMedia", () => {
228
291
  beforeEach(() => {
229
292
  // Clean up DOM
@@ -86,7 +86,7 @@ export class EFMedia extends EFTargetable(
86
86
  * @domAttribute "audio-buffer-duration"
87
87
  */
88
88
  @property({ type: Number, attribute: "audio-buffer-duration" })
89
- audioBufferDurationMs = 30000; // 30 seconds
89
+ audioBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding
90
90
 
91
91
  /**
92
92
  * Maximum number of concurrent audio segment fetches for buffering
@@ -258,26 +258,4 @@ export class EFMedia extends EFTargetable(
258
258
  ): Promise<AudioSpan> {
259
259
  return fetchAudioSpanningTime(this, fromMs, toMs, signal);
260
260
  }
261
-
262
- /**
263
- * Check if an audio segment is cached in the unified buffer system
264
- * Now uses the same caching approach as video for consistency
265
- */
266
- getCachedAudioSegment(segmentId: number): boolean {
267
- return this.audioBufferTask.value?.cachedSegments.has(segmentId) ?? false;
268
- }
269
-
270
- /**
271
- * Get cached audio segments from the unified buffer system
272
- * Now uses the same caching approach as video for consistency
273
- */
274
- getCachedAudioSegments(segmentIds: number[]): Set<number> {
275
- const bufferState = this.audioBufferTask.value;
276
- if (!bufferState) {
277
- return new Set();
278
- }
279
- return new Set(
280
- segmentIds.filter((id) => bufferState.cachedSegments.has(id)),
281
- );
282
- }
283
261
  }