@editframe/elements 0.20.3-beta.0 → 0.20.4-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/EF_FRAMEGEN.js +3 -20
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +4 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +8 -4
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +10 -2
- package/dist/elements/EFMedia/BaseMediaEngine.js +8 -2
- package/dist/elements/EFMedia/JitMediaEngine.js +13 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +0 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +5 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +2 -12
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +5 -2
- package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
- package/dist/elements/EFMedia/shared/BufferUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +3 -1
- package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +1 -9
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +1 -2
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -6
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +2 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -2
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +0 -2
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -2
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +0 -2
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +4 -5
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +2 -2
- package/dist/elements/EFMedia.d.ts +2 -1
- package/dist/elements/EFMedia.js +1 -0
- package/dist/elements/EFTimegroup.js +1 -1
- package/dist/transcoding/types/index.d.ts +6 -4
- package/package.json +2 -2
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +6 -4
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +25 -23
- package/src/elements/EFMedia/AssetMediaEngine.ts +16 -6
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +94 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +10 -8
- package/src/elements/EFMedia/JitMediaEngine.ts +20 -6
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +5 -2
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -5
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +2 -1
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +11 -5
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +4 -16
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +4 -2
- package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +95 -0
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -6
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +5 -4
- package/src/elements/EFMedia/shared/BufferUtils.ts +7 -3
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +1 -1
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +41 -42
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -23
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -9
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +3 -2
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +1 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +10 -19
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +2 -5
- package/src/elements/EFMedia.ts +2 -1
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +2 -1
- package/src/elements/EFTimegroup.ts +1 -1
- package/src/transcoding/types/index.ts +6 -4
- package/types.json +1 -1
package/dist/EF_FRAMEGEN.js
CHANGED
|
@@ -92,7 +92,9 @@ var EFFramegen = class {
|
|
|
92
92
|
const BRIDGE = this.BRIDGE;
|
|
93
93
|
if (!BRIDGE) throw new Error("No BRIDGE when attempting to connect to bridge");
|
|
94
94
|
BRIDGE.onInitialize(async (renderOptions) => {
|
|
95
|
-
await this.initialize(renderOptions)
|
|
95
|
+
await this.initialize(renderOptions).catch((error) => {
|
|
96
|
+
console.error("[EF_FRAMEGEN.connectToBridge] error initializing", error);
|
|
97
|
+
});
|
|
96
98
|
BRIDGE.initialized();
|
|
97
99
|
});
|
|
98
100
|
BRIDGE.onBeginFrame((frameNumber, isLast) => {
|
|
@@ -107,20 +109,6 @@ var EFFramegen = class {
|
|
|
107
109
|
}
|
|
108
110
|
async initialize(renderOptions) {
|
|
109
111
|
this.renderOptions = renderOptions;
|
|
110
|
-
const alignedFromMs = renderOptions.encoderOptions.alignedFromUs / 1e3;
|
|
111
|
-
const alignedToMs = renderOptions.encoderOptions.alignedToUs / 1e3;
|
|
112
|
-
const alignedDurationMs = alignedToMs - alignedFromMs;
|
|
113
|
-
await this.syncLog("[EF_FRAMEGEN.initialize] Aligned boundary parameters:", {
|
|
114
|
-
alignedFromUs: renderOptions.encoderOptions.alignedFromUs,
|
|
115
|
-
alignedToUs: renderOptions.encoderOptions.alignedToUs,
|
|
116
|
-
alignedFromMs: alignedFromMs.toFixed(3),
|
|
117
|
-
alignedToMs: alignedToMs.toFixed(3),
|
|
118
|
-
alignedDurationMs: alignedDurationMs.toFixed(3),
|
|
119
|
-
sequenceNumber: renderOptions.encoderOptions.sequenceNumber,
|
|
120
|
-
originalFromMs: renderOptions.encoderOptions.fromMs,
|
|
121
|
-
originalToMs: renderOptions.encoderOptions.toMs,
|
|
122
|
-
originalDurationMs: (renderOptions.encoderOptions.toMs - renderOptions.encoderOptions.fromMs).toFixed(3)
|
|
123
|
-
});
|
|
124
112
|
const workbench = document.querySelector("ef-workbench");
|
|
125
113
|
if (!workbench) throw new Error("No workbench found");
|
|
126
114
|
workbench.rendering = true;
|
|
@@ -147,11 +135,6 @@ var EFFramegen = class {
|
|
|
147
135
|
document.body.prepend(this.frameBox);
|
|
148
136
|
}
|
|
149
137
|
this.triggerCanvas.initialize();
|
|
150
|
-
await this.syncLog("[EF_FRAMEGEN.initialize] About to call renderAudio with:", {
|
|
151
|
-
fromMs: alignedFromMs.toFixed(3),
|
|
152
|
-
toMs: alignedToMs.toFixed(3),
|
|
153
|
-
durationMs: alignedDurationMs.toFixed(3)
|
|
154
|
-
});
|
|
155
138
|
this.audioBufferPromise = firstGroup.renderAudio(renderOptions.encoderOptions.alignedFromUs / 1e3, renderOptions.encoderOptions.alignedToUs / 1e3);
|
|
156
139
|
}
|
|
157
140
|
async beginFrame(frameNumber, isLast) {
|
|
@@ -13,14 +13,14 @@ export declare class AssetMediaEngine extends BaseMediaEngine implements MediaEn
|
|
|
13
13
|
get audioTrackIndex(): import('../../../../assets/src/index.ts').AudioTrackFragmentIndex | undefined;
|
|
14
14
|
get videoTrackIndex(): import('../../../../assets/src/index.ts').VideoTrackFragmentIndex | undefined;
|
|
15
15
|
get videoRendition(): {
|
|
16
|
-
trackId: number
|
|
16
|
+
trackId: number;
|
|
17
17
|
src: string;
|
|
18
18
|
startTimeOffsetMs: number | undefined;
|
|
19
|
-
};
|
|
19
|
+
} | undefined;
|
|
20
20
|
get audioRendition(): {
|
|
21
|
-
trackId: number
|
|
21
|
+
trackId: number;
|
|
22
22
|
src: string;
|
|
23
|
-
};
|
|
23
|
+
} | undefined;
|
|
24
24
|
get initSegmentPaths(): InitSegmentPaths;
|
|
25
25
|
get templates(): {
|
|
26
26
|
initSegment: string;
|
|
@@ -24,15 +24,19 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
|
24
24
|
return Object.values(this.data).find((track) => track.type === "video");
|
|
25
25
|
}
|
|
26
26
|
get videoRendition() {
|
|
27
|
+
const videoTrack = this.videoTrackIndex;
|
|
28
|
+
if (!videoTrack || videoTrack.track === void 0) return void 0;
|
|
27
29
|
return {
|
|
28
|
-
trackId:
|
|
30
|
+
trackId: videoTrack.track,
|
|
29
31
|
src: this.src,
|
|
30
|
-
startTimeOffsetMs:
|
|
32
|
+
startTimeOffsetMs: videoTrack.startTimeOffsetMs
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
get audioRendition() {
|
|
36
|
+
const audioTrack = this.audioTrackIndex;
|
|
37
|
+
if (!audioTrack || audioTrack.track === void 0) return void 0;
|
|
34
38
|
return {
|
|
35
|
-
trackId:
|
|
39
|
+
trackId: audioTrack.track,
|
|
36
40
|
src: this.src
|
|
37
41
|
};
|
|
38
42
|
}
|
|
@@ -162,7 +166,7 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
|
162
166
|
}
|
|
163
167
|
convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
|
|
164
168
|
{
|
|
165
|
-
if (!rendition.trackId) throw new Error("
|
|
169
|
+
if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
|
|
166
170
|
const trackData = this.data[rendition.trackId];
|
|
167
171
|
if (!trackData) throw new Error("Track not found");
|
|
168
172
|
const segment = trackData.segments?.[segmentId];
|
|
@@ -10,8 +10,16 @@ export declare abstract class BaseMediaEngine {
|
|
|
10
10
|
constructor(host: EFMedia);
|
|
11
11
|
abstract get videoRendition(): VideoRendition | undefined;
|
|
12
12
|
abstract get audioRendition(): AudioRendition | undefined;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Get video rendition if available. Returns undefined for audio-only assets.
|
|
15
|
+
* Callers should handle undefined gracefully.
|
|
16
|
+
*/
|
|
17
|
+
getVideoRendition(): VideoRendition | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Get audio rendition if available. Returns undefined for video-only assets.
|
|
20
|
+
* Callers should handle undefined gracefully.
|
|
21
|
+
*/
|
|
22
|
+
getAudioRendition(): AudioRendition | undefined;
|
|
15
23
|
/**
|
|
16
24
|
* Generate cache key for segment requests
|
|
17
25
|
*/
|
|
@@ -6,12 +6,18 @@ var BaseMediaEngine = class {
|
|
|
6
6
|
constructor(host) {
|
|
7
7
|
this.host = host;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Get video rendition if available. Returns undefined for audio-only assets.
|
|
11
|
+
* Callers should handle undefined gracefully.
|
|
12
|
+
*/
|
|
9
13
|
getVideoRendition() {
|
|
10
|
-
if (!this.videoRendition) throw new Error("No video rendition available");
|
|
11
14
|
return this.videoRendition;
|
|
12
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Get audio rendition if available. Returns undefined for video-only assets.
|
|
18
|
+
* Callers should handle undefined gracefully.
|
|
19
|
+
*/
|
|
13
20
|
getAudioRendition() {
|
|
14
|
-
if (!this.audioRendition) throw new Error("No audio rendition available");
|
|
15
21
|
return this.audioRendition;
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
@@ -107,10 +107,19 @@ 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
|
-
|
|
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
|
+
}
|
|
114
123
|
return this.thumbnailExtractor.extractThumbnails(timestamps, rendition, this.durationMs);
|
|
115
124
|
}
|
|
116
125
|
convertToSegmentRelativeTimestamps(globalTimestamps, _segmentId, _rendition) {
|
|
@@ -48,7 +48,7 @@ const makeAudioBufferTask = (host) => {
|
|
|
48
48
|
getRendition: async () => {
|
|
49
49
|
const mediaEngine$1 = await getLatestMediaEngine(host, signal);
|
|
50
50
|
const audioRendition = mediaEngine$1.audioRendition;
|
|
51
|
-
if (!audioRendition) throw new Error("
|
|
51
|
+
if (!audioRendition) throw new Error("Audio rendition not available");
|
|
52
52
|
return audioRendition;
|
|
53
53
|
},
|
|
54
54
|
logError: console.error
|
|
@@ -51,8 +51,6 @@ 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;
|
|
56
54
|
const currentTimeMs = element.currentSourceTimeMs;
|
|
57
55
|
const frameIntervalMs = 1e3 / 30;
|
|
58
56
|
const earliestFrameMs = currentTimeMs - (element.fftDecay - 1) * frameIntervalMs;
|
|
@@ -9,7 +9,7 @@ const makeAudioInitSegmentFetchTask = (host) => {
|
|
|
9
9
|
onComplete: (_value) => {},
|
|
10
10
|
task: async ([_mediaEngine], { signal }) => {
|
|
11
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
|
-
const audioRendition = mediaEngine.
|
|
12
|
+
const audioRendition = mediaEngine.getAudioRendition();
|
|
13
13
|
if (!audioRendition) return void 0;
|
|
14
14
|
return mediaEngine.fetchInitSegment(audioRendition, signal);
|
|
15
15
|
}
|
|
@@ -9,14 +9,15 @@ const makeAudioInputTask = (host) => {
|
|
|
9
9
|
},
|
|
10
10
|
onComplete: (_value) => {},
|
|
11
11
|
task: async (_, { signal }) => {
|
|
12
|
+
const mediaEngine = await host.mediaEngineTask.taskComplete;
|
|
13
|
+
const audioRendition = mediaEngine?.audioRendition;
|
|
14
|
+
if (!audioRendition) return void 0;
|
|
12
15
|
const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
|
|
13
16
|
signal.throwIfAborted();
|
|
14
17
|
const segment = await host.audioSegmentFetchTask.taskComplete;
|
|
15
18
|
signal.throwIfAborted();
|
|
16
|
-
if (!initSegment || !segment)
|
|
17
|
-
const
|
|
18
|
-
const audioRendition = mediaEngine?.audioRendition;
|
|
19
|
-
const startTimeOffsetMs = audioRendition?.startTimeOffsetMs;
|
|
19
|
+
if (!initSegment || !segment) return void 0;
|
|
20
|
+
const startTimeOffsetMs = audioRendition.startTimeOffsetMs;
|
|
20
21
|
const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
|
|
21
22
|
signal.throwIfAborted();
|
|
22
23
|
return new BufferedSeekingInput(arrayBuffer, {
|
|
@@ -9,19 +9,9 @@ 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;
|
|
14
12
|
const segmentId = await host.audioSegmentIdTask.taskComplete;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
hasRendition: true,
|
|
18
|
-
segmentDurationMs: audioRendition.segmentDurationMs,
|
|
19
|
-
segmentDurationsMs: audioRendition.segmentDurationsMs?.length || 0,
|
|
20
|
-
desiredSeekTimeMs: host.desiredSeekTimeMs,
|
|
21
|
-
intrinsicDurationMs: host.intrinsicDurationMs
|
|
22
|
-
};
|
|
23
|
-
throw new Error(`Segment ID is not available for audio. Debug info: ${JSON.stringify(debugInfo)}`);
|
|
24
|
-
}
|
|
13
|
+
const audioRendition = mediaEngine.getAudioRendition();
|
|
14
|
+
if (!audioRendition || segmentId === void 0) return void 0;
|
|
25
15
|
return mediaEngine.fetchMediaSegment(segmentId, audioRendition, signal);
|
|
26
16
|
}
|
|
27
17
|
});
|
|
@@ -10,7 +10,7 @@ const makeAudioSegmentIdTask = (host) => {
|
|
|
10
10
|
task: async ([, targetSeekTimeMs], { signal }) => {
|
|
11
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
12
|
signal.throwIfAborted();
|
|
13
|
-
const audioRendition = mediaEngine.
|
|
13
|
+
const audioRendition = mediaEngine.getAudioRendition();
|
|
14
14
|
if (!audioRendition) return void 0;
|
|
15
15
|
return mediaEngine.computeSegmentId(targetSeekTimeMs, audioRendition);
|
|
16
16
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
|
|
2
2
|
import { LRUCache } from "../../../utils/LRUCache.js";
|
|
3
|
+
import { IgnorableError } from "../../EFMedia.js";
|
|
3
4
|
import { Task } from "@lit/task";
|
|
4
5
|
const DECAY_WEIGHT = .8;
|
|
5
6
|
function makeAudioTimeDomainAnalysisTask(element) {
|
|
@@ -7,6 +8,10 @@ function makeAudioTimeDomainAnalysisTask(element) {
|
|
|
7
8
|
return new Task(element, {
|
|
8
9
|
autoRun: EF_INTERACTIVE,
|
|
9
10
|
onError: (error) => {
|
|
11
|
+
if (error instanceof IgnorableError) {
|
|
12
|
+
console.info("byteTimeDomainTask skipped: no audio track");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
10
15
|
console.error("byteTimeDomainTask error", error);
|
|
11
16
|
},
|
|
12
17
|
args: () => [
|
|
@@ -18,8 +23,6 @@ function makeAudioTimeDomainAnalysisTask(element) {
|
|
|
18
23
|
],
|
|
19
24
|
task: async (_, { signal }) => {
|
|
20
25
|
if (element.currentSourceTimeMs < 0) return null;
|
|
21
|
-
const mediaEngine = element.mediaEngineTask.value;
|
|
22
|
-
if (!mediaEngine?.audioRendition) return null;
|
|
23
26
|
const currentTimeMs = element.currentSourceTimeMs;
|
|
24
27
|
const frameIntervalMs = 1e3 / 30;
|
|
25
28
|
const earliestFrameMs = currentTimeMs - (element.fftDecay - 1) * frameIntervalMs;
|
|
@@ -4,4 +4,4 @@ import { EFMedia } from '../../EFMedia';
|
|
|
4
4
|
* Fetch audio spanning a time range
|
|
5
5
|
* Main function that orchestrates segment calculation, fetching, and blob creation
|
|
6
6
|
*/
|
|
7
|
-
export declare const fetchAudioSpanningTime: (host: EFMedia, fromMs: number, toMs: number, signal: AbortSignal) => Promise<AudioSpan>;
|
|
7
|
+
export declare const fetchAudioSpanningTime: (host: EFMedia, fromMs: number, toMs: number, signal: AbortSignal) => Promise<AudioSpan | undefined>;
|
|
@@ -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("Audio rendition not available");
|
|
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,8 +31,8 @@ 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)
|
|
35
|
-
if (!initSegment)
|
|
34
|
+
if (!mediaEngine?.audioRendition) return void 0;
|
|
35
|
+
if (!initSegment) return void 0;
|
|
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`);
|
|
38
38
|
const segmentIds = segmentRanges.map((r) => r.segmentId);
|
|
@@ -24,7 +24,7 @@ export interface MediaBufferDependencies<T extends AudioRendition | VideoRenditi
|
|
|
24
24
|
computeSegmentId: (timeMs: number, rendition: T) => Promise<number | undefined>;
|
|
25
25
|
prefetchSegment: (segmentId: number, rendition: T) => Promise<void>;
|
|
26
26
|
isSegmentCached: (segmentId: number, rendition: T) => boolean;
|
|
27
|
-
getRendition: () => Promise<T>;
|
|
27
|
+
getRendition: () => Promise<T | undefined>;
|
|
28
28
|
logError: (message: string, error: any) => void;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
@@ -29,6 +29,7 @@ const computeBufferQueue = (desiredSegments, requestedSegments) => {
|
|
|
29
29
|
const manageMediaBuffer = async (seekTimeMs, config, currentState, durationMs, signal, deps) => {
|
|
30
30
|
if (!config.enableBuffering) return currentState;
|
|
31
31
|
const rendition = await deps.getRendition();
|
|
32
|
+
if (!rendition) return currentState;
|
|
32
33
|
const endTimeMs = seekTimeMs + config.bufferDurationMs;
|
|
33
34
|
const desiredSegments = await computeSegmentRangeAsync(seekTimeMs, endTimeMs, durationMs, rendition, deps.computeSegmentId);
|
|
34
35
|
const uncachedSegments = desiredSegments.filter((segmentId) => !deps.isSegmentCached(segmentId, rendition));
|
|
@@ -59,11 +60,12 @@ const manageMediaBuffer = async (seekTimeMs, config, currentState, durationMs, s
|
|
|
59
60
|
};
|
|
60
61
|
const initialBatchSize = Math.min(config.maxParallelFetches, newQueue.length);
|
|
61
62
|
for (let i = 0; i < initialBatchSize; i++) startNextSegment();
|
|
62
|
-
|
|
63
|
+
const result = {
|
|
63
64
|
currentSeekTimeMs: seekTimeMs,
|
|
64
65
|
requestedSegments: newRequestedSegments,
|
|
65
66
|
activeRequests: newActiveRequests,
|
|
66
67
|
requestQueue: remainingQueue
|
|
67
68
|
};
|
|
69
|
+
return result;
|
|
68
70
|
};
|
|
69
71
|
export { manageMediaBuffer };
|
|
@@ -20,4 +20,4 @@ export type SegmentFetchTask = Task<readonly [MediaEngine | undefined, number |
|
|
|
20
20
|
/**
|
|
21
21
|
* Generic task type for input creation
|
|
22
22
|
*/
|
|
23
|
-
export type InputTask = Task<readonly [ArrayBuffer, ArrayBuffer], BufferedSeekingInput>;
|
|
23
|
+
export type InputTask = Task<readonly [ArrayBuffer, ArrayBuffer], BufferedSeekingInput | undefined>;
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import { AudioRendition,
|
|
2
|
-
/**
|
|
3
|
-
* Get audio rendition from media engine, throwing if not available
|
|
4
|
-
*/
|
|
5
|
-
export declare const getAudioRendition: (mediaEngine: MediaEngine) => AudioRendition;
|
|
6
|
-
/**
|
|
7
|
-
* Get video rendition from media engine, throwing if not available
|
|
8
|
-
*/
|
|
9
|
-
export declare const getVideoRendition: (mediaEngine: MediaEngine) => VideoRendition;
|
|
1
|
+
import { AudioRendition, VideoRendition } from '../../../transcoding/types';
|
|
10
2
|
/**
|
|
11
3
|
* Calculate which segment contains a given timestamp
|
|
12
4
|
* Returns 1-based segment ID, or undefined if segmentDurationMs is not available
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Task } from '@lit/task';
|
|
2
|
-
import { MediaEngine
|
|
2
|
+
import { MediaEngine } from '../../../transcoding/types';
|
|
3
3
|
import { EFMedia } from '../../EFMedia';
|
|
4
4
|
export declare const getLatestMediaEngine: (host: EFMedia, signal: AbortSignal) => Promise<MediaEngine>;
|
|
5
|
-
export declare const getVideoRendition: (mediaEngine: MediaEngine) => VideoRendition;
|
|
6
5
|
/**
|
|
7
6
|
* Core logic for creating a MediaEngine with explicit dependencies.
|
|
8
7
|
* Pure function that requires all dependencies to be provided.
|
|
@@ -9,11 +9,6 @@ const getLatestMediaEngine = async (host, signal) => {
|
|
|
9
9
|
if (!mediaEngine) throw new Error("Media engine is not available");
|
|
10
10
|
return mediaEngine;
|
|
11
11
|
};
|
|
12
|
-
const getVideoRendition = (mediaEngine) => {
|
|
13
|
-
const videoRendition = mediaEngine.videoRendition;
|
|
14
|
-
if (!videoRendition) throw new Error("No video track available in source");
|
|
15
|
-
return videoRendition;
|
|
16
|
-
};
|
|
17
12
|
/**
|
|
18
13
|
* Core logic for creating a MediaEngine with explicit dependencies.
|
|
19
14
|
* Pure function that requires all dependencies to be provided.
|
|
@@ -57,4 +52,4 @@ const makeMediaEngineTask = (host) => {
|
|
|
57
52
|
}
|
|
58
53
|
});
|
|
59
54
|
};
|
|
60
|
-
export { getLatestMediaEngine,
|
|
55
|
+
export { getLatestMediaEngine, makeMediaEngineTask };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
|
|
1
2
|
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
3
|
import { manageMediaBuffer } from "../shared/BufferUtils.js";
|
|
3
4
|
import { Task } from "@lit/task";
|
|
@@ -14,7 +15,7 @@ const makeScrubVideoBufferTask = (host) => {
|
|
|
14
15
|
requestQueue: []
|
|
15
16
|
};
|
|
16
17
|
return new Task(host, {
|
|
17
|
-
autoRun:
|
|
18
|
+
autoRun: EF_INTERACTIVE,
|
|
18
19
|
args: () => [host.mediaEngineTask.value],
|
|
19
20
|
onError: (error) => {
|
|
20
21
|
console.error("scrubVideoBufferTask error", error);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
3
|
const makeScrubVideoInitSegmentFetchTask = (host) => {
|
|
@@ -9,7 +8,6 @@ const makeScrubVideoInitSegmentFetchTask = (host) => {
|
|
|
9
8
|
},
|
|
10
9
|
onComplete: (_value) => {},
|
|
11
10
|
task: async ([_mediaEngine], { signal }) => {
|
|
12
|
-
if (EF_RENDERING()) return /* @__PURE__ */ new ArrayBuffer(0);
|
|
13
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
14
12
|
const scrubRendition = mediaEngine.getScrubVideoRendition();
|
|
15
13
|
if (!scrubRendition) throw new Error("No scrub rendition available");
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { BufferedSeekingInput } from "../BufferedSeekingInput.js";
|
|
3
2
|
import { EFMedia } from "../../EFMedia.js";
|
|
4
3
|
import { Task } from "@lit/task";
|
|
@@ -10,7 +9,6 @@ const makeScrubVideoInputTask = (host) => {
|
|
|
10
9
|
},
|
|
11
10
|
onComplete: (_value) => {},
|
|
12
11
|
task: async () => {
|
|
13
|
-
if (EF_RENDERING()) console.info("Scrub not available in rendering mode");
|
|
14
12
|
const initSegment = await host.scrubVideoInitSegmentFetchTask.taskComplete;
|
|
15
13
|
const segment = await host.scrubVideoSegmentFetchTask.taskComplete;
|
|
16
14
|
if (!initSegment || !segment) throw new Error("Scrub init segment or segment is not available");
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
3
|
const makeScrubVideoSegmentFetchTask = (host) => {
|
|
@@ -9,7 +8,6 @@ const makeScrubVideoSegmentFetchTask = (host) => {
|
|
|
9
8
|
},
|
|
10
9
|
onComplete: (_value) => {},
|
|
11
10
|
task: async (_, { signal }) => {
|
|
12
|
-
if (EF_RENDERING()) return /* @__PURE__ */ new ArrayBuffer(0);
|
|
13
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
14
12
|
const segmentId = await host.scrubVideoSegmentIdTask.taskComplete;
|
|
15
13
|
if (segmentId === void 0) throw new Error("Scrub segment ID is not available for video");
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
3
|
const makeScrubVideoSegmentIdTask = (host) => {
|
|
@@ -9,7 +8,6 @@ const makeScrubVideoSegmentIdTask = (host) => {
|
|
|
9
8
|
},
|
|
10
9
|
onComplete: (_value) => {},
|
|
11
10
|
task: async ([, targetSeekTimeMs], { signal }) => {
|
|
12
|
-
if (EF_RENDERING()) return void 0;
|
|
13
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
14
12
|
signal.throwIfAborted();
|
|
15
13
|
const scrubRendition = mediaEngine.getScrubVideoRendition();
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
3
2
|
import { ScrubInputCache } from "./ScrubInputCache.js";
|
|
4
3
|
import { Task } from "@lit/task";
|
|
@@ -14,7 +13,6 @@ const makeUnifiedVideoSeekTask = (host) => {
|
|
|
14
13
|
task: async ([desiredSeekTimeMs], { signal }) => {
|
|
15
14
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
16
15
|
if (!mediaEngine) return void 0;
|
|
17
|
-
if (EF_RENDERING()) return await getMainVideoSample(host, mediaEngine, desiredSeekTimeMs, signal);
|
|
18
16
|
const mainRendition = mediaEngine.videoRendition;
|
|
19
17
|
if (mainRendition) {
|
|
20
18
|
const mainSegmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, mainRendition);
|
|
@@ -72,14 +70,15 @@ async function tryGetScrubSample(mediaEngine, desiredSeekTimeMs, signal) {
|
|
|
72
70
|
*/
|
|
73
71
|
async function getMainVideoSample(_host, mediaEngine, desiredSeekTimeMs, signal) {
|
|
74
72
|
try {
|
|
75
|
-
const
|
|
73
|
+
const videoRendition = mediaEngine.getVideoRendition();
|
|
74
|
+
if (!videoRendition) throw new Error("Video rendition unavailable after checking videoRendition exists");
|
|
75
|
+
const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, videoRendition);
|
|
76
76
|
if (segmentId === void 0) return void 0;
|
|
77
|
-
const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(
|
|
77
|
+
const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(videoRendition, signal), mediaEngine.fetchMediaSegment(segmentId, videoRendition, signal)]);
|
|
78
78
|
if (!initSegment || !mediaSegment) return void 0;
|
|
79
79
|
signal.throwIfAborted();
|
|
80
80
|
const { BufferedSeekingInput } = await import("../BufferedSeekingInput.js");
|
|
81
81
|
const { EFMedia } = await import("../../EFMedia.js");
|
|
82
|
-
const videoRendition = mediaEngine.videoRendition;
|
|
83
82
|
const startTimeOffsetMs = videoRendition?.startTimeOffsetMs;
|
|
84
83
|
const mainInput = new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
|
|
85
84
|
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
|
|
2
2
|
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
3
3
|
import { manageMediaBuffer } from "../shared/BufferUtils.js";
|
|
4
|
-
import { getLatestMediaEngine
|
|
4
|
+
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
5
5
|
import { Task } from "@lit/task";
|
|
6
6
|
const makeVideoBufferTask = (host) => {
|
|
7
7
|
let currentState = {
|
|
@@ -46,7 +46,7 @@ const makeVideoBufferTask = (host) => {
|
|
|
46
46
|
},
|
|
47
47
|
getRendition: async () => {
|
|
48
48
|
const mediaEngine$1 = await getLatestMediaEngine(host, signal);
|
|
49
|
-
return
|
|
49
|
+
return mediaEngine$1.getVideoRendition();
|
|
50
50
|
},
|
|
51
51
|
logError: console.error
|
|
52
52
|
});
|
|
@@ -81,7 +81,8 @@ export declare class EFMedia extends EFMedia_base {
|
|
|
81
81
|
/**
|
|
82
82
|
* Main integration method for EFTimegroup audio playback
|
|
83
83
|
* Now powered by clean, testable utility functions
|
|
84
|
+
* Returns undefined if no audio rendition is available
|
|
84
85
|
*/
|
|
85
|
-
fetchAudioSpanningTime(fromMs: number, toMs: number, signal?: AbortSignal): Promise<AudioSpan>;
|
|
86
|
+
fetchAudioSpanningTime(fromMs: number, toMs: number, signal?: AbortSignal): Promise<AudioSpan | undefined>;
|
|
86
87
|
}
|
|
87
88
|
export {};
|
package/dist/elements/EFMedia.js
CHANGED
|
@@ -145,6 +145,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
145
145
|
/**
|
|
146
146
|
* Main integration method for EFTimegroup audio playback
|
|
147
147
|
* Now powered by clean, testable utility functions
|
|
148
|
+
* Returns undefined if no audio rendition is available
|
|
148
149
|
*/
|
|
149
150
|
async fetchAudioSpanningTime(fromMs, toMs, signal = new AbortController().signal) {
|
|
150
151
|
return fetchAudioSpanningTime(this, fromMs, toMs, signal);
|
|
@@ -373,7 +373,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
373
373
|
if (error instanceof Error && error.message.includes("No audio track available")) return;
|
|
374
374
|
throw error;
|
|
375
375
|
}
|
|
376
|
-
if (!audio)
|
|
376
|
+
if (!audio) return;
|
|
377
377
|
const bufferSource = audioContext.createBufferSource();
|
|
378
378
|
bufferSource.buffer = await audioContext.decodeAudioData(await audio.blob.arrayBuffer());
|
|
379
379
|
bufferSource.connect(audioContext.destination);
|
|
@@ -207,13 +207,15 @@ export interface MediaEngine {
|
|
|
207
207
|
}, signal?: AbortSignal) => Promise<ArrayBuffer>;
|
|
208
208
|
computeSegmentId: (desiredSeekTimeMs: number, rendition: MediaRendition) => number | undefined;
|
|
209
209
|
/**
|
|
210
|
-
* Get the video rendition
|
|
210
|
+
* Get the video rendition if available, otherwise return undefined.
|
|
211
|
+
* Callers should handle undefined appropriately.
|
|
211
212
|
*/
|
|
212
|
-
getVideoRendition: () => VideoRendition;
|
|
213
|
+
getVideoRendition: () => VideoRendition | undefined;
|
|
213
214
|
/**
|
|
214
|
-
* Get the audio rendition
|
|
215
|
+
* Get the audio rendition if available, otherwise return undefined.
|
|
216
|
+
* Callers should handle undefined appropriately.
|
|
215
217
|
*/
|
|
216
|
-
getAudioRendition: () => AudioRendition;
|
|
218
|
+
getAudioRendition: () => AudioRendition | undefined;
|
|
217
219
|
/**
|
|
218
220
|
* Check if a segment is cached for a given rendition
|
|
219
221
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.4-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.4-beta.0",
|
|
31
31
|
"@lit/context": "^1.1.2",
|
|
32
32
|
"@lit/task": "^1.0.1",
|
|
33
33
|
"d3": "^7.9.0",
|
|
@@ -122,14 +122,16 @@ describe("AssetIdMediaEngine", () => {
|
|
|
122
122
|
|
|
123
123
|
it("should return correct audio rendition", () => {
|
|
124
124
|
const audioRendition = engine.audioRendition;
|
|
125
|
-
expect(audioRendition
|
|
126
|
-
expect(audioRendition
|
|
125
|
+
expect(audioRendition).toBeDefined();
|
|
126
|
+
expect(audioRendition!.trackId).toBe(1);
|
|
127
|
+
expect(audioRendition!.src).toBe(mockAssetId);
|
|
127
128
|
});
|
|
128
129
|
|
|
129
130
|
it("should return correct video rendition", () => {
|
|
130
131
|
const videoRendition = engine.videoRendition;
|
|
131
|
-
expect(videoRendition
|
|
132
|
-
expect(videoRendition
|
|
132
|
+
expect(videoRendition).toBeDefined();
|
|
133
|
+
expect(videoRendition!.trackId).toBe(2);
|
|
134
|
+
expect(videoRendition!.src).toBe(mockAssetId);
|
|
133
135
|
});
|
|
134
136
|
});
|
|
135
137
|
|