@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.
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +1 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +7 -4
- package/dist/elements/EFMedia/JitMediaEngine.js +4 -13
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +2 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +2 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +3 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +6 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +3 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +2 -0
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +2 -2
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +2 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +2 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +2 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +2 -0
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +2 -0
- package/dist/elements/EFMedia.d.ts +2 -2
- package/dist/elements/EFTimegroup.js +7 -1
- package/package.json +2 -2
- package/src/elements/ContextProxiesController.ts +1 -0
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +3 -1
- package/src/elements/EFMedia/AssetMediaEngine.ts +17 -4
- package/src/elements/EFMedia/JitMediaEngine.ts +6 -20
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +6 -5
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +5 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +8 -5
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +5 -5
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +11 -12
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +7 -4
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -0
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +2 -2
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +2 -2
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +2 -2
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -1
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +5 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +5 -1
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +5 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +5 -0
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +11 -0
- package/src/elements/EFThumbnailStrip.ts +1 -1
- package/src/elements/EFTimegroup.ts +18 -5
- package/src/gui/EFControls.browsertest.ts +1 -1
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -1
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +1 -1
- 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)
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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:
|
|
17
|
-
segmentDurationMs:
|
|
18
|
-
segmentDurationsMs:
|
|
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,
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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;
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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();
|
|
22
|
+
signal.throwIfAborted();
|
|
23
23
|
const segment = await host.audioSegmentFetchTask.taskComplete;
|
|
24
|
-
signal.throwIfAborted();
|
|
24
|
+
signal.throwIfAborted();
|
|
25
|
+
|
|
25
26
|
if (!initSegment || !segment) {
|
|
26
|
-
throw new Error("
|
|
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();
|
|
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:
|
|
27
|
-
segmentDurationMs:
|
|
28
|
-
segmentDurationsMs:
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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("
|
|
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("
|
|
76
|
+
throw new Error("No audio track available in source");
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (!initSegment) {
|