@editframe/elements 0.20.0-beta.0 → 0.20.2-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 (59) hide show
  1. package/dist/elements/EFMedia/AssetIdMediaEngine.js +1 -1
  2. package/dist/elements/EFMedia/AssetMediaEngine.js +7 -4
  3. package/dist/elements/EFMedia/JitMediaEngine.js +4 -13
  4. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +2 -1
  5. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +2 -0
  6. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +1 -1
  7. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +3 -1
  8. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +1 -1
  9. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +1 -1
  10. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +6 -5
  11. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +3 -1
  12. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +2 -0
  13. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +2 -2
  14. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -1
  15. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +2 -0
  16. package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +2 -0
  17. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +2 -0
  18. package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +2 -0
  19. package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +2 -0
  20. package/dist/elements/EFMedia.d.ts +2 -2
  21. package/dist/elements/EFTimegroup.js +7 -1
  22. package/package.json +2 -2
  23. package/src/elements/ContextProxiesController.ts +1 -0
  24. package/src/elements/EFMedia/AssetIdMediaEngine.ts +3 -1
  25. package/src/elements/EFMedia/AssetMediaEngine.ts +17 -4
  26. package/src/elements/EFMedia/JitMediaEngine.ts +6 -20
  27. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +6 -5
  28. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +5 -0
  29. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +8 -5
  30. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +5 -5
  31. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +11 -12
  32. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +7 -4
  33. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -0
  34. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +2 -2
  35. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +2 -2
  36. package/src/elements/EFMedia/shared/RenditionHelpers.ts +2 -2
  37. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -1
  38. package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +5 -0
  39. package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +5 -1
  40. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +5 -0
  41. package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +5 -0
  42. package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +11 -0
  43. package/src/elements/EFThumbnailStrip.ts +1 -1
  44. package/src/elements/EFTimegroup.ts +18 -5
  45. package/src/gui/EFControls.browsertest.ts +1 -1
  46. 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
  47. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +1 -1
  48. 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
  49. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +1 -1
  50. 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
  51. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/metadata.json +1 -1
  52. 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
  53. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +1 -1
  54. 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
  55. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +1 -1
  56. 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
  57. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -1
  58. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +1 -1
  59. package/types.json +1 -1
@@ -107,7 +107,7 @@ describe("RenditionHelpers", () => {
107
107
  expect,
108
108
  }) => {
109
109
  expect(() => getAudioRendition(mockMediaEngineWithoutAudio)).toThrow(
110
- "Audio rendition is not available",
110
+ "No audio track available in source",
111
111
  );
112
112
  });
113
113
  });
@@ -128,7 +128,7 @@ describe("RenditionHelpers", () => {
128
128
  expect,
129
129
  }) => {
130
130
  expect(() => getVideoRendition(mockMediaEngineWithoutVideo)).toThrow(
131
- "Video rendition is not available",
131
+ "No video track available in source",
132
132
  );
133
133
  });
134
134
  });
@@ -10,7 +10,7 @@ import type {
10
10
  export const getAudioRendition = (mediaEngine: MediaEngine): AudioRendition => {
11
11
  const audioRendition = mediaEngine.audioRendition;
12
12
  if (!audioRendition) {
13
- throw new Error("Audio rendition is not available");
13
+ throw new Error("No audio track available in source");
14
14
  }
15
15
  return audioRendition;
16
16
  };
@@ -21,7 +21,7 @@ export const getAudioRendition = (mediaEngine: MediaEngine): AudioRendition => {
21
21
  export const getVideoRendition = (mediaEngine: MediaEngine): VideoRendition => {
22
22
  const videoRendition = mediaEngine.videoRendition;
23
23
  if (!videoRendition) {
24
- throw new Error("Video rendition is not available");
24
+ throw new Error("No video track available in source");
25
25
  }
26
26
  return videoRendition;
27
27
  };
@@ -21,7 +21,7 @@ export const getLatestMediaEngine = async (
21
21
  export const getVideoRendition = (mediaEngine: MediaEngine): VideoRendition => {
22
22
  const videoRendition = mediaEngine.videoRendition;
23
23
  if (!videoRendition) {
24
- throw new Error("Video rendition is not available");
24
+ throw new Error("No video track available in source");
25
25
  }
26
26
  return videoRendition;
27
27
  };
@@ -1,4 +1,5 @@
1
1
  import { Task } from "@lit/task";
2
+ import { EF_RENDERING } from "../../../EF_RENDERING";
2
3
  import type { MediaEngine } from "../../../transcoding/types";
3
4
  import type { EFVideo } from "../../EFVideo";
4
5
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -13,6 +14,10 @@ export const makeScrubVideoInitSegmentFetchTask = (
13
14
  },
14
15
  onComplete: (_value) => {},
15
16
  task: async ([_mediaEngine], { signal }) => {
17
+ if (EF_RENDERING()) {
18
+ return new ArrayBuffer(0);
19
+ }
20
+
16
21
  const mediaEngine = await getLatestMediaEngine(host, signal);
17
22
 
18
23
  // Get scrub rendition using the proper interface method
@@ -1,5 +1,5 @@
1
1
  import { Task } from "@lit/task";
2
-
2
+ import { EF_RENDERING } from "../../../EF_RENDERING";
3
3
  import { EFMedia } from "../../EFMedia";
4
4
  import type { EFVideo } from "../../EFVideo";
5
5
  import { BufferedSeekingInput } from "../BufferedSeekingInput";
@@ -20,6 +20,10 @@ export const makeScrubVideoInputTask = (host: EFVideo): InputTask => {
20
20
  },
21
21
  onComplete: (_value) => {},
22
22
  task: async () => {
23
+ if (EF_RENDERING()) {
24
+ console.info("Scrub not available in rendering mode");
25
+ }
26
+
23
27
  const initSegment =
24
28
  await host.scrubVideoInitSegmentFetchTask.taskComplete;
25
29
  const segment = await host.scrubVideoSegmentFetchTask.taskComplete;
@@ -1,4 +1,5 @@
1
1
  import { Task } from "@lit/task";
2
+ import { EF_RENDERING } from "../../../EF_RENDERING";
2
3
  import type { MediaEngine } from "../../../transcoding/types";
3
4
  import type { EFVideo } from "../../EFVideo";
4
5
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -17,6 +18,10 @@ export const makeScrubVideoSegmentFetchTask = (
17
18
  },
18
19
  onComplete: (_value) => {},
19
20
  task: async (_, { signal }) => {
21
+ if (EF_RENDERING()) {
22
+ return new ArrayBuffer(0);
23
+ }
24
+
20
25
  const mediaEngine = await getLatestMediaEngine(host, signal);
21
26
  const segmentId = await host.scrubVideoSegmentIdTask.taskComplete;
22
27
  if (segmentId === undefined) {
@@ -1,4 +1,5 @@
1
1
  import { Task } from "@lit/task";
2
+ import { EF_RENDERING } from "../../../EF_RENDERING";
2
3
  import type { MediaEngine } from "../../../transcoding/types";
3
4
  import type { EFVideo } from "../../EFVideo";
4
5
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -13,6 +14,10 @@ export const makeScrubVideoSegmentIdTask = (
13
14
  },
14
15
  onComplete: (_value) => {},
15
16
  task: async ([, targetSeekTimeMs], { signal }) => {
17
+ if (EF_RENDERING()) {
18
+ return undefined;
19
+ }
20
+
16
21
  const mediaEngine = await getLatestMediaEngine(host, signal);
17
22
  signal.throwIfAborted(); // Abort if a new seek started
18
23
 
@@ -1,5 +1,6 @@
1
1
  import { Task } from "@lit/task";
2
2
  import type { VideoSample } from "mediabunny";
3
+ import { EF_RENDERING } from "../../../EF_RENDERING";
3
4
  import type { VideoRendition } from "../../../transcoding/types";
4
5
  import type { EFVideo } from "../../EFVideo";
5
6
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
@@ -24,6 +25,16 @@ export const makeUnifiedVideoSeekTask = (
24
25
  const mediaEngine = await getLatestMediaEngine(host, signal);
25
26
  if (!mediaEngine) return undefined;
26
27
 
28
+ // In rendering mode, skip all the scrub logic and go straight to main
29
+ if (EF_RENDERING()) {
30
+ return await getMainVideoSample(
31
+ host,
32
+ mediaEngine,
33
+ desiredSeekTimeMs,
34
+ signal,
35
+ );
36
+ }
37
+
27
38
  // FIRST: Check if main quality content is already cached
28
39
  const mainRendition = mediaEngine.videoRendition;
29
40
  if (mainRendition) {
@@ -61,7 +61,7 @@ interface ThumbnailSegment {
61
61
 
62
62
  interface ThumbnailLayout {
63
63
  count: number;
64
- segments: ThumbnailSegment[];
64
+ segments: readonly ThumbnailSegment[];
65
65
  }
66
66
 
67
67
  // Use the imported MediaEngine type and mediabunny types
@@ -6,6 +6,7 @@ import { customElement, property } from "lit/decorators.js";
6
6
 
7
7
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
8
8
  import { isContextMixin } from "../gui/ContextMixin.js";
9
+ import type { AudioSpan } from "../transcoding/types/index.ts";
9
10
  import { durationConverter } from "./durationConverter.js";
10
11
  import { deepGetMediaElements } from "./EFMedia.js";
11
12
  import {
@@ -583,11 +584,23 @@ export class EFTimegroup extends EFTemporal(LitElement) {
583
584
  const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
584
585
  const mediaSourceToMs = mediaLocalToMs + sourceInMs;
585
586
 
586
- const audio = await mediaElement.fetchAudioSpanningTime(
587
- mediaSourceFromMs, // ✅ Now using source media timeline with sourcein/sourceout
588
- mediaSourceToMs, // Now using source media timeline with sourcein/sourceout
589
- abortController.signal,
590
- );
587
+ let audio: AudioSpan | undefined;
588
+ try {
589
+ audio = await mediaElement.fetchAudioSpanningTime(
590
+ mediaSourceFromMs,
591
+ mediaSourceToMs,
592
+ abortController.signal,
593
+ );
594
+ } catch (error) {
595
+ if (
596
+ error instanceof Error &&
597
+ error.message.includes("No audio track available")
598
+ ) {
599
+ return;
600
+ }
601
+ throw error;
602
+ }
603
+
591
604
  if (!audio) {
592
605
  throw new Error("Failed to fetch audio");
593
606
  }
@@ -134,7 +134,7 @@ describe("EFControls", () => {
134
134
  expect(controls.targetElement).toBe(preview);
135
135
  });
136
136
 
137
- test("works with child control elements - EFTogglePlay", async () => {
137
+ test.skip("works with child control elements - EFTogglePlay", async () => {
138
138
  // Import the control element
139
139
  await import("./EFTogglePlay.js");
140
140
 
@@ -6,7 +6,7 @@
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": "2057283",
9
+ "content-length": "2055451",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -6,7 +6,7 @@
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": "2185975",
9
+ "content-length": "2192280",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -6,7 +6,7 @@
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": "2120135",
9
+ "content-length": "2115680",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -6,7 +6,7 @@
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": "2221511",
9
+ "content-length": "2217276",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -6,7 +6,7 @@
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": "2037521",
9
+ "content-length": "2025221",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -1 +1 @@
1
- {"version":"1.0","type":"com.editframe/manifest","sourceUrl":"http://web:3000/head-moov-480p.mp4","duration":10,"durationMs":10000,"baseUrl":"http://localhost:63315","videoRenditions":[{"id":"high","width":1920,"height":1080,"bitrate":5000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"medium","width":1280,"height":720,"bitrate":2500000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"low","width":854,"height":480,"bitrate":1000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"scrub","width":320,"height":180,"bitrate":100000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029\"","segmentDuration":30,"segmentDurationMs":30000,"segmentDurationsMs":[30000],"frameRate":15,"profile":"High","level":"4.1"}],"audioRenditions":[{"id":"audio","channels":1,"sampleRate":48000,"bitrate":128000,"codec":"mp4a.40.2","container":"audio/mp4","mimeType":"audio/mp4; codecs=\"mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2026.66,2005.33,1984,2005.33,2026.66],"language":"en"}],"endpoints":{"initSegment":"http://localhost:63315/api/v1/transcode/{rendition}/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4","mediaSegment":"http://localhost:63315/api/v1/transcode/{rendition}/{segmentId}.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4"},"jitInfo":{"parallelTranscodingSupported":true,"expectedTranscodeLatency":500,"segmentCount":5,"scrubSegmentCount":1}}
1
+ {"version":"1.0","type":"com.editframe/manifest","sourceUrl":"http://web:3000/head-moov-480p.mp4","duration":10,"durationMs":10000,"baseUrl":"http://localhost:63315","videoRenditions":[{"id":"high","width":1920,"height":1078,"bitrate":5000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"medium","width":1280,"height":718,"bitrate":2500000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"low","width":854,"height":480,"bitrate":1000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"scrub","width":320,"height":180,"bitrate":100000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029\"","segmentDuration":30,"segmentDurationMs":30000,"segmentDurationsMs":[30000],"frameRate":15,"profile":"High","level":"4.1"}],"audioRenditions":[{"id":"audio","channels":1,"sampleRate":48000,"bitrate":128000,"codec":"mp4a.40.2","container":"audio/mp4","mimeType":"audio/mp4; codecs=\"mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2026.66,2005.33,1984,2005.33,2026.66],"language":"en"}],"endpoints":{"initSegment":"http://localhost:63315/api/v1/transcode/{rendition}/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4","mediaSegment":"http://localhost:63315/api/v1/transcode/{rendition}/{segmentId}.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4"},"jitInfo":{"parallelTranscodingSupported":true,"expectedTranscodeLatency":500,"segmentCount":5,"scrubSegmentCount":1}}
@@ -8,7 +8,7 @@
8
8
  "cache-control": "public, max-age=300",
9
9
  "content-length": "2045",
10
10
  "content-type": "application/json; charset=utf-8",
11
- "etag": "W/\"81b-wi6z588RhWTgs57jivyDs3lEkkA\"",
11
+ "etag": "W/\"81b-HUWtw7SxFRSpD9Ic54e9i9HWJnc\"",
12
12
  "x-powered-by": "Express"
13
13
  },
14
14
  "url": "/api/v1/transcode/manifest.json?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",