@editframe/elements 0.18.7-beta.0 → 0.18.19-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 (60) hide show
  1. package/dist/elements/EFAudio.d.ts +1 -2
  2. package/dist/elements/EFAudio.js +6 -9
  3. package/dist/elements/EFMedia/AssetIdMediaEngine.js +4 -1
  4. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +3 -4
  5. package/dist/elements/EFMedia/AssetMediaEngine.js +28 -17
  6. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +30 -11
  7. package/dist/elements/EFMedia/BaseMediaEngine.js +83 -31
  8. package/dist/elements/EFMedia/JitMediaEngine.d.ts +2 -4
  9. package/dist/elements/EFMedia/JitMediaEngine.js +12 -12
  10. package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +7 -2
  11. package/dist/elements/EFVideo.d.ts +0 -1
  12. package/dist/elements/EFVideo.js +0 -9
  13. package/dist/elements/TargetController.js +3 -2
  14. package/package.json +2 -2
  15. package/src/elements/EFAudio.ts +7 -20
  16. package/src/elements/EFMedia/AssetIdMediaEngine.ts +10 -1
  17. package/src/elements/EFMedia/AssetMediaEngine.ts +45 -21
  18. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +311 -0
  19. package/src/elements/EFMedia/BaseMediaEngine.ts +168 -51
  20. package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +2 -12
  21. package/src/elements/EFMedia/JitMediaEngine.ts +25 -16
  22. package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +10 -1
  23. package/src/elements/EFTemporal.browsertest.ts +47 -0
  24. package/src/elements/EFVideo.browsertest.ts +127 -281
  25. package/src/elements/EFVideo.ts +9 -9
  26. package/src/elements/TargetController.ts +6 -2
  27. package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/metadata.json +3 -8
  28. package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/metadata.json +3 -8
  29. package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/metadata.json +3 -8
  30. package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/metadata.json +3 -8
  31. package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/metadata.json +3 -8
  32. package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/metadata.json +3 -8
  33. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/data.bin +0 -0
  34. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +4 -9
  35. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/data.bin +0 -0
  36. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +4 -9
  37. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/data.bin +0 -0
  38. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/metadata.json +4 -9
  39. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
  40. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +4 -9
  41. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/data.bin +0 -0
  42. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +4 -9
  43. package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/data.bin +0 -0
  44. package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/metadata.json +4 -9
  45. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +2 -4
  46. package/test/recordReplayProxyPlugin.js +46 -31
  47. package/test/setup.ts +16 -0
  48. package/test/useAssetMSW.ts +54 -0
  49. package/test/useMSW.ts +4 -11
  50. package/types.json +1 -1
  51. package/dist/elements/MediaController.d.ts +0 -30
  52. package/src/elements/EFMedia/BaseMediaEngine.test.ts +0 -164
  53. package/src/elements/MediaController.ts +0 -98
  54. package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/data.bin +0 -0
  55. package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/metadata.json +0 -22
  56. package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/data.bin +0 -0
  57. package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/metadata.json +0 -22
  58. package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/data.bin +0 -0
  59. package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/metadata.json +0 -22
  60. /package/dist/elements/EFMedia/{BaseMediaEngine.test.d.ts → BaseMediaEngine.browsertest.d.ts} +0 -0
@@ -29,15 +29,15 @@ interface LoadingState {
29
29
 
30
30
  @customElement("ef-video")
31
31
  export class EFVideo extends TWMixin(EFMedia) {
32
- static get observedAttributes() {
33
- const parentAttributes = EFMedia.observedAttributes || [];
34
- return [
35
- ...parentAttributes,
36
- "video-buffer-duration",
37
- "max-video-buffer-fetches",
38
- "enable-video-buffering",
39
- ];
40
- }
32
+ // static get observedAttributes() {
33
+ // const parentAttributes = EFMedia.observedAttributes || [];
34
+ // return [
35
+ // ...parentAttributes,
36
+ // "video-buffer-duration",
37
+ // "max-video-buffer-fetches",
38
+ // "enable-video-buffering",
39
+ // ];
40
+ // }
41
41
 
42
42
  static styles = [
43
43
  /**
@@ -41,7 +41,11 @@ class TargetRegistry {
41
41
  }
42
42
  }
43
43
 
44
- unregister(id: string) {
44
+ unregister(id: string, target: LitElement) {
45
+ if (this.idMap.get(id) !== target) {
46
+ // Avoid unregistering a target that is not the current target
47
+ return;
48
+ }
45
49
  for (const callback of this.callbacks.get(id) ?? []) {
46
50
  callback(undefined);
47
51
  }
@@ -87,7 +91,7 @@ export const EFTargetable = <T extends Constructor<LitElement>>(
87
91
  if (oldValue === newValue) return;
88
92
 
89
93
  if (oldValue) {
90
- this.#registry.unregister(oldValue);
94
+ this.#registry.unregister(oldValue, this);
91
95
  }
92
96
  if (newValue) {
93
97
  this.#registry.register(newValue, this);
@@ -8,14 +8,9 @@
8
8
  "cache-control": "public, max-age=3600",
9
9
  "content-length": "32957",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "0",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/audio/1.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.247Z"
21
- }
15
+ "range": null
16
+ }
@@ -8,14 +8,9 @@
8
8
  "cache-control": "public, max-age=3600",
9
9
  "content-length": "32502",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "0",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/audio/2.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.247Z"
21
- }
15
+ "range": null
16
+ }
@@ -8,14 +8,9 @@
8
8
  "cache-control": "public, max-age=3600",
9
9
  "content-length": "32196",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "0",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/audio/3.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.271Z"
21
- }
15
+ "range": null
16
+ }
@@ -8,14 +8,9 @@
8
8
  "cache-control": "public, max-age=3600",
9
9
  "content-length": "32570",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "0",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/audio/4.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.247Z"
21
- }
15
+ "range": null
16
+ }
@@ -8,14 +8,9 @@
8
8
  "cache-control": "public, max-age=3600",
9
9
  "content-length": "32922",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Mon, 04 Aug 2025 18:16:50 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "5",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/audio/5.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-04T18:16:50.399Z"
21
- }
15
+ "range": null
16
+ }
@@ -8,14 +8,9 @@
8
8
  "cache-control": "public, max-age=3600",
9
9
  "content-length": "728",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "0",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/audio/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.205Z"
21
- }
15
+ "range": null
16
+ }
@@ -6,16 +6,11 @@
6
6
  "access-control-allow-origin": "*",
7
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
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "827216",
9
+ "content-length": "2057283",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "0",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/high/1.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.256Z"
21
- }
15
+ "range": null
16
+ }
@@ -6,16 +6,11 @@
6
6
  "access-control-allow-origin": "*",
7
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
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "905893",
9
+ "content-length": "2185975",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "1",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/high/2.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.697Z"
21
- }
15
+ "range": null
16
+ }
@@ -6,16 +6,11 @@
6
6
  "access-control-allow-origin": "*",
7
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
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "856088",
9
+ "content-length": "2120135",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Mon, 04 Aug 2025 18:16:50 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "8",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/high/3.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-04T18:16:50.395Z"
21
- }
15
+ "range": null
16
+ }
@@ -6,16 +6,11 @@
6
6
  "access-control-allow-origin": "*",
7
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
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "911925",
9
+ "content-length": "2221511",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "0",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/high/4.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.247Z"
21
- }
15
+ "range": null
16
+ }
@@ -6,16 +6,11 @@
6
6
  "access-control-allow-origin": "*",
7
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
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "829743",
9
+ "content-length": "2037521",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Tue, 05 Aug 2025 06:16:17 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "16",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/high/5.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-05T06:16:17.550Z"
21
- }
15
+ "range": null
16
+ }
@@ -6,16 +6,11 @@
6
6
  "access-control-allow-origin": "*",
7
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
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "782",
9
+ "content-length": "775",
10
10
  "content-type": "video/iso.segment",
11
- "date": "Fri, 08 Aug 2025 03:57:07 GMT",
12
- "x-cache": "HIT",
13
- "x-powered-by": "Express",
14
- "x-total-server-time-ms": "0",
15
- "x-transcode-time-ms": "0"
11
+ "x-powered-by": "Express"
16
12
  },
17
13
  "url": "/api/v1/transcode/high/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
18
14
  "method": "GET",
19
- "range": null,
20
- "timestamp": "2025-08-08T03:57:07.214Z"
21
- }
15
+ "range": null
16
+ }
@@ -8,12 +8,10 @@
8
8
  "cache-control": "public, max-age=300",
9
9
  "content-length": "2045",
10
10
  "content-type": "application/json; charset=utf-8",
11
- "date": "Thu, 07 Aug 2025 20:55:37 GMT",
12
11
  "etag": "W/\"81b-wi6z588RhWTgs57jivyDs3lEkkA\"",
13
12
  "x-powered-by": "Express"
14
13
  },
15
14
  "url": "/api/v1/transcode/manifest.json?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
16
15
  "method": "GET",
17
- "range": null,
18
- "timestamp": "2025-08-07T20:55:37.385Z"
19
- }
16
+ "range": null
17
+ }
@@ -9,6 +9,9 @@ const CACHE_DIR = join(__dirname, "__cache__");
9
9
  const TARGET_HOST = "host.docker.internal";
10
10
  const TARGET_PORT = 3000;
11
11
 
12
+ // Check if we should run in cache-only mode (for CI/prepare-release)
13
+ const CACHE_ONLY_MODE = process.env.EF_CACHE_ONLY === "true";
14
+
12
15
  /**
13
16
  * Vite plugin that adds record-and-replay proxy middleware
14
17
  * This proxy intercepts requests to /api/v1/transcode/*, caches responses to disk,
@@ -20,7 +23,7 @@ export function recordReplayProxyPlugin() {
20
23
 
21
24
  configureServer(server) {
22
25
  console.log(
23
- "[Proxy Plugin] Configuring record-replay proxy middleware...",
26
+ `[Proxy Plugin] Configuring record-replay proxy middleware... ${CACHE_ONLY_MODE ? "(CACHE-ONLY MODE)" : ""}`,
24
27
  );
25
28
 
26
29
  // Initialize cache directory
@@ -33,6 +36,11 @@ export function recordReplayProxyPlugin() {
33
36
 
34
37
  console.log("[Proxy Plugin] Proxy middleware configured");
35
38
  console.log(`[Proxy Plugin] Cache directory: ${CACHE_DIR}`);
39
+ if (CACHE_ONLY_MODE) {
40
+ console.log(
41
+ "[Proxy Plugin] ⚠️ Running in CACHE-ONLY mode - no remote fetching",
42
+ );
43
+ }
36
44
  },
37
45
  };
38
46
 
@@ -107,6 +115,7 @@ export function recordReplayProxyPlugin() {
107
115
  const headers = { ...normalized.headers };
108
116
  delete headers.date;
109
117
  delete headers["x-total-server-time-ms"];
118
+ delete headers["x-transcode-time-ms"]; // This varies between requests
110
119
  delete headers["x-cache"]; // This can vary between HIT/MISS
111
120
  normalized.headers = headers;
112
121
  }
@@ -117,30 +126,6 @@ export function recordReplayProxyPlugin() {
117
126
  return normalized;
118
127
  }
119
128
 
120
- // Check if cache should be updated by comparing normalized metadata
121
- async function shouldUpdateCache(cacheDir, newMetadata) {
122
- try {
123
- const metadataFile = join(cacheDir, "metadata.json");
124
- if (!existsSync(metadataFile)) {
125
- return true; // No existing cache, should update
126
- }
127
-
128
- const existingMetadata = JSON.parse(
129
- await readFile(metadataFile, "utf-8"),
130
- );
131
- const normalizedExisting = normalizeMetadata(existingMetadata);
132
- const normalizedNew = normalizeMetadata(newMetadata);
133
-
134
- // Compare normalized metadata to decide if update is needed
135
- return (
136
- JSON.stringify(normalizedExisting) !== JSON.stringify(normalizedNew)
137
- );
138
- } catch (error) {
139
- console.warn(`[Proxy] Failed to check cache metadata: ${error.message}`);
140
- return true; // Default to updating on error
141
- }
142
- }
143
-
144
129
  // Save response to cache
145
130
  async function cacheResponse(
146
131
  cacheDir,
@@ -163,14 +148,15 @@ export function recordReplayProxyPlugin() {
163
148
  timestamp: new Date().toISOString(),
164
149
  };
165
150
 
166
- // Only update cache if metadata has meaningfully changed
167
- if (!(await shouldUpdateCache(cacheDir, metadata))) {
168
- console.log("[Proxy] Cache up to date, skipping write");
169
- return;
170
- }
151
+ // Always write the response to cache - binary content can change even if headers don't
152
+ // Write normalized metadata to disk (without dynamic fields)
153
+ const normalizedMetadata = normalizeMetadata(metadata);
171
154
 
172
155
  const metadataFile = join(cacheDir, "metadata.json");
173
- await writeFile(metadataFile, JSON.stringify(metadata, null, 2));
156
+ await writeFile(
157
+ metadataFile,
158
+ JSON.stringify(normalizedMetadata, null, 2),
159
+ );
174
160
 
175
161
  const dataFile = join(cacheDir, "data.bin");
176
162
  await writeFile(dataFile, body); // Write raw binary data
@@ -209,6 +195,35 @@ export function recordReplayProxyPlugin() {
209
195
  const cacheKey = getCacheKey(req.method, fullPath, req.headers);
210
196
  const cacheDir = join(CACHE_DIR, cacheKey);
211
197
 
198
+ // In cache-only mode, try to serve from cache first
199
+ if (CACHE_ONLY_MODE) {
200
+ if (existsSync(cacheDir)) {
201
+ try {
202
+ const metadataFile = join(cacheDir, "metadata.json");
203
+ if (existsSync(metadataFile)) {
204
+ console.log(
205
+ `[Proxy] ✓ CACHE-ONLY: Serving from cache: ${cacheKey}`,
206
+ );
207
+ await serveCachedResponse(res, cacheDir, req);
208
+ return;
209
+ }
210
+ } catch (cacheError) {
211
+ console.error(`[Proxy] Failed to read cache: ${cacheError.message}`);
212
+ }
213
+ }
214
+
215
+ console.log(`[Proxy] ✗ CACHE-ONLY: No cache available for ${cacheKey}`);
216
+ res.writeHead(404, { "Content-Type": "application/json" });
217
+ res.end(
218
+ JSON.stringify({
219
+ error: "Cache-only mode enabled but no cache found",
220
+ cacheKey,
221
+ suggestion: "Run tests locally first to populate cache",
222
+ }),
223
+ );
224
+ return;
225
+ }
226
+
212
227
  try {
213
228
  // Collect request body
214
229
  const requestChunks = [];
package/test/setup.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Global test setup for all browser tests
3
+ * This runs before every test to ensure clean state
4
+ */
5
+
6
+ import { beforeEach } from "vitest";
7
+ import {
8
+ globalRequestDeduplicator,
9
+ mediaCache,
10
+ } from "../src/elements/EFMedia/BaseMediaEngine.js";
11
+
12
+ // Clear global caches before each test to ensure isolation
13
+ beforeEach(() => {
14
+ globalRequestDeduplicator.clear();
15
+ mediaCache.clear();
16
+ });
@@ -46,4 +46,58 @@ export const assetMSWHandlers = [
46
46
  },
47
47
  });
48
48
  }),
49
+
50
+ // Asset ID API handlers - these are needed when tests set assetId properties
51
+ http.get("/api/v1/isobmff_files/:assetId/index", async () => {
52
+ const mockIndex = {
53
+ 0: {
54
+ duration: 10000,
55
+ timescale: 1000,
56
+ fragments: [
57
+ {
58
+ offset: 0,
59
+ size: 1024,
60
+ timestamp: 0,
61
+ duration: 10000,
62
+ },
63
+ ],
64
+ },
65
+ };
66
+
67
+ return HttpResponse.json(mockIndex, {
68
+ headers: {
69
+ "Content-Type": "application/json",
70
+ },
71
+ });
72
+ }),
73
+
74
+ http.get("/api/v1/isobmff_tracks/:assetId/:trackId", async ({ request }) => {
75
+ // Check if this is a range request
76
+ const rangeHeader = request.headers.get("range");
77
+
78
+ if (rangeHeader) {
79
+ // Return a mock MP4 segment with proper range headers
80
+ const mockData = new ArrayBuffer(1024); // 1KB mock data
81
+ return new HttpResponse(mockData, {
82
+ status: 206,
83
+ headers: {
84
+ "Content-Type": "video/mp4",
85
+ "Accept-Ranges": "bytes",
86
+ "Content-Range": rangeHeader,
87
+ "Content-Length": "1024",
88
+ },
89
+ });
90
+ }
91
+
92
+ // Return the full mock track
93
+ const mockData = new ArrayBuffer(1024);
94
+ return new HttpResponse(mockData, {
95
+ status: 200,
96
+ headers: {
97
+ "Content-Type": "video/mp4",
98
+ "Accept-Ranges": "bytes",
99
+ "Content-Length": "1024",
100
+ },
101
+ });
102
+ }),
49
103
  ];
package/test/useMSW.ts CHANGED
@@ -20,25 +20,18 @@ export const test = testBase.extend<{
20
20
  worker: typeof worker;
21
21
  }>({
22
22
  worker: [
23
- async ({}, use) => {
23
+ async ({ expect: _expect }, use) => {
24
24
  // Only start the worker once
25
25
  if (!workerStarted) {
26
26
  await worker.start({
27
- onUnhandledRequest: "bypass", // Allow unhandled requests to pass through to server
28
- quiet: true, // Enable logging to see what's being intercepted
27
+ onUnhandledRequest: "bypass", // Allow unhandled requests to pass through
29
28
  });
30
29
  workerStarted = true;
31
30
  }
32
31
 
33
- // Expose the worker object on the test's context
32
+ // Use the worker in the test
34
33
  await use(worker);
35
-
36
- // Remove any request handlers added in individual test cases
37
- // This prevents them from affecting unrelated tests
38
- worker.resetHandlers();
39
- },
40
- {
41
- auto: true, // Critical: Auto-start MSW for all tests
42
34
  },
35
+ { scope: "test" },
43
36
  ],
44
37
  });