@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.
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
@@ -7,12 +7,14 @@ import { createRef, ref } from "lit/directives/ref.js";
7
7
  import { DelayedLoadingState } from "../DelayedLoadingState.js";
8
8
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
9
9
  import { TWMixin } from "../gui/TWMixin.js";
10
+ import { makeScrubVideoBufferTask } from "./EFMedia/videoTasks/makeScrubVideoBufferTask.ts";
11
+ import { makeScrubVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts";
12
+ import { makeScrubVideoInputTask } from "./EFMedia/videoTasks/makeScrubVideoInputTask.ts";
13
+ import { makeScrubVideoSeekTask } from "./EFMedia/videoTasks/makeScrubVideoSeekTask.ts";
14
+ import { makeScrubVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts";
15
+ import { makeScrubVideoSegmentIdTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts";
16
+ import { makeUnifiedVideoSeekTask } from "./EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts";
10
17
  import { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.ts";
11
- import { makeVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts";
12
- import { makeVideoInputTask } from "./EFMedia/videoTasks/makeVideoInputTask.ts";
13
- import { makeVideoSeekTask } from "./EFMedia/videoTasks/makeVideoSeekTask.ts";
14
- import { makeVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoSegmentFetchTask.ts";
15
- import { makeVideoSegmentIdTask } from "./EFMedia/videoTasks/makeVideoSegmentIdTask.ts";
16
18
  import { EFMedia } from "./EFMedia.js";
17
19
 
18
20
  // EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
@@ -98,7 +100,7 @@ export class EFVideo extends TWMixin(EFMedia) {
98
100
  * @domAttribute "video-buffer-duration"
99
101
  */
100
102
  @property({ type: Number, attribute: "video-buffer-duration" })
101
- videoBufferDurationMs = 60000; // 60 seconds
103
+ videoBufferDurationMs = 10000; // 10 seconds - reasonable for JIT encoding
102
104
 
103
105
  /**
104
106
  * Maximum number of concurrent video segment fetches for buffering
@@ -114,13 +116,17 @@ export class EFVideo extends TWMixin(EFMedia) {
114
116
  @property({ type: Boolean, attribute: "enable-video-buffering" })
115
117
  enableVideoBuffering = true;
116
118
 
117
- // Video-specific tasks
118
- videoSegmentIdTask = makeVideoSegmentIdTask(this);
119
- videoInitSegmentFetchTask = makeVideoInitSegmentFetchTask(this);
120
- videoSegmentFetchTask = makeVideoSegmentFetchTask(this);
121
- videoInputTask = makeVideoInputTask(this);
122
- videoSeekTask = makeVideoSeekTask(this);
123
- videoBufferTask = makeVideoBufferTask(this);
119
+ // Unified video system - single smart seek task that routes to scrub or main
120
+ unifiedVideoSeekTask = makeUnifiedVideoSeekTask(this);
121
+ videoBufferTask = makeVideoBufferTask(this); // Keep for main video buffering
122
+
123
+ // Scrub video preloading system
124
+ scrubVideoBufferTask = makeScrubVideoBufferTask(this);
125
+ scrubVideoInputTask = makeScrubVideoInputTask(this);
126
+ scrubVideoSeekTask = makeScrubVideoSeekTask(this);
127
+ scrubVideoSegmentIdTask = makeScrubVideoSegmentIdTask(this);
128
+ scrubVideoSegmentFetchTask = makeScrubVideoSegmentFetchTask(this);
129
+ scrubVideoInitSegmentFetchTask = makeScrubVideoInitSegmentFetchTask(this);
124
130
 
125
131
  /**
126
132
  * Delayed loading state manager for user feedback
@@ -190,7 +196,7 @@ export class EFVideo extends TWMixin(EFMedia) {
190
196
  },
191
197
  onComplete: () => {},
192
198
  task: async ([_desiredSeekTimeMs], { signal }) => {
193
- await this.videoSeekTask.taskComplete;
199
+ await this.unifiedVideoSeekTask.taskComplete;
194
200
  await this.paintTask.taskComplete;
195
201
  if (signal.aborted) {
196
202
  return;
@@ -244,18 +250,24 @@ export class EFVideo extends TWMixin(EFMedia) {
244
250
  },
245
251
  onComplete: () => {},
246
252
  task: async ([_seekToMs], { signal }) => {
247
- await this.videoSeekTask.taskComplete;
248
253
  // Check if we're in production rendering mode vs preview mode
249
254
  const isProductionRendering = this.isInProductionRenderingMode();
250
255
 
251
- const sample = this.videoSeekTask.value;
252
- if (sample) {
253
- const videoFrame = sample.toVideoFrame();
254
- try {
255
- this.displayFrame(videoFrame, _seekToMs);
256
- } finally {
257
- videoFrame.close();
256
+ // Unified video system: smart routing to scrub or main, with background upgrades
257
+ try {
258
+ await this.unifiedVideoSeekTask.taskComplete;
259
+ const videoSample = this.unifiedVideoSeekTask.value;
260
+
261
+ if (videoSample) {
262
+ const videoFrame = videoSample.toVideoFrame();
263
+ try {
264
+ this.displayFrame(videoFrame, _seekToMs);
265
+ } finally {
266
+ videoFrame.close();
267
+ }
258
268
  }
269
+ } catch (error) {
270
+ console.warn("Unified video pipeline error:", error);
259
271
  }
260
272
 
261
273
  // EF_FRAMEGEN-aware rendering mode detection
@@ -292,7 +304,7 @@ export class EFVideo extends TWMixin(EFMedia) {
292
304
  /**
293
305
  * Display a video frame on the canvas
294
306
  */
295
- private displayFrame(frame: VideoFrame, seekToMs: number): number {
307
+ displayFrame(frame: VideoFrame, seekToMs: number): number {
296
308
  log("trace: displayFrame start", { seekToMs, frameFormat: frame.format });
297
309
  if (!this.canvasElement) {
298
310
  log("trace: displayFrame aborted - no canvas element");
@@ -387,11 +399,11 @@ export class EFVideo extends TWMixin(EFMedia) {
387
399
  }
388
400
 
389
401
  /**
390
- * Legacy getter for fragment index task (maps to videoSegmentIdTask)
391
- * Still used by EFCaptions
402
+ * Legacy getter for fragment index task
403
+ * Still used by EFCaptions - maps to unified video seek task
392
404
  */
393
405
  get fragmentIndexTask() {
394
- return this.videoSegmentIdTask;
406
+ return this.unifiedVideoSeekTask;
395
407
  }
396
408
 
397
409
  /**
@@ -1,4 +1,4 @@
1
- import { LitElement, html } from "lit";
1
+ import { html, LitElement } from "lit";
2
2
  import { customElement, property, state } from "lit/decorators.js";
3
3
  import { afterEach, describe, expect, test } from "vitest";
4
4
  import { EFTargetable, TargetController } from "./TargetController.ts";
@@ -1,5 +1,5 @@
1
1
  import { createContext, provide } from "@lit/context";
2
- import { LitElement, css, html } from "lit";
2
+ import { css, html, LitElement } from "lit";
3
3
  import { customElement, property } from "lit/decorators.js";
4
4
 
5
5
  export const efConfigurationContext = createContext<EFConfiguration | null>(
@@ -25,6 +25,9 @@ export class EFConfiguration extends LitElement {
25
25
  @property({ type: String, attribute: "signing-url" })
26
26
  signingURL?: string;
27
27
 
28
+ @property({ type: String, attribute: "media-engine" })
29
+ mediaEngine?: "cloud" | "local" = "cloud";
30
+
28
31
  render() {
29
32
  return html`<slot></slot>`;
30
33
  }
@@ -1,12 +1,12 @@
1
1
  import { consume } from "@lit/context";
2
2
  import {
3
+ css,
4
+ html,
3
5
  LitElement,
6
+ nothing,
4
7
  type PropertyValueMap,
5
8
  type ReactiveController,
6
9
  type TemplateResult,
7
- css,
8
- html,
9
- nothing,
10
10
  } from "lit";
11
11
  import {
12
12
  customElement,
@@ -29,10 +29,10 @@ import { msToTimeCode } from "../msToTimeCode.js";
29
29
  import { targetTimegroupContext } from "./ContextMixin.ts";
30
30
  import type { EFPreview } from "./EFPreview.js";
31
31
  import type { EFWorkbench } from "./EFWorkbench.js";
32
- import { TWMixin } from "./TWMixin.js";
33
32
  import { type FocusContext, focusContext } from "./focusContext.js";
34
33
  import { focusedElementContext } from "./focusedElementContext.js";
35
34
  import { loopContext, playingContext } from "./playingContext.js";
35
+ import { TWMixin } from "./TWMixin.js";
36
36
 
37
37
  class ElementFilmstripController implements ReactiveController {
38
38
  constructor(
@@ -1,5 +1,5 @@
1
1
  import { consume } from "@lit/context";
2
- import { LitElement, css, html } from "lit";
2
+ import { css, html, LitElement } from "lit";
3
3
  import { customElement } from "lit/decorators.js";
4
4
  import { createRef, ref } from "lit/directives/ref.js";
5
5
  import { focusedElementContext } from "./focusedElementContext.js";
@@ -1,10 +1,9 @@
1
- import { LitElement, css, html } from "lit";
2
- import { customElement } from "lit/decorators.js";
3
-
4
1
  import { provide } from "@lit/context";
2
+ import { css, html, LitElement } from "lit";
3
+ import { customElement } from "lit/decorators.js";
5
4
  import { ContextMixin } from "./ContextMixin.js";
6
- import { TWMixin } from "./TWMixin.js";
7
5
  import { focusedElementContext } from "./focusedElementContext.js";
6
+ import { TWMixin } from "./TWMixin.js";
8
7
 
9
8
  @customElement("ef-preview")
10
9
  export class EFPreview extends ContextMixin(TWMixin(LitElement)) {
@@ -1,5 +1,5 @@
1
1
  import { consume } from "@lit/context";
2
- import { LitElement, css, html } from "lit";
2
+ import { css, html, LitElement } from "lit";
3
3
  import { customElement, state } from "lit/decorators.js";
4
4
 
5
5
  import { ref } from "lit/directives/ref.js";
@@ -1,5 +1,5 @@
1
1
  import { consume } from "@lit/context";
2
- import { LitElement, css, html } from "lit";
2
+ import { css, html, LitElement } from "lit";
3
3
  import { customElement } from "lit/decorators.js";
4
4
  import type { ContextMixinInterface } from "./ContextMixin.js";
5
5
  import { efContext } from "./efContext.js";
@@ -1,5 +1,5 @@
1
1
  import { consume } from "@lit/context";
2
- import { LitElement, css, html } from "lit";
2
+ import { css, html, LitElement } from "lit";
3
3
  import { customElement } from "lit/decorators.js";
4
4
 
5
5
  import type { ContextMixinInterface } from "./ContextMixin.js";
@@ -1,5 +1,5 @@
1
1
  import { consume } from "@lit/context";
2
- import { LitElement, css, html } from "lit";
2
+ import { css, html, LitElement } from "lit";
3
3
  import { customElement } from "lit/decorators.js";
4
4
 
5
5
  import type { ContextMixinInterface } from "./ContextMixin.js";
@@ -1,4 +1,4 @@
1
- import { LitElement, type PropertyValueMap, css, html } from "lit";
1
+ import { css, html, LitElement, type PropertyValueMap } from "lit";
2
2
  import { customElement, eventOptions, property } from "lit/decorators.js";
3
3
  import { createRef, ref } from "lit/directives/ref.js";
4
4
 
@@ -237,6 +237,22 @@ export interface MediaEngine {
237
237
  */
238
238
  getAudioRendition: () => AudioRendition;
239
239
 
240
+ /**
241
+ * Check if a segment is cached for a given rendition
242
+ */
243
+ isSegmentCached: (
244
+ segmentId: number,
245
+ rendition: AudioRendition | VideoRendition,
246
+ ) => boolean;
247
+
248
+ /**
249
+ * Get scrub video rendition if available, otherwise return undefined
250
+ * Each engine implements this based on their capabilities:
251
+ * - JitMediaEngine: looks for "scrub" rendition in manifest
252
+ * - AssetMediaEngine: returns regular video rendition (no separate scrub)
253
+ */
254
+ getScrubVideoRendition(): VideoRendition | undefined;
255
+
240
256
  /**
241
257
  * Calculate audio segments needed for a time range
242
258
  * Each media engine implements this based on their segment structure
@@ -0,0 +1,16 @@
1
+ {
2
+ "statusCode": 200,
3
+ "headers": {
4
+ "access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization, Range",
5
+ "access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
6
+ "access-control-allow-origin": "*",
7
+ "access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
8
+ "cache-control": "public, max-age=3600",
9
+ "content-length": "645445",
10
+ "content-type": "video/iso.segment",
11
+ "x-powered-by": "Express"
12
+ },
13
+ "url": "/api/v1/transcode/scrub/1.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
14
+ "method": "GET",
15
+ "range": null
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "statusCode": 200,
3
+ "headers": {
4
+ "access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization, Range",
5
+ "access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
6
+ "access-control-allow-origin": "*",
7
+ "access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
8
+ "cache-control": "public, max-age=3600",
9
+ "content-length": "754",
10
+ "content-type": "video/iso.segment",
11
+ "x-powered-by": "Express"
12
+ },
13
+ "url": "/api/v1/transcode/scrub/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
14
+ "method": "GET",
15
+ "range": null
16
+ }
@@ -0,0 +1,84 @@
1
+ import { customElement } from "lit/decorators.js";
2
+ import { EFMedia } from "../src/elements/EFMedia.ts";
3
+ import { test as baseTest } from "./useMSW.js";
4
+
5
+ @customElement("test-cache-integration")
6
+ class TestCacheIntegration extends EFMedia {}
7
+
8
+ declare global {
9
+ interface HTMLElementTagNameMap {
10
+ "test-cache-integration": TestCacheIntegration;
11
+ }
12
+ }
13
+
14
+ const test = baseTest.extend<{
15
+ element: TestCacheIntegration;
16
+ }>({
17
+ element: async ({}, use) => {
18
+ const element = document.createElement("test-cache-integration");
19
+ document.body.appendChild(element);
20
+ await use(element);
21
+ element.remove();
22
+ },
23
+ });
24
+
25
+ test("buffer cache integration stores and serves segment data", async ({
26
+ element,
27
+ expect,
28
+ }) => {
29
+ // Setup element with real media source
30
+ element.src = "bars-n-tone2.mp4";
31
+ element.enableAudioBuffering = true;
32
+ element.audioBufferDurationMs = 3000;
33
+ element.maxAudioBufferFetches = 2;
34
+ element.desiredSeekTimeMs = 0;
35
+
36
+ // Track network requests to verify cache effectiveness
37
+ const originalFetch = window.fetch;
38
+ const networkRequests: string[] = [];
39
+
40
+ window.fetch = (input: RequestInfo | URL, init?: RequestInit) => {
41
+ const url = input.toString();
42
+ if (url.includes("audio")) {
43
+ networkRequests.push(url);
44
+ console.debug("Network request:", url);
45
+ }
46
+ return originalFetch(input, init);
47
+ };
48
+
49
+ try {
50
+ // Allow initial buffering
51
+ await new Promise((resolve) => setTimeout(resolve, 1000));
52
+
53
+ const bufferState = element.audioBufferTask.value;
54
+ console.log("Buffer state after initial buffering:", {
55
+ cachedSegmentsCount: bufferState?.cachedSegments.size || 0,
56
+ cachedDataCount: bufferState?.cachedSegmentData.size || 0,
57
+ activeRequestsCount: bufferState?.activeRequests.size || 0,
58
+ });
59
+
60
+ if (bufferState && bufferState.cachedSegments.size > 0) {
61
+ const initialNetworkRequests = networkRequests.length;
62
+
63
+ // Trigger segment fetch for potentially cached segment
64
+ element.desiredSeekTimeMs = 1000;
65
+ await new Promise((resolve) => setTimeout(resolve, 200));
66
+
67
+ // Check if cache integration is working
68
+ const finalNetworkRequests = networkRequests.length;
69
+
70
+ console.log("Cache integration test results:", {
71
+ initialNetworkRequests,
72
+ finalNetworkRequests,
73
+ cacheHit: finalNetworkRequests === initialNetworkRequests,
74
+ cachedSegments: Array.from(bufferState.cachedSegments),
75
+ cachedData: Array.from(bufferState.cachedSegmentData.keys()),
76
+ });
77
+
78
+ // The test verifies that segment data is being cached
79
+ expect(bufferState.cachedSegmentData.size).toBeGreaterThan(0);
80
+ }
81
+ } finally {
82
+ window.fetch = originalFetch;
83
+ }
84
+ });