@editframe/elements 0.20.0-beta.0 → 0.20.1-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/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.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/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/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
|
@@ -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
|
/**
|
|
@@ -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.1-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.1-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
|
);
|
|
@@ -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) {
|
|
@@ -107,7 +107,7 @@ describe("RenditionHelpers", () => {
|
|
|
107
107
|
expect,
|
|
108
108
|
}) => {
|
|
109
109
|
expect(() => getAudioRendition(mockMediaEngineWithoutAudio)).toThrow(
|
|
110
|
-
"
|
|
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
|
-
"
|
|
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("
|
|
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("
|
|
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("
|
|
24
|
+
throw new Error("No video track available in source");
|
|
25
25
|
}
|
|
26
26
|
return videoRendition;
|
|
27
27
|
};
|
|
@@ -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
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
|
|
Binary file
|
|
@@ -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": "
|
|
9
|
+
"content-length": "2055451",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
11
|
"x-powered-by": "Express"
|
|
12
12
|
},
|
|
Binary file
|
|
@@ -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": "
|
|
9
|
+
"content-length": "2192280",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
11
|
"x-powered-by": "Express"
|
|
12
12
|
},
|
|
Binary file
|
|
@@ -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": "
|
|
9
|
+
"content-length": "2115680",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
11
|
"x-powered-by": "Express"
|
|
12
12
|
},
|
|
Binary file
|
|
@@ -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": "
|
|
9
|
+
"content-length": "2217276",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
11
|
"x-powered-by": "Express"
|
|
12
12
|
},
|
|
Binary file
|
|
@@ -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": "
|
|
9
|
+
"content-length": "2025221",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
11
|
"x-powered-by": "Express"
|
|
12
12
|
},
|