@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
@@ -41,7 +41,7 @@ var AssetIdMediaEngine = class AssetIdMediaEngine extends AssetMediaEngine {
41
41
  return `${this.apiHost}/api/v1/isobmff_tracks/${this.assetId}/${trackId}`;
42
42
  }
43
43
  convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
44
- if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
44
+ if (!rendition.trackId) throw new Error("[convertToSegmentRelativeTimestamps] Track ID is required for asset metadata");
45
45
  const trackData = this.data[rendition.trackId];
46
46
  if (!trackData) throw new Error("Track not found");
47
47
  const segment = trackData.segments?.[segmentId];
@@ -63,7 +63,7 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
63
63
  return `/@ef-track/${this.src}?trackId=${trackId}&segmentId=${segmentId}`;
64
64
  }
65
65
  async fetchInitSegment(rendition, signal) {
66
- if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
66
+ if (!rendition.trackId) throw new Error("[fetchInitSegment] Track ID is required for asset metadata");
67
67
  const url = this.buildInitSegmentUrl(rendition.trackId);
68
68
  const initSegment = this.data[rendition.trackId]?.initSegment;
69
69
  if (!initSegment) throw new Error("Init segment not found");
@@ -71,7 +71,7 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
71
71
  return this.fetchMediaWithHeaders(url, headers, signal);
72
72
  }
73
73
  async fetchMediaSegment(segmentId, rendition, signal) {
74
- if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
74
+ if (!rendition.trackId) throw new Error("[fetchMediaSegment] Track ID is required for asset metadata");
75
75
  if (segmentId === void 0) throw new Error("Segment ID is not available");
76
76
  const url = this.buildMediaSegmentUrl(rendition.trackId, segmentId);
77
77
  const mediaSegment = this.data[rendition.trackId]?.segments[segmentId];
@@ -113,7 +113,10 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
113
113
  return segmentRanges;
114
114
  }
115
115
  computeSegmentId(seekTimeMs, rendition) {
116
- if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
116
+ if (!rendition.trackId) {
117
+ console.warn(`computeSegmentId: trackId not found for rendition ${JSON.stringify(rendition)}`);
118
+ throw new Error("[computeSegmentId] Track ID is required for asset metadata");
119
+ }
117
120
  const track = this.data[rendition.trackId];
118
121
  if (!track) throw new Error("Track not found");
119
122
  const { timescale, segments } = track;
@@ -159,7 +162,7 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
159
162
  }
160
163
  convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
161
164
  {
162
- if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
165
+ if (!rendition.trackId) throw new Error("[convertToSegmentRelativeTimestamps] Track ID is required for asset metadata");
163
166
  const trackData = this.data[rendition.trackId];
164
167
  if (!trackData) throw new Error("Track not found");
165
168
  const segment = trackData.segments?.[segmentId];
@@ -107,19 +107,10 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
107
107
  * Extract thumbnail canvases using same rendition priority as video playback for frame alignment
108
108
  */
109
109
  async extractThumbnails(timestamps) {
110
- let rendition;
111
- try {
112
- const mainRendition = this.getVideoRendition();
113
- if (mainRendition) rendition = mainRendition;
114
- else {
115
- const scrubRendition = this.getScrubVideoRendition();
116
- if (scrubRendition) rendition = scrubRendition;
117
- else throw new Error("No video rendition available");
118
- }
119
- } catch (error) {
120
- console.warn("JitMediaEngine: No video rendition available for thumbnails", error);
121
- return timestamps.map(() => null);
122
- }
110
+ const mainRendition = this.videoRendition;
111
+ const scrubRendition = this.getScrubVideoRendition();
112
+ const rendition = mainRendition || scrubRendition;
113
+ if (!rendition) return timestamps.map(() => null);
123
114
  return this.thumbnailExtractor.extractThumbnails(timestamps, rendition, this.durationMs);
124
115
  }
125
116
  convertToSegmentRelativeTimestamps(globalTimestamps, _segmentId, _rendition) {
@@ -22,6 +22,7 @@ const makeAudioBufferTask = (host) => {
22
22
  task: async ([seekTimeMs], { signal }) => {
23
23
  if (EF_RENDERING()) return currentState;
24
24
  const mediaEngine = await getLatestMediaEngine(host, signal);
25
+ if (!mediaEngine.audioRendition) return currentState;
25
26
  const engineConfig = mediaEngine.getBufferConfig();
26
27
  const bufferDurationMs = engineConfig.audioBufferDurationMs;
27
28
  const maxParallelFetches = engineConfig.maxAudioBufferFetches;
@@ -47,7 +48,7 @@ const makeAudioBufferTask = (host) => {
47
48
  getRendition: async () => {
48
49
  const mediaEngine$1 = await getLatestMediaEngine(host, signal);
49
50
  const audioRendition = mediaEngine$1.audioRendition;
50
- if (!audioRendition) throw new Error("Audio rendition not available");
51
+ if (!audioRendition) throw new Error("No audio track available in source");
51
52
  return audioRendition;
52
53
  },
53
54
  logError: console.error
@@ -51,6 +51,8 @@ function makeAudioFrequencyAnalysisTask(element) {
51
51
  ],
52
52
  task: async (_, { signal }) => {
53
53
  if (element.currentSourceTimeMs < 0) return null;
54
+ const mediaEngine = element.mediaEngineTask.value;
55
+ if (!mediaEngine?.audioRendition) return null;
54
56
  const currentTimeMs = element.currentSourceTimeMs;
55
57
  const frameIntervalMs = 1e3 / 30;
56
58
  const earliestFrameMs = currentTimeMs - (element.fftDecay - 1) * frameIntervalMs;
@@ -1,4 +1,4 @@
1
1
  import { Task } from '@lit/task';
2
2
  import { MediaEngine } from '../../../transcoding/types';
3
3
  import { EFMedia } from '../../EFMedia';
4
- export declare const makeAudioInitSegmentFetchTask: (host: EFMedia) => Task<readonly [MediaEngine | undefined], ArrayBuffer>;
4
+ export declare const makeAudioInitSegmentFetchTask: (host: EFMedia) => Task<readonly [MediaEngine | undefined], ArrayBuffer | undefined>;
@@ -9,7 +9,9 @@ const makeAudioInitSegmentFetchTask = (host) => {
9
9
  onComplete: (_value) => {},
10
10
  task: async ([_mediaEngine], { signal }) => {
11
11
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
- return mediaEngine.fetchInitSegment(mediaEngine.getAudioRendition(), signal);
12
+ const audioRendition = mediaEngine.audioRendition;
13
+ if (!audioRendition) return void 0;
14
+ return mediaEngine.fetchInitSegment(audioRendition, signal);
13
15
  }
14
16
  });
15
17
  };
@@ -13,7 +13,7 @@ const makeAudioInputTask = (host) => {
13
13
  signal.throwIfAborted();
14
14
  const segment = await host.audioSegmentFetchTask.taskComplete;
15
15
  signal.throwIfAborted();
16
- if (!initSegment || !segment) throw new Error("Init segment or segment is not available");
16
+ if (!initSegment || !segment) throw new Error("No audio track available in source");
17
17
  const mediaEngine = await host.mediaEngineTask.taskComplete;
18
18
  const audioRendition = mediaEngine?.audioRendition;
19
19
  const startTimeOffsetMs = audioRendition?.startTimeOffsetMs;
@@ -1,4 +1,4 @@
1
1
  import { Task } from '@lit/task';
2
2
  import { MediaEngine } from '../../../transcoding/types';
3
3
  import { EFMedia } from '../../EFMedia';
4
- export declare const makeAudioSegmentFetchTask: (host: EFMedia) => Task<readonly [MediaEngine | undefined, number | undefined], ArrayBuffer>;
4
+ export declare const makeAudioSegmentFetchTask: (host: EFMedia) => Task<readonly [MediaEngine | undefined, number | undefined], ArrayBuffer | undefined>;
@@ -9,19 +9,20 @@ const makeAudioSegmentFetchTask = (host) => {
9
9
  onComplete: (_value) => {},
10
10
  task: async (_, { signal }) => {
11
11
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
+ const audioRendition = mediaEngine.audioRendition;
13
+ if (!audioRendition) return void 0;
12
14
  const segmentId = await host.audioSegmentIdTask.taskComplete;
13
15
  if (segmentId === void 0) {
14
- const rendition = mediaEngine.audioRendition;
15
16
  const debugInfo = {
16
- hasRendition: !!rendition,
17
- segmentDurationMs: rendition?.segmentDurationMs,
18
- segmentDurationsMs: rendition?.segmentDurationsMs?.length || 0,
17
+ hasRendition: true,
18
+ segmentDurationMs: audioRendition.segmentDurationMs,
19
+ segmentDurationsMs: audioRendition.segmentDurationsMs?.length || 0,
19
20
  desiredSeekTimeMs: host.desiredSeekTimeMs,
20
21
  intrinsicDurationMs: host.intrinsicDurationMs
21
22
  };
22
23
  throw new Error(`Segment ID is not available for audio. Debug info: ${JSON.stringify(debugInfo)}`);
23
24
  }
24
- return mediaEngine.fetchMediaSegment(segmentId, mediaEngine.getAudioRendition(), signal);
25
+ return mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);
25
26
  }
26
27
  });
27
28
  };
@@ -10,7 +10,9 @@ const makeAudioSegmentIdTask = (host) => {
10
10
  task: async ([, targetSeekTimeMs], { signal }) => {
11
11
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
12
  signal.throwIfAborted();
13
- return mediaEngine.computeSegmentId(targetSeekTimeMs, mediaEngine.getAudioRendition());
13
+ const audioRendition = mediaEngine.audioRendition;
14
+ if (!audioRendition) return void 0;
15
+ return mediaEngine.computeSegmentId(targetSeekTimeMs, audioRendition);
14
16
  }
15
17
  });
16
18
  };
@@ -18,6 +18,8 @@ function makeAudioTimeDomainAnalysisTask(element) {
18
18
  ],
19
19
  task: async (_, { signal }) => {
20
20
  if (element.currentSourceTimeMs < 0) return null;
21
+ const mediaEngine = element.mediaEngineTask.value;
22
+ if (!mediaEngine?.audioRendition) return null;
21
23
  const currentTimeMs = element.currentSourceTimeMs;
22
24
  const frameIntervalMs = 1e3 / 30;
23
25
  const earliestFrameMs = currentTimeMs - (element.fftDecay - 1) * frameIntervalMs;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  const fetchAudioSegmentData = async (segmentIds, mediaEngine, signal) => {
6
6
  const audioRendition = mediaEngine.audioRendition;
7
- if (!audioRendition) throw new Error("Audio rendition not available");
7
+ if (!audioRendition) throw new Error("No audio track available in source");
8
8
  const segmentData = /* @__PURE__ */ new Map();
9
9
  const fetchPromises = segmentIds.map(async (segmentId) => {
10
10
  const arrayBuffer = await mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);
@@ -31,7 +31,7 @@ const fetchAudioSpanningTime = async (host, fromMs, toMs, signal) => {
31
31
  if (fromMs >= toMs || fromMs < 0) throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);
32
32
  const mediaEngine = await host.mediaEngineTask.taskComplete;
33
33
  const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
34
- if (!mediaEngine?.audioRendition) throw new Error("Audio rendition not available");
34
+ if (!mediaEngine?.audioRendition) throw new Error("No audio track available in source");
35
35
  if (!initSegment) throw new Error("Audio init segment is not available");
36
36
  const segmentRanges = mediaEngine.calculateAudioSegmentRange(fromMs, toMs, mediaEngine.audioRendition, host.intrinsicDurationMs || 1e4);
37
37
  if (segmentRanges.length === 0) throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);
@@ -11,7 +11,7 @@ const getLatestMediaEngine = async (host, signal) => {
11
11
  };
12
12
  const getVideoRendition = (mediaEngine) => {
13
13
  const videoRendition = mediaEngine.videoRendition;
14
- if (!videoRendition) throw new Error("Video rendition is not available");
14
+ if (!videoRendition) throw new Error("No video track available in source");
15
15
  return videoRendition;
16
16
  };
17
17
  /**
@@ -1,3 +1,4 @@
1
+ import { EF_RENDERING } from "../../../EF_RENDERING.js";
1
2
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
3
  import { Task } from "@lit/task";
3
4
  const makeScrubVideoInitSegmentFetchTask = (host) => {
@@ -8,6 +9,7 @@ const makeScrubVideoInitSegmentFetchTask = (host) => {
8
9
  },
9
10
  onComplete: (_value) => {},
10
11
  task: async ([_mediaEngine], { signal }) => {
12
+ if (EF_RENDERING()) return /* @__PURE__ */ new ArrayBuffer(0);
11
13
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
14
  const scrubRendition = mediaEngine.getScrubVideoRendition();
13
15
  if (!scrubRendition) throw new Error("No scrub rendition available");
@@ -1,3 +1,4 @@
1
+ import { EF_RENDERING } from "../../../EF_RENDERING.js";
1
2
  import { BufferedSeekingInput } from "../BufferedSeekingInput.js";
2
3
  import { EFMedia } from "../../EFMedia.js";
3
4
  import { Task } from "@lit/task";
@@ -9,6 +10,7 @@ const makeScrubVideoInputTask = (host) => {
9
10
  },
10
11
  onComplete: (_value) => {},
11
12
  task: async () => {
13
+ if (EF_RENDERING()) console.info("Scrub not available in rendering mode");
12
14
  const initSegment = await host.scrubVideoInitSegmentFetchTask.taskComplete;
13
15
  const segment = await host.scrubVideoSegmentFetchTask.taskComplete;
14
16
  if (!initSegment || !segment) throw new Error("Scrub init segment or segment is not available");
@@ -1,3 +1,4 @@
1
+ import { EF_RENDERING } from "../../../EF_RENDERING.js";
1
2
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
3
  import { Task } from "@lit/task";
3
4
  const makeScrubVideoSegmentFetchTask = (host) => {
@@ -8,6 +9,7 @@ const makeScrubVideoSegmentFetchTask = (host) => {
8
9
  },
9
10
  onComplete: (_value) => {},
10
11
  task: async (_, { signal }) => {
12
+ if (EF_RENDERING()) return /* @__PURE__ */ new ArrayBuffer(0);
11
13
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
14
  const segmentId = await host.scrubVideoSegmentIdTask.taskComplete;
13
15
  if (segmentId === void 0) throw new Error("Scrub segment ID is not available for video");
@@ -1,3 +1,4 @@
1
+ import { EF_RENDERING } from "../../../EF_RENDERING.js";
1
2
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
3
  import { Task } from "@lit/task";
3
4
  const makeScrubVideoSegmentIdTask = (host) => {
@@ -8,6 +9,7 @@ const makeScrubVideoSegmentIdTask = (host) => {
8
9
  },
9
10
  onComplete: (_value) => {},
10
11
  task: async ([, targetSeekTimeMs], { signal }) => {
12
+ if (EF_RENDERING()) return void 0;
11
13
  const mediaEngine = await getLatestMediaEngine(host, signal);
12
14
  signal.throwIfAborted();
13
15
  const scrubRendition = mediaEngine.getScrubVideoRendition();
@@ -1,3 +1,4 @@
1
+ import { EF_RENDERING } from "../../../EF_RENDERING.js";
1
2
  import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
2
3
  import { ScrubInputCache } from "./ScrubInputCache.js";
3
4
  import { Task } from "@lit/task";
@@ -13,6 +14,7 @@ const makeUnifiedVideoSeekTask = (host) => {
13
14
  task: async ([desiredSeekTimeMs], { signal }) => {
14
15
  const mediaEngine = await getLatestMediaEngine(host, signal);
15
16
  if (!mediaEngine) return void 0;
17
+ if (EF_RENDERING()) return await getMainVideoSample(host, mediaEngine, desiredSeekTimeMs, signal);
16
18
  const mainRendition = mediaEngine.videoRendition;
17
19
  if (mainRendition) {
18
20
  const mainSegmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, mainRendition);
@@ -58,8 +58,8 @@ export declare class EFMedia extends EFMedia_base {
58
58
  get urlGenerator(): UrlGenerator;
59
59
  mediaEngineTask: import('@lit/task').Task<readonly [string, string | null], import('../transcoding/types/index.ts').MediaEngine>;
60
60
  audioSegmentIdTask: import('@lit/task').Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number], number | undefined>;
61
- audioInitSegmentFetchTask: import('@lit/task').Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined], ArrayBuffer>;
62
- audioSegmentFetchTask: import('@lit/task').Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number | undefined], ArrayBuffer>;
61
+ audioInitSegmentFetchTask: import('@lit/task').Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined], ArrayBuffer | undefined>;
62
+ audioSegmentFetchTask: import('@lit/task').Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number | undefined], ArrayBuffer | undefined>;
63
63
  audioInputTask: import('./EFMedia/shared/MediaTaskUtils.ts').InputTask;
64
64
  audioSeekTask: import('@lit/task').Task<readonly [number, import('./EFMedia/BufferedSeekingInput.ts').BufferedSeekingInput | undefined], import('mediabunny').VideoSample | undefined>;
65
65
  audioBufferTask: import('@lit/task').Task<readonly [number], import('./EFMedia/audioTasks/makeAudioBufferTask.ts').AudioBufferState>;
@@ -366,7 +366,13 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
366
366
  const sourceInMs = mediaElement.sourceInMs || mediaElement.trimStartMs || 0;
367
367
  const mediaSourceFromMs = mediaLocalFromMs + sourceInMs;
368
368
  const mediaSourceToMs = mediaLocalToMs + sourceInMs;
369
- const audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, abortController.signal);
369
+ let audio;
370
+ try {
371
+ audio = await mediaElement.fetchAudioSpanningTime(mediaSourceFromMs, mediaSourceToMs, abortController.signal);
372
+ } catch (error) {
373
+ if (error instanceof Error && error.message.includes("No audio track available")) return;
374
+ throw error;
375
+ }
370
376
  if (!audio) throw new Error("Failed to fetch audio");
371
377
  const bufferSource = audioContext.createBufferSource();
372
378
  bufferSource.buffer = await audioContext.decodeAudioData(await audio.blob.arrayBuffer());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/elements",
3
- "version": "0.20.0-beta.0",
3
+ "version": "0.20.2-beta.0",
4
4
  "description": "",
5
5
  "exports": {
6
6
  ".": {
@@ -27,7 +27,7 @@
27
27
  "license": "UNLICENSED",
28
28
  "dependencies": {
29
29
  "@bramus/style-observer": "^1.3.0",
30
- "@editframe/assets": "0.20.0-beta.0",
30
+ "@editframe/assets": "0.20.2-beta.0",
31
31
  "@lit/context": "^1.1.2",
32
32
  "@lit/task": "^1.0.1",
33
33
  "d3": "^7.9.0",
@@ -110,6 +110,7 @@ export class ContextProxyController implements ReactiveController {
110
110
  try {
111
111
  const newEvent = new ContextEvent(
112
112
  contextEvent.context,
113
+ // @ts-ignore (this fails a typecheck but works)
113
114
  contextEvent.callback,
114
115
  contextEvent.subscribe,
115
116
  );
@@ -87,7 +87,9 @@ export class AssetIdMediaEngine
87
87
  rendition: VideoRendition,
88
88
  ): number[] {
89
89
  if (!rendition.trackId) {
90
- throw new Error("Track ID is required for asset metadata");
90
+ throw new Error(
91
+ "[convertToSegmentRelativeTimestamps] Track ID is required for asset metadata",
92
+ );
91
93
  }
92
94
  // For AssetMediaEngine, we need to calculate the actual segment start time
93
95
  // using the precise segment boundaries from the track fragment index
@@ -110,7 +110,9 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
110
110
  signal: AbortSignal,
111
111
  ) {
112
112
  if (!rendition.trackId) {
113
- throw new Error("Track ID is required for asset metadata");
113
+ throw new Error(
114
+ "[fetchInitSegment] Track ID is required for asset metadata",
115
+ );
114
116
  }
115
117
  const url = this.buildInitSegmentUrl(rendition.trackId);
116
118
  const initSegment = this.data[rendition.trackId]?.initSegment;
@@ -132,7 +134,9 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
132
134
  signal?: AbortSignal,
133
135
  ) {
134
136
  if (!rendition.trackId) {
135
- throw new Error("Track ID is required for asset metadata");
137
+ throw new Error(
138
+ "[fetchMediaSegment] Track ID is required for asset metadata",
139
+ );
136
140
  }
137
141
  if (segmentId === undefined) {
138
142
  throw new Error("Segment ID is not available");
@@ -217,7 +221,14 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
217
221
 
218
222
  computeSegmentId(seekTimeMs: number, rendition: MediaRendition) {
219
223
  if (!rendition.trackId) {
220
- throw new Error("Track ID is required for asset metadata");
224
+ console.warn(
225
+ `computeSegmentId: trackId not found for rendition ${JSON.stringify(
226
+ rendition,
227
+ )}`,
228
+ );
229
+ throw new Error(
230
+ "[computeSegmentId] Track ID is required for asset metadata",
231
+ );
221
232
  }
222
233
  const track = this.data[rendition.trackId];
223
234
  if (!track) {
@@ -311,7 +322,9 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
311
322
  // This is because Asset segments are independent timeline fragments
312
323
 
313
324
  if (!rendition.trackId) {
314
- throw new Error("Track ID is required for asset metadata");
325
+ throw new Error(
326
+ "[convertToSegmentRelativeTimestamps] Track ID is required for asset metadata",
327
+ );
315
328
  }
316
329
  // For AssetMediaEngine, we need to calculate the actual segment start time
317
330
  // using the precise segment boundaries from the track fragment index
@@ -215,29 +215,15 @@ export class JitMediaEngine extends BaseMediaEngine implements MediaEngine {
215
215
  async extractThumbnails(
216
216
  timestamps: number[],
217
217
  ): Promise<(ThumbnailResult | null)[]> {
218
- // Use same rendition priority as video: try main rendition first for frame alignment
219
- let rendition: VideoRendition;
220
- try {
221
- const mainRendition = this.getVideoRendition();
222
- if (mainRendition) {
223
- rendition = mainRendition;
224
- } else {
225
- const scrubRendition = this.getScrubVideoRendition();
226
- if (scrubRendition) {
227
- rendition = scrubRendition;
228
- } else {
229
- throw new Error("No video rendition available");
230
- }
231
- }
232
- } catch (error) {
233
- console.warn(
234
- "JitMediaEngine: No video rendition available for thumbnails",
235
- error,
236
- );
218
+ const mainRendition = this.videoRendition;
219
+ const scrubRendition = this.getScrubVideoRendition();
220
+
221
+ const rendition = mainRendition || scrubRendition;
222
+
223
+ if (!rendition) {
237
224
  return timestamps.map(() => null);
238
225
  }
239
226
 
240
- // Use shared thumbnail extraction logic
241
227
  return this.thumbnailExtractor.extractThumbnails(
242
228
  timestamps,
243
229
  rendition,
@@ -42,13 +42,15 @@ export const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {
42
42
  task: async ([seekTimeMs], { signal }) => {
43
43
  // Skip buffering entirely in rendering mode
44
44
  if (EF_RENDERING()) {
45
- return currentState; // Return existing state without any buffering activity
45
+ return currentState;
46
46
  }
47
47
 
48
- // Get media engine to potentially override buffer configuration
49
48
  const mediaEngine = await getLatestMediaEngine(host, signal);
50
49
 
51
- // Use media engine's buffer config, falling back to host properties
50
+ if (!mediaEngine.audioRendition) {
51
+ return currentState;
52
+ }
53
+
52
54
  const engineConfig = mediaEngine.getBufferConfig();
53
55
  const bufferDurationMs = engineConfig.audioBufferDurationMs;
54
56
  const maxParallelFetches = engineConfig.maxAudioBufferFetches;
@@ -85,11 +87,10 @@ export const makeAudioBufferTask = (host: EFMedia): AudioBufferTask => {
85
87
  return mediaEngine.isSegmentCached(segmentId, rendition);
86
88
  },
87
89
  getRendition: async () => {
88
- // Get real audio rendition from media engine
89
90
  const mediaEngine = await getLatestMediaEngine(host, signal);
90
91
  const audioRendition = mediaEngine.audioRendition;
91
92
  if (!audioRendition) {
92
- throw new Error("Audio rendition not available");
93
+ throw new Error("No audio track available in source");
93
94
  }
94
95
  return audioRendition;
95
96
  },
@@ -95,6 +95,11 @@ export function makeAudioFrequencyAnalysisTask(element: EFMedia) {
95
95
  task: async (_, { signal }) => {
96
96
  if (element.currentSourceTimeMs < 0) return null;
97
97
 
98
+ const mediaEngine = element.mediaEngineTask.value;
99
+ if (!mediaEngine?.audioRendition) {
100
+ return null;
101
+ }
102
+
98
103
  const currentTimeMs = element.currentSourceTimeMs;
99
104
 
100
105
  // Calculate exact audio window needed based on fftDecay and frame timing
@@ -5,7 +5,7 @@ import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask";
5
5
 
6
6
  export const makeAudioInitSegmentFetchTask = (
7
7
  host: EFMedia,
8
- ): Task<readonly [MediaEngine | undefined], ArrayBuffer> => {
8
+ ): Task<readonly [MediaEngine | undefined], ArrayBuffer | undefined> => {
9
9
  return new Task(host, {
10
10
  args: () => [host.mediaEngineTask.value] as const,
11
11
  onError: (error) => {
@@ -14,10 +14,13 @@ export const makeAudioInitSegmentFetchTask = (
14
14
  onComplete: (_value) => {},
15
15
  task: async ([_mediaEngine], { signal }) => {
16
16
  const mediaEngine = await getLatestMediaEngine(host, signal);
17
- return mediaEngine.fetchInitSegment(
18
- mediaEngine.getAudioRendition(),
19
- signal,
20
- );
17
+
18
+ const audioRendition = mediaEngine.audioRendition;
19
+ if (!audioRendition) {
20
+ return undefined;
21
+ }
22
+
23
+ return mediaEngine.fetchInitSegment(audioRendition, signal);
21
24
  },
22
25
  });
23
26
  };
@@ -19,20 +19,20 @@ export const makeAudioInputTask = (host: EFMedia): InputTask => {
19
19
  onComplete: (_value) => {},
20
20
  task: async (_, { signal }) => {
21
21
  const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
22
- signal.throwIfAborted(); // Abort if a new seek started
22
+ signal.throwIfAborted();
23
23
  const segment = await host.audioSegmentFetchTask.taskComplete;
24
- signal.throwIfAborted(); // Abort if a new seek started
24
+ signal.throwIfAborted();
25
+
25
26
  if (!initSegment || !segment) {
26
- throw new Error("Init segment or segment is not available");
27
+ throw new Error("No audio track available in source");
27
28
  }
28
29
 
29
- // Get startTimeOffsetMs from the audio rendition if available
30
30
  const mediaEngine = await host.mediaEngineTask.taskComplete;
31
31
  const audioRendition = mediaEngine?.audioRendition;
32
32
  const startTimeOffsetMs = audioRendition?.startTimeOffsetMs;
33
33
 
34
34
  const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
35
- signal.throwIfAborted(); // Abort if a new seek started
35
+ signal.throwIfAborted();
36
36
  return new BufferedSeekingInput(arrayBuffer, {
37
37
  videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
38
38
  audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
@@ -7,7 +7,7 @@ export const makeAudioSegmentFetchTask = (
7
7
  host: EFMedia,
8
8
  ): Task<
9
9
  readonly [MediaEngine | undefined, number | undefined],
10
- ArrayBuffer
10
+ ArrayBuffer | undefined
11
11
  > => {
12
12
  return new Task(host, {
13
13
  args: () =>
@@ -18,14 +18,18 @@ export const makeAudioSegmentFetchTask = (
18
18
  onComplete: (_value) => {},
19
19
  task: async (_, { signal }) => {
20
20
  const mediaEngine = await getLatestMediaEngine(host, signal);
21
+
22
+ const audioRendition = mediaEngine.audioRendition;
23
+ if (!audioRendition) {
24
+ return undefined;
25
+ }
26
+
21
27
  const segmentId = await host.audioSegmentIdTask.taskComplete;
22
28
  if (segmentId === undefined) {
23
- // Provide more context in the error to help with debugging
24
- const rendition = mediaEngine.audioRendition;
25
29
  const debugInfo = {
26
- hasRendition: !!rendition,
27
- segmentDurationMs: rendition?.segmentDurationMs,
28
- segmentDurationsMs: rendition?.segmentDurationsMs?.length || 0,
30
+ hasRendition: true,
31
+ segmentDurationMs: audioRendition.segmentDurationMs,
32
+ segmentDurationsMs: audioRendition.segmentDurationsMs?.length || 0,
29
33
  desiredSeekTimeMs: host.desiredSeekTimeMs,
30
34
  intrinsicDurationMs: host.intrinsicDurationMs,
31
35
  };
@@ -34,12 +38,7 @@ export const makeAudioSegmentFetchTask = (
34
38
  );
35
39
  }
36
40
 
37
- // SIMPLIFIED: Direct call to mediaEngine - deduplication is built-in
38
- return mediaEngine.fetchMediaSegment(
39
- segmentId,
40
- mediaEngine.getAudioRendition(),
41
- signal,
42
- );
41
+ return mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);
43
42
  },
44
43
  });
45
44
  };
@@ -15,10 +15,13 @@ export const makeAudioSegmentIdTask = (
15
15
  task: async ([, targetSeekTimeMs], { signal }) => {
16
16
  const mediaEngine = await getLatestMediaEngine(host, signal);
17
17
  signal.throwIfAborted(); // Abort if a new seek started
18
- return mediaEngine.computeSegmentId(
19
- targetSeekTimeMs, // Use captured value, not host.desiredSeekTimeMs
20
- mediaEngine.getAudioRendition(),
21
- );
18
+
19
+ const audioRendition = mediaEngine.audioRendition;
20
+ if (!audioRendition) {
21
+ return undefined;
22
+ }
23
+
24
+ return mediaEngine.computeSegmentId(targetSeekTimeMs, audioRendition);
22
25
  },
23
26
  });
24
27
  };
@@ -27,6 +27,11 @@ export function makeAudioTimeDomainAnalysisTask(element: EFMedia) {
27
27
  task: async (_, { signal }) => {
28
28
  if (element.currentSourceTimeMs < 0) return null;
29
29
 
30
+ const mediaEngine = element.mediaEngineTask.value;
31
+ if (!mediaEngine?.audioRendition) {
32
+ return null;
33
+ }
34
+
30
35
  const currentTimeMs = element.currentSourceTimeMs;
31
36
 
32
37
  // Calculate exact audio window needed based on fftDecay and frame timing
@@ -16,7 +16,7 @@ const fetchAudioSegmentData = async (
16
16
  ): Promise<Map<number, ArrayBuffer>> => {
17
17
  const audioRendition = mediaEngine.audioRendition;
18
18
  if (!audioRendition) {
19
- throw new Error("Audio rendition not available");
19
+ throw new Error("No audio track available in source");
20
20
  }
21
21
 
22
22
  const segmentData = new Map<number, ArrayBuffer>();
@@ -73,7 +73,7 @@ export const fetchAudioSpanningTime = async (
73
73
  const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
74
74
 
75
75
  if (!mediaEngine?.audioRendition) {
76
- throw new Error("Audio rendition not available");
76
+ throw new Error("No audio track available in source");
77
77
  }
78
78
 
79
79
  if (!initSegment) {