@editframe/elements 0.38.1 → 0.39.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/EFCaptions.js +1 -1
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.js +3 -4
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +1 -1
- package/dist/elements/EFMedia/CachedFetcher.js +99 -0
- package/dist/elements/EFMedia/CachedFetcher.js.map +1 -0
- package/dist/elements/EFMedia/MediaEngine.d.ts +19 -0
- package/dist/elements/EFMedia/MediaEngine.js +129 -0
- package/dist/elements/EFMedia/MediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/SegmentIndex.d.ts +32 -0
- package/dist/elements/EFMedia/SegmentIndex.js +185 -0
- package/dist/elements/EFMedia/SegmentIndex.js.map +1 -0
- package/dist/elements/EFMedia/SegmentTransport.d.ts +12 -0
- package/dist/elements/EFMedia/SegmentTransport.js +69 -0
- package/dist/elements/EFMedia/SegmentTransport.js.map +1 -0
- package/dist/elements/EFMedia/TimingModel.d.ts +10 -0
- package/dist/elements/EFMedia/TimingModel.js +28 -0
- package/dist/elements/EFMedia/TimingModel.js.map +1 -0
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +7 -6
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +13 -34
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +2 -1
- package/dist/elements/EFMedia.js +14 -31
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +1 -1
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFTemporal.js +2 -1
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFTimegroup.js +2 -1
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.js +204 -187
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/gui/EFConfiguration.d.ts +0 -7
- package/dist/gui/EFConfiguration.js +0 -5
- package/dist/gui/EFConfiguration.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +2 -0
- package/dist/gui/EFWorkbench.js +68 -1
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +2 -0
- package/dist/gui/PlaybackController.js +11 -1
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/ef-theme.css +11 -0
- package/dist/gui/timeline/tracks/AudioTrack.js +28 -30
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +1 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +41 -8
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.js +2 -2
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +19 -19
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +8 -0
- package/dist/preview/QualityUpgradeScheduler.js +13 -1
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.js +3 -3
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderVideoToVideo.js +5 -6
- package/dist/preview/renderVideoToVideo.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +6 -94
- package/dist/transcoding/utils/UrlGenerator.d.ts +3 -12
- package/dist/transcoding/utils/UrlGenerator.js +3 -29
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/package.json +2 -2
- package/test/setup.ts +1 -1
- package/test/useAssetMSW.ts +0 -100
- package/dist/elements/EFMedia/AssetMediaEngine.js +0 -284
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +0 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +0 -200
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +0 -1
- package/dist/elements/EFMedia/FileMediaEngine.js +0 -122
- package/dist/elements/EFMedia/FileMediaEngine.js.map +0 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +0 -157
- package/dist/elements/EFMedia/JitMediaEngine.js.map +0 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "../../transcoding/types/index.js";
|
|
2
|
+
import "@editframe/assets";
|
|
3
|
+
|
|
4
|
+
//#region src/elements/EFMedia/SegmentIndex.d.ts
|
|
5
|
+
type TrackRole = "video" | "audio" | "scrub";
|
|
6
|
+
interface TrackRef {
|
|
7
|
+
readonly role: TrackRole;
|
|
8
|
+
readonly id: string | number;
|
|
9
|
+
readonly src: string;
|
|
10
|
+
readonly segmentDurationMs?: number;
|
|
11
|
+
readonly segmentDurationsMs?: number[];
|
|
12
|
+
readonly startTimeOffsetMs?: number;
|
|
13
|
+
}
|
|
14
|
+
interface TrackSet {
|
|
15
|
+
video?: TrackRef;
|
|
16
|
+
audio?: TrackRef;
|
|
17
|
+
scrub?: TrackRef;
|
|
18
|
+
}
|
|
19
|
+
interface SegmentTimeRange {
|
|
20
|
+
segmentId: number;
|
|
21
|
+
startMs: number;
|
|
22
|
+
endMs: number;
|
|
23
|
+
}
|
|
24
|
+
interface SegmentIndex {
|
|
25
|
+
readonly durationMs: number;
|
|
26
|
+
readonly tracks: TrackSet;
|
|
27
|
+
segmentAt(timeMs: number, track: TrackRef): number | undefined;
|
|
28
|
+
segmentsInRange(fromMs: number, toMs: number, track: TrackRef): SegmentTimeRange[];
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { SegmentIndex, SegmentTimeRange, TrackRef, TrackRole, TrackSet };
|
|
32
|
+
//# sourceMappingURL=SegmentIndex.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { convertToScaledTime, roundToMilliseconds } from "./shared/PrecisionUtils.js";
|
|
2
|
+
|
|
3
|
+
//#region src/elements/EFMedia/SegmentIndex.ts
|
|
4
|
+
function createFragmentIndex(data, src) {
|
|
5
|
+
const durationMs = Object.values(data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0) * 1e3;
|
|
6
|
+
const audioTrack = Object.values(data).find((t) => t.type === "audio");
|
|
7
|
+
const videoTrack = Object.values(data).find((t) => t.type === "video" && t.track !== void 0 && t.track > 0);
|
|
8
|
+
const scrubTrack = data[-1];
|
|
9
|
+
const tracks = {};
|
|
10
|
+
if (videoTrack && videoTrack.track !== void 0) tracks.video = {
|
|
11
|
+
role: "video",
|
|
12
|
+
id: videoTrack.track,
|
|
13
|
+
src,
|
|
14
|
+
startTimeOffsetMs: videoTrack.startTimeOffsetMs
|
|
15
|
+
};
|
|
16
|
+
if (audioTrack && audioTrack.track !== void 0) tracks.audio = {
|
|
17
|
+
role: "audio",
|
|
18
|
+
id: audioTrack.track,
|
|
19
|
+
src
|
|
20
|
+
};
|
|
21
|
+
if (scrubTrack && scrubTrack.track !== void 0) {
|
|
22
|
+
const segmentDurationsMs = scrubTrack.segments.length > 0 ? scrubTrack.segments.map((s) => s.duration / scrubTrack.timescale * 1e3) : void 0;
|
|
23
|
+
tracks.scrub = {
|
|
24
|
+
role: "scrub",
|
|
25
|
+
id: scrubTrack.track,
|
|
26
|
+
src,
|
|
27
|
+
segmentDurationMs: 3e4,
|
|
28
|
+
segmentDurationsMs,
|
|
29
|
+
startTimeOffsetMs: scrubTrack.startTimeOffsetMs
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
durationMs,
|
|
34
|
+
tracks,
|
|
35
|
+
segmentAt(timeMs, track) {
|
|
36
|
+
const trackId = typeof track.id === "number" ? track.id : Number.parseInt(track.id, 10);
|
|
37
|
+
const trackData = data[trackId];
|
|
38
|
+
if (!trackData) throw new Error(`Track ${trackId} not found`);
|
|
39
|
+
const { timescale, segments } = trackData;
|
|
40
|
+
const scaledSeekTime = convertToScaledTime(roundToMilliseconds(timeMs + (track.startTimeOffsetMs || 0)), timescale);
|
|
41
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
42
|
+
const segment = segments[i];
|
|
43
|
+
const segmentEndTime = segment.cts + segment.duration;
|
|
44
|
+
if (segment.cts <= scaledSeekTime && scaledSeekTime < segmentEndTime) return i;
|
|
45
|
+
}
|
|
46
|
+
let nearestSegmentIndex = 0;
|
|
47
|
+
let nearestDistance = Number.MAX_SAFE_INTEGER;
|
|
48
|
+
for (let i = 0; i < segments.length; i++) {
|
|
49
|
+
const segment = segments[i];
|
|
50
|
+
const segmentEndTime = segment.cts + segment.duration;
|
|
51
|
+
let distance;
|
|
52
|
+
if (scaledSeekTime < segment.cts) distance = segment.cts - scaledSeekTime;
|
|
53
|
+
else if (scaledSeekTime >= segmentEndTime) distance = scaledSeekTime - segmentEndTime;
|
|
54
|
+
else return i;
|
|
55
|
+
if (distance < nearestDistance) {
|
|
56
|
+
nearestDistance = distance;
|
|
57
|
+
nearestSegmentIndex = i;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return nearestSegmentIndex;
|
|
61
|
+
},
|
|
62
|
+
segmentsInRange(fromMs, toMs, track) {
|
|
63
|
+
if (fromMs >= toMs) return [];
|
|
64
|
+
const trackData = data[typeof track.id === "number" ? track.id : Number.parseInt(track.id, 10)];
|
|
65
|
+
if (!trackData) return [];
|
|
66
|
+
const { timescale, segments } = trackData;
|
|
67
|
+
const ranges = [];
|
|
68
|
+
for (let i = 0; i < segments.length; i++) {
|
|
69
|
+
const segment = segments[i];
|
|
70
|
+
const segmentStartMs = segment.cts / timescale * 1e3;
|
|
71
|
+
const segmentEndMs = (segment.cts + segment.duration) / timescale * 1e3;
|
|
72
|
+
if (segmentStartMs < toMs && segmentEndMs > fromMs) ranges.push({
|
|
73
|
+
segmentId: i,
|
|
74
|
+
startMs: segmentStartMs,
|
|
75
|
+
endMs: segmentEndMs
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return ranges;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function createManifestIndex(manifest) {
|
|
83
|
+
const durationMs = manifest.durationMs;
|
|
84
|
+
const tracks = {};
|
|
85
|
+
if (manifest.videoRenditions && manifest.videoRenditions.length > 0) {
|
|
86
|
+
const r = manifest.videoRenditions[0];
|
|
87
|
+
tracks.video = {
|
|
88
|
+
role: "video",
|
|
89
|
+
id: r.id,
|
|
90
|
+
src: manifest.sourceUrl,
|
|
91
|
+
segmentDurationMs: r.segmentDurationMs,
|
|
92
|
+
segmentDurationsMs: r.segmentDurationsMs,
|
|
93
|
+
startTimeOffsetMs: r.startTimeOffsetMs
|
|
94
|
+
};
|
|
95
|
+
const scrubRendition = manifest.videoRenditions.find((v) => v.id === "scrub");
|
|
96
|
+
if (scrubRendition) tracks.scrub = {
|
|
97
|
+
role: "scrub",
|
|
98
|
+
id: scrubRendition.id,
|
|
99
|
+
src: manifest.sourceUrl,
|
|
100
|
+
segmentDurationMs: scrubRendition.segmentDurationMs,
|
|
101
|
+
segmentDurationsMs: scrubRendition.segmentDurationsMs,
|
|
102
|
+
startTimeOffsetMs: scrubRendition.startTimeOffsetMs
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (manifest.audioRenditions && manifest.audioRenditions.length > 0) {
|
|
106
|
+
const r = manifest.audioRenditions[0];
|
|
107
|
+
tracks.audio = {
|
|
108
|
+
role: "audio",
|
|
109
|
+
id: r.id,
|
|
110
|
+
src: manifest.sourceUrl,
|
|
111
|
+
segmentDurationMs: r.segmentDurationMs,
|
|
112
|
+
segmentDurationsMs: r.segmentDurationsMs,
|
|
113
|
+
startTimeOffsetMs: r.startTimeOffsetMs
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function computeSegmentIdForTrack(desiredSeekTimeMs, track) {
|
|
117
|
+
if (desiredSeekTimeMs > durationMs) return;
|
|
118
|
+
if (track.segmentDurationsMs && track.segmentDurationsMs.length > 0) {
|
|
119
|
+
let cumulativeTime = 0;
|
|
120
|
+
for (let i = 0; i < track.segmentDurationsMs.length; i++) {
|
|
121
|
+
const segmentDuration = track.segmentDurationsMs[i];
|
|
122
|
+
if (segmentDuration === void 0) throw new Error("Segment duration is required for JIT metadata");
|
|
123
|
+
const segmentStartMs = cumulativeTime;
|
|
124
|
+
const segmentEndMs = cumulativeTime + segmentDuration;
|
|
125
|
+
const includesEndTime = i === track.segmentDurationsMs.length - 1 && desiredSeekTimeMs === durationMs;
|
|
126
|
+
if (desiredSeekTimeMs >= segmentStartMs && (desiredSeekTimeMs < segmentEndMs || includesEndTime)) return i + 1;
|
|
127
|
+
cumulativeTime += segmentDuration;
|
|
128
|
+
if (cumulativeTime >= durationMs) break;
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!track.segmentDurationMs) throw new Error("Segment duration is required for JIT metadata");
|
|
133
|
+
const segmentIndex = Math.floor(desiredSeekTimeMs / track.segmentDurationMs);
|
|
134
|
+
if (segmentIndex * track.segmentDurationMs >= durationMs) return;
|
|
135
|
+
return segmentIndex + 1;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
durationMs,
|
|
139
|
+
tracks,
|
|
140
|
+
segmentAt(timeMs, track) {
|
|
141
|
+
return computeSegmentIdForTrack(timeMs, track);
|
|
142
|
+
},
|
|
143
|
+
segmentsInRange(fromMs, toMs, track) {
|
|
144
|
+
if (fromMs >= toMs) return [];
|
|
145
|
+
const segments = [];
|
|
146
|
+
if (track.segmentDurationsMs && track.segmentDurationsMs.length > 0) {
|
|
147
|
+
let cumulativeTime = 0;
|
|
148
|
+
for (let i = 0; i < track.segmentDurationsMs.length; i++) {
|
|
149
|
+
const segmentDuration = track.segmentDurationsMs[i];
|
|
150
|
+
if (segmentDuration === void 0) continue;
|
|
151
|
+
const segmentStartMs = cumulativeTime;
|
|
152
|
+
const segmentEndMs = Math.min(cumulativeTime + segmentDuration, durationMs);
|
|
153
|
+
if (segmentStartMs >= durationMs) break;
|
|
154
|
+
if (segmentStartMs < toMs && segmentEndMs > fromMs) segments.push({
|
|
155
|
+
segmentId: i + 1,
|
|
156
|
+
startMs: segmentStartMs,
|
|
157
|
+
endMs: segmentEndMs
|
|
158
|
+
});
|
|
159
|
+
cumulativeTime += segmentDuration;
|
|
160
|
+
if (cumulativeTime >= durationMs) break;
|
|
161
|
+
}
|
|
162
|
+
return segments;
|
|
163
|
+
}
|
|
164
|
+
const segmentDurationMs = track.segmentDurationMs || 1e3;
|
|
165
|
+
const startSegmentIndex = Math.floor(fromMs / segmentDurationMs);
|
|
166
|
+
const endSegmentIndex = Math.floor(toMs / segmentDurationMs);
|
|
167
|
+
for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {
|
|
168
|
+
const segmentId = i + 1;
|
|
169
|
+
const segmentStartMs = i * segmentDurationMs;
|
|
170
|
+
const segmentEndMs = Math.min((i + 1) * segmentDurationMs, durationMs);
|
|
171
|
+
if (segmentStartMs >= durationMs) break;
|
|
172
|
+
if (segmentStartMs < toMs && segmentEndMs > fromMs) segments.push({
|
|
173
|
+
segmentId,
|
|
174
|
+
startMs: segmentStartMs,
|
|
175
|
+
endMs: segmentEndMs
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return segments;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
//#endregion
|
|
184
|
+
export { createFragmentIndex, createManifestIndex };
|
|
185
|
+
//# sourceMappingURL=SegmentIndex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SegmentIndex.js","names":["tracks: TrackSet","distance: number","ranges: SegmentTimeRange[]","segments: SegmentTimeRange[]"],"sources":["../../../src/elements/EFMedia/SegmentIndex.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { ManifestResponse } from \"../../transcoding/types/index.js\";\nimport {\n convertToScaledTime,\n roundToMilliseconds,\n} from \"./shared/PrecisionUtils.js\";\n\nexport type TrackRole = \"video\" | \"audio\" | \"scrub\";\n\nexport interface TrackRef {\n readonly role: TrackRole;\n readonly id: string | number;\n readonly src: string;\n readonly segmentDurationMs?: number;\n readonly segmentDurationsMs?: number[];\n readonly startTimeOffsetMs?: number;\n}\n\nexport interface TrackSet {\n video?: TrackRef;\n audio?: TrackRef;\n scrub?: TrackRef;\n}\n\nexport interface SegmentTimeRange {\n segmentId: number;\n startMs: number;\n endMs: number;\n}\n\nexport interface SegmentIndex {\n readonly durationMs: number;\n readonly tracks: TrackSet;\n segmentAt(timeMs: number, track: TrackRef): number | undefined;\n segmentsInRange(\n fromMs: number,\n toMs: number,\n track: TrackRef,\n ): SegmentTimeRange[];\n}\n\n// ---------------------------------------------------------------------------\n// FragmentIndex — backed by TrackFragmentIndex (local and file-id files)\n// ---------------------------------------------------------------------------\n\nexport function createFragmentIndex(\n data: Record<number, TrackFragmentIndex>,\n src: string,\n): SegmentIndex {\n const longestFragment = Object.values(data).reduce(\n (max, fragment) => Math.max(max, fragment.duration / fragment.timescale),\n 0,\n );\n const durationMs = longestFragment * 1000;\n\n const audioTrack = Object.values(data).find((t) => t.type === \"audio\");\n const videoTrack = Object.values(data).find(\n (t) => t.type === \"video\" && t.track !== undefined && t.track > 0,\n );\n const scrubTrack = data[-1];\n\n const tracks: TrackSet = {};\n\n if (videoTrack && videoTrack.track !== undefined) {\n tracks.video = {\n role: \"video\",\n id: videoTrack.track,\n src,\n startTimeOffsetMs: videoTrack.startTimeOffsetMs,\n };\n }\n\n if (audioTrack && audioTrack.track !== undefined) {\n tracks.audio = {\n role: \"audio\",\n id: audioTrack.track,\n src,\n };\n }\n\n if (scrubTrack && scrubTrack.track !== undefined) {\n const segmentDurationsMs =\n scrubTrack.segments.length > 0\n ? scrubTrack.segments.map(\n (s) => (s.duration / scrubTrack.timescale) * 1000,\n )\n : undefined;\n tracks.scrub = {\n role: \"scrub\",\n id: scrubTrack.track,\n src,\n segmentDurationMs: 30000,\n segmentDurationsMs,\n startTimeOffsetMs: scrubTrack.startTimeOffsetMs,\n };\n }\n\n return {\n durationMs,\n tracks,\n\n segmentAt(timeMs: number, track: TrackRef): number | undefined {\n const trackId =\n typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n const trackData = data[trackId];\n if (!trackData) {\n throw new Error(`Track ${trackId} not found`);\n }\n const { timescale, segments } = trackData;\n\n const startTimeOffsetMs = track.startTimeOffsetMs || 0;\n const offsetSeekTimeMs = roundToMilliseconds(timeMs + startTimeOffsetMs);\n const scaledSeekTime = convertToScaledTime(offsetSeekTimeMs, timescale);\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i]!;\n const segmentEndTime = segment.cts + segment.duration;\n if (segment.cts <= scaledSeekTime && scaledSeekTime < segmentEndTime) {\n return i;\n }\n }\n\n // Gap handling: find nearest segment\n let nearestSegmentIndex = 0;\n let nearestDistance = Number.MAX_SAFE_INTEGER;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n const segmentEndTime = segment.cts + segment.duration;\n\n let distance: number;\n if (scaledSeekTime < segment.cts) {\n distance = segment.cts - scaledSeekTime;\n } else if (scaledSeekTime >= segmentEndTime) {\n distance = scaledSeekTime - segmentEndTime;\n } else {\n return i;\n }\n\n if (distance < nearestDistance) {\n nearestDistance = distance;\n nearestSegmentIndex = i;\n }\n }\n\n return nearestSegmentIndex;\n },\n\n segmentsInRange(\n fromMs: number,\n toMs: number,\n track: TrackRef,\n ): SegmentTimeRange[] {\n if (fromMs >= toMs) return [];\n\n const trackId =\n typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n const trackData = data[trackId];\n if (!trackData) return [];\n\n const { timescale, segments } = trackData;\n const ranges: SegmentTimeRange[] = [];\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n const segmentStartMs = (segment.cts / timescale) * 1000;\n const segmentEndMs =\n ((segment.cts + segment.duration) / timescale) * 1000;\n\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n ranges.push({\n segmentId: i,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n }\n\n return ranges;\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// ManifestIndex — backed by ManifestResponse (JIT transcoding)\n// ---------------------------------------------------------------------------\n\nexport function createManifestIndex(manifest: ManifestResponse): SegmentIndex {\n const durationMs = manifest.durationMs;\n const tracks: TrackSet = {};\n\n if (manifest.videoRenditions && manifest.videoRenditions.length > 0) {\n const r = manifest.videoRenditions[0]!;\n tracks.video = {\n role: \"video\",\n id: r.id,\n src: manifest.sourceUrl,\n segmentDurationMs: r.segmentDurationMs,\n segmentDurationsMs: r.segmentDurationsMs,\n startTimeOffsetMs: r.startTimeOffsetMs,\n };\n\n const scrubRendition = manifest.videoRenditions.find(\n (v) => v.id === \"scrub\",\n );\n if (scrubRendition) {\n tracks.scrub = {\n role: \"scrub\",\n id: scrubRendition.id,\n src: manifest.sourceUrl,\n segmentDurationMs: scrubRendition.segmentDurationMs,\n segmentDurationsMs: scrubRendition.segmentDurationsMs,\n startTimeOffsetMs: scrubRendition.startTimeOffsetMs,\n };\n }\n }\n\n if (manifest.audioRenditions && manifest.audioRenditions.length > 0) {\n const r = manifest.audioRenditions[0]!;\n tracks.audio = {\n role: \"audio\",\n id: r.id,\n src: manifest.sourceUrl,\n segmentDurationMs: r.segmentDurationMs,\n segmentDurationsMs: r.segmentDurationsMs,\n startTimeOffsetMs: r.startTimeOffsetMs,\n };\n }\n\n function computeSegmentIdForTrack(\n desiredSeekTimeMs: number,\n track: TrackRef,\n ): number | undefined {\n if (desiredSeekTimeMs > durationMs) {\n return undefined;\n }\n\n if (track.segmentDurationsMs && track.segmentDurationsMs.length > 0) {\n let cumulativeTime = 0;\n for (let i = 0; i < track.segmentDurationsMs.length; i++) {\n const segmentDuration = track.segmentDurationsMs[i];\n if (segmentDuration === undefined) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = cumulativeTime + segmentDuration;\n\n const isLastSegment = i === track.segmentDurationsMs.length - 1;\n const includesEndTime =\n isLastSegment && desiredSeekTimeMs === durationMs;\n\n if (\n desiredSeekTimeMs >= segmentStartMs &&\n (desiredSeekTimeMs < segmentEndMs || includesEndTime)\n ) {\n return i + 1;\n }\n\n cumulativeTime += segmentDuration;\n if (cumulativeTime >= durationMs) break;\n }\n return undefined;\n }\n\n if (!track.segmentDurationMs) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n\n const segmentIndex = Math.floor(\n desiredSeekTimeMs / track.segmentDurationMs,\n );\n const segmentStartMs = segmentIndex * track.segmentDurationMs;\n if (segmentStartMs >= durationMs) {\n return undefined;\n }\n return segmentIndex + 1;\n }\n\n return {\n durationMs,\n tracks,\n\n segmentAt(timeMs: number, track: TrackRef): number | undefined {\n return computeSegmentIdForTrack(timeMs, track);\n },\n\n segmentsInRange(\n fromMs: number,\n toMs: number,\n track: TrackRef,\n ): SegmentTimeRange[] {\n if (fromMs >= toMs) return [];\n\n const segments: SegmentTimeRange[] = [];\n\n if (track.segmentDurationsMs && track.segmentDurationsMs.length > 0) {\n let cumulativeTime = 0;\n for (let i = 0; i < track.segmentDurationsMs.length; i++) {\n const segmentDuration = track.segmentDurationsMs[i];\n if (segmentDuration === undefined) continue;\n const segmentStartMs = cumulativeTime;\n const segmentEndMs = Math.min(\n cumulativeTime + segmentDuration,\n durationMs,\n );\n\n if (segmentStartMs >= durationMs) break;\n\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n segments.push({\n segmentId: i + 1,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n\n cumulativeTime += segmentDuration;\n if (cumulativeTime >= durationMs) break;\n }\n return segments;\n }\n\n const segmentDurationMs = track.segmentDurationMs || 1000;\n const startSegmentIndex = Math.floor(fromMs / segmentDurationMs);\n const endSegmentIndex = Math.floor(toMs / segmentDurationMs);\n\n for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {\n const segmentId = i + 1;\n const segmentStartMs = i * segmentDurationMs;\n const segmentEndMs = Math.min((i + 1) * segmentDurationMs, durationMs);\n\n if (segmentStartMs >= durationMs) break;\n if (segmentStartMs < toMs && segmentEndMs > fromMs) {\n segments.push({\n segmentId,\n startMs: segmentStartMs,\n endMs: segmentEndMs,\n });\n }\n }\n\n return segments;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TrackRef } from "./SegmentIndex.js";
|
|
2
|
+
import "@editframe/assets";
|
|
3
|
+
|
|
4
|
+
//#region src/elements/EFMedia/SegmentTransport.d.ts
|
|
5
|
+
interface SegmentTransport {
|
|
6
|
+
fetchInitSegment(track: TrackRef, signal: AbortSignal): Promise<ArrayBuffer>;
|
|
7
|
+
fetchMediaSegment(segmentId: number, track: TrackRef, signal: AbortSignal): Promise<ArrayBuffer>;
|
|
8
|
+
isCached(segmentId: number, track: TrackRef): boolean;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { SegmentTransport };
|
|
12
|
+
//# sourceMappingURL=SegmentTransport.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/elements/EFMedia/SegmentTransport.ts
|
|
2
|
+
function resolveRenditionId(track) {
|
|
3
|
+
if (track.role === "audio") return "audio";
|
|
4
|
+
if (track.role === "scrub") return "scrub";
|
|
5
|
+
if (typeof track.id === "string") return track.id;
|
|
6
|
+
if (track.id === -1) return "scrub";
|
|
7
|
+
if (track.id === 2) return "audio";
|
|
8
|
+
return "high";
|
|
9
|
+
}
|
|
10
|
+
function createUrlTransport(opts) {
|
|
11
|
+
const { fetcher, src, templates, audioTrackId, videoTrackId } = opts;
|
|
12
|
+
function buildSegmentUrl(segmentId, track) {
|
|
13
|
+
const renditionId = resolveRenditionId(track);
|
|
14
|
+
const template = segmentId === "init" ? templates.initSegment : templates.mediaSegment;
|
|
15
|
+
const trackId = typeof track.id === "number" ? track.id : track.role === "audio" ? audioTrackId : videoTrackId;
|
|
16
|
+
return template.replace("{rendition}", renditionId).replace("{segmentId}", segmentId.toString()).replace("{src}", src).replace("{trackId}", trackId?.toString() ?? "");
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
async fetchInitSegment(track, signal) {
|
|
20
|
+
const url = buildSegmentUrl("init", track);
|
|
21
|
+
return fetcher.fetchArrayBuffer(url, signal);
|
|
22
|
+
},
|
|
23
|
+
async fetchMediaSegment(segmentId, track, signal) {
|
|
24
|
+
const url = buildSegmentUrl(segmentId, track);
|
|
25
|
+
return fetcher.fetchArrayBuffer(url, signal);
|
|
26
|
+
},
|
|
27
|
+
isCached(segmentId, track) {
|
|
28
|
+
const url = buildSegmentUrl(segmentId, track);
|
|
29
|
+
return fetcher.has(url);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function createByteRangeTransport(data, fileId, apiHost, fetcher) {
|
|
34
|
+
function buildTrackUrl(trackId) {
|
|
35
|
+
return `${apiHost}/api/v1/files/${fileId}/tracks/${trackId}`;
|
|
36
|
+
}
|
|
37
|
+
function getTrackId(track) {
|
|
38
|
+
const trackId = typeof track.id === "number" ? track.id : Number.parseInt(track.id, 10);
|
|
39
|
+
if (Number.isNaN(trackId)) throw new Error(`Invalid track ID: ${track.id}`);
|
|
40
|
+
return trackId;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
async fetchInitSegment(track, signal) {
|
|
44
|
+
const trackId = getTrackId(track);
|
|
45
|
+
const trackData = data[trackId];
|
|
46
|
+
if (!trackData) throw new Error(`Track ${trackId} not found`);
|
|
47
|
+
const { offset, size } = trackData.initSegment;
|
|
48
|
+
const url = buildTrackUrl(trackId);
|
|
49
|
+
return (await fetcher.fetchArrayBuffer(url, signal)).slice(offset, offset + size);
|
|
50
|
+
},
|
|
51
|
+
async fetchMediaSegment(segmentId, track, signal) {
|
|
52
|
+
const trackId = getTrackId(track);
|
|
53
|
+
const trackData = data[trackId];
|
|
54
|
+
if (!trackData) throw new Error(`Track ${trackId} not found`);
|
|
55
|
+
const segment = trackData.segments[segmentId];
|
|
56
|
+
if (!segment) throw new Error(`Segment ${segmentId} not found for track ${trackId}`);
|
|
57
|
+
const url = buildTrackUrl(trackId);
|
|
58
|
+
return (await fetcher.fetchArrayBuffer(url, signal)).slice(segment.offset, segment.offset + segment.size);
|
|
59
|
+
},
|
|
60
|
+
isCached(_segmentId, track) {
|
|
61
|
+
const url = buildTrackUrl(getTrackId(track));
|
|
62
|
+
return fetcher.has(url);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
export { createByteRangeTransport, createUrlTransport };
|
|
69
|
+
//# sourceMappingURL=SegmentTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SegmentTransport.js","names":[],"sources":["../../../src/elements/EFMedia/SegmentTransport.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { RenditionId } from \"../../transcoding/types/index.js\";\nimport type { TrackRef } from \"./SegmentIndex.js\";\nimport type { CachedFetcher } from \"./CachedFetcher.js\";\n\nexport interface SegmentTransport {\n fetchInitSegment(track: TrackRef, signal: AbortSignal): Promise<ArrayBuffer>;\n fetchMediaSegment(\n segmentId: number,\n track: TrackRef,\n signal: AbortSignal,\n ): Promise<ArrayBuffer>;\n isCached(segmentId: number, track: TrackRef): boolean;\n}\n\n// ---------------------------------------------------------------------------\n// UrlTransport — each segment has its own URL\n// Used by AssetMediaEngine (via JIT URLs) and JitMediaEngine natively.\n// ---------------------------------------------------------------------------\n\ninterface UrlTransportOptions {\n fetcher: CachedFetcher;\n src: string;\n templates: { initSegment: string; mediaSegment: string };\n audioTrackId: number | undefined;\n videoTrackId: number | undefined;\n}\n\nfunction resolveRenditionId(track: TrackRef): RenditionId {\n if (track.role === \"audio\") return \"audio\";\n if (track.role === \"scrub\") return \"scrub\";\n if (typeof track.id === \"string\") return track.id as RenditionId;\n // For numeric IDs (fragment-based), map to JIT rendition names\n if (track.id === -1) return \"scrub\";\n if (track.id === 2) return \"audio\";\n return \"high\";\n}\n\nexport function createUrlTransport(\n opts: UrlTransportOptions,\n): SegmentTransport {\n const { fetcher, src, templates, audioTrackId, videoTrackId } = opts;\n\n function buildSegmentUrl(\n segmentId: \"init\" | number,\n track: TrackRef,\n ): string {\n const renditionId = resolveRenditionId(track);\n const template =\n segmentId === \"init\" ? templates.initSegment : templates.mediaSegment;\n const trackId =\n typeof track.id === \"number\"\n ? track.id\n : track.role === \"audio\"\n ? audioTrackId\n : videoTrackId;\n return template\n .replace(\"{rendition}\", renditionId)\n .replace(\"{segmentId}\", segmentId.toString())\n .replace(\"{src}\", src)\n .replace(\"{trackId}\", trackId?.toString() ?? \"\");\n }\n\n return {\n async fetchInitSegment(\n track: TrackRef,\n signal: AbortSignal,\n ): Promise<ArrayBuffer> {\n const url = buildSegmentUrl(\"init\", track);\n return fetcher.fetchArrayBuffer(url, signal);\n },\n\n async fetchMediaSegment(\n segmentId: number,\n track: TrackRef,\n signal: AbortSignal,\n ): Promise<ArrayBuffer> {\n const url = buildSegmentUrl(segmentId, track);\n return fetcher.fetchArrayBuffer(url, signal);\n },\n\n isCached(segmentId: number, track: TrackRef): boolean {\n const url = buildSegmentUrl(segmentId, track);\n return fetcher.has(url);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// ByteRangeTransport — fetches full track binary, slices segments\n// Used by FileMediaEngine.\n// ---------------------------------------------------------------------------\n\nexport function createByteRangeTransport(\n data: Record<number, TrackFragmentIndex>,\n fileId: string,\n apiHost: string,\n fetcher: CachedFetcher,\n): SegmentTransport {\n function buildTrackUrl(trackId: number): string {\n return `${apiHost}/api/v1/files/${fileId}/tracks/${trackId}`;\n }\n\n function getTrackId(track: TrackRef): number {\n const trackId =\n typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n if (Number.isNaN(trackId)) {\n throw new Error(`Invalid track ID: ${track.id}`);\n }\n return trackId;\n }\n\n return {\n async fetchInitSegment(\n track: TrackRef,\n signal: AbortSignal,\n ): Promise<ArrayBuffer> {\n const trackId = getTrackId(track);\n const trackData = data[trackId];\n if (!trackData) throw new Error(`Track ${trackId} not found`);\n\n const { offset, size } = trackData.initSegment;\n const url = buildTrackUrl(trackId);\n const fullTrack = await fetcher.fetchArrayBuffer(url, signal);\n return fullTrack.slice(offset, offset + size);\n },\n\n async fetchMediaSegment(\n segmentId: number,\n track: TrackRef,\n signal: AbortSignal,\n ): Promise<ArrayBuffer> {\n const trackId = getTrackId(track);\n const trackData = data[trackId];\n if (!trackData) throw new Error(`Track ${trackId} not found`);\n\n const segment = trackData.segments[segmentId];\n if (!segment) {\n throw new Error(`Segment ${segmentId} not found for track ${trackId}`);\n }\n\n const url = buildTrackUrl(trackId);\n const fullTrack = await fetcher.fetchArrayBuffer(url, signal);\n return fullTrack.slice(segment.offset, segment.offset + segment.size);\n },\n\n isCached(_segmentId: number, track: TrackRef): boolean {\n const trackId = getTrackId(track);\n const url = buildTrackUrl(trackId);\n return fetcher.has(url);\n },\n };\n}\n"],"mappings":";AA4BA,SAAS,mBAAmB,OAA8B;AACxD,KAAI,MAAM,SAAS,QAAS,QAAO;AACnC,KAAI,MAAM,SAAS,QAAS,QAAO;AACnC,KAAI,OAAO,MAAM,OAAO,SAAU,QAAO,MAAM;AAE/C,KAAI,MAAM,OAAO,GAAI,QAAO;AAC5B,KAAI,MAAM,OAAO,EAAG,QAAO;AAC3B,QAAO;;AAGT,SAAgB,mBACd,MACkB;CAClB,MAAM,EAAE,SAAS,KAAK,WAAW,cAAc,iBAAiB;CAEhE,SAAS,gBACP,WACA,OACQ;EACR,MAAM,cAAc,mBAAmB,MAAM;EAC7C,MAAM,WACJ,cAAc,SAAS,UAAU,cAAc,UAAU;EAC3D,MAAM,UACJ,OAAO,MAAM,OAAO,WAChB,MAAM,KACN,MAAM,SAAS,UACb,eACA;AACR,SAAO,SACJ,QAAQ,eAAe,YAAY,CACnC,QAAQ,eAAe,UAAU,UAAU,CAAC,CAC5C,QAAQ,SAAS,IAAI,CACrB,QAAQ,aAAa,SAAS,UAAU,IAAI,GAAG;;AAGpD,QAAO;EACL,MAAM,iBACJ,OACA,QACsB;GACtB,MAAM,MAAM,gBAAgB,QAAQ,MAAM;AAC1C,UAAO,QAAQ,iBAAiB,KAAK,OAAO;;EAG9C,MAAM,kBACJ,WACA,OACA,QACsB;GACtB,MAAM,MAAM,gBAAgB,WAAW,MAAM;AAC7C,UAAO,QAAQ,iBAAiB,KAAK,OAAO;;EAG9C,SAAS,WAAmB,OAA0B;GACpD,MAAM,MAAM,gBAAgB,WAAW,MAAM;AAC7C,UAAO,QAAQ,IAAI,IAAI;;EAE1B;;AAQH,SAAgB,yBACd,MACA,QACA,SACA,SACkB;CAClB,SAAS,cAAc,SAAyB;AAC9C,SAAO,GAAG,QAAQ,gBAAgB,OAAO,UAAU;;CAGrD,SAAS,WAAW,OAAyB;EAC3C,MAAM,UACJ,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK,OAAO,SAAS,MAAM,IAAI,GAAG;AACzE,MAAI,OAAO,MAAM,QAAQ,CACvB,OAAM,IAAI,MAAM,qBAAqB,MAAM,KAAK;AAElD,SAAO;;AAGT,QAAO;EACL,MAAM,iBACJ,OACA,QACsB;GACtB,MAAM,UAAU,WAAW,MAAM;GACjC,MAAM,YAAY,KAAK;AACvB,OAAI,CAAC,UAAW,OAAM,IAAI,MAAM,SAAS,QAAQ,YAAY;GAE7D,MAAM,EAAE,QAAQ,SAAS,UAAU;GACnC,MAAM,MAAM,cAAc,QAAQ;AAElC,WADkB,MAAM,QAAQ,iBAAiB,KAAK,OAAO,EAC5C,MAAM,QAAQ,SAAS,KAAK;;EAG/C,MAAM,kBACJ,WACA,OACA,QACsB;GACtB,MAAM,UAAU,WAAW,MAAM;GACjC,MAAM,YAAY,KAAK;AACvB,OAAI,CAAC,UAAW,OAAM,IAAI,MAAM,SAAS,QAAQ,YAAY;GAE7D,MAAM,UAAU,UAAU,SAAS;AACnC,OAAI,CAAC,QACH,OAAM,IAAI,MAAM,WAAW,UAAU,uBAAuB,UAAU;GAGxE,MAAM,MAAM,cAAc,QAAQ;AAElC,WADkB,MAAM,QAAQ,iBAAiB,KAAK,OAAO,EAC5C,MAAM,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,KAAK;;EAGvE,SAAS,YAAoB,OAA0B;GAErD,MAAM,MAAM,cADI,WAAW,MAAM,CACC;AAClC,UAAO,QAAQ,IAAI,IAAI;;EAE1B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TrackRef } from "./SegmentIndex.js";
|
|
2
|
+
import "@editframe/assets";
|
|
3
|
+
|
|
4
|
+
//#region src/elements/EFMedia/TimingModel.d.ts
|
|
5
|
+
interface TimingModel {
|
|
6
|
+
toContainerSeconds(timeMs: number, segmentId: number, track: TrackRef): number;
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
export { TimingModel };
|
|
10
|
+
//# sourceMappingURL=TimingModel.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/elements/EFMedia/TimingModel.ts
|
|
2
|
+
/**
|
|
3
|
+
* For byte-range sliced segments from full track files (FileMediaEngine).
|
|
4
|
+
* mediabunny sees segment-relative timestamps since we sliced at segment boundaries,
|
|
5
|
+
* so we subtract the segment's CTS to get relative time.
|
|
6
|
+
*/
|
|
7
|
+
function createByteRangeTiming(data) {
|
|
8
|
+
return { toContainerSeconds(timeMs, segmentId, track) {
|
|
9
|
+
const trackData = data[typeof track.id === "number" ? track.id : Number.parseInt(track.id, 10)];
|
|
10
|
+
if (!trackData) throw new Error("Track not found");
|
|
11
|
+
const segment = trackData.segments[segmentId];
|
|
12
|
+
if (!segment) throw new Error("Segment not found");
|
|
13
|
+
return (timeMs - segment.cts / trackData.timescale * 1e3) / 1e3;
|
|
14
|
+
} };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* For JIT transcoded segments (JitMediaEngine).
|
|
18
|
+
* Segments are self-contained — just convert ms to seconds.
|
|
19
|
+
*/
|
|
20
|
+
function createJitTiming() {
|
|
21
|
+
return { toContainerSeconds(timeMs, _segmentId, _track) {
|
|
22
|
+
return timeMs / 1e3;
|
|
23
|
+
} };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { createByteRangeTiming, createJitTiming };
|
|
28
|
+
//# sourceMappingURL=TimingModel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimingModel.js","names":[],"sources":["../../../src/elements/EFMedia/TimingModel.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type { TrackRef } from \"./SegmentIndex.js\";\n\nexport interface TimingModel {\n toContainerSeconds(\n timeMs: number,\n segmentId: number,\n track: TrackRef,\n ): number;\n}\n\n/**\n * For byte-range sliced segments from full track files (FileMediaEngine).\n * mediabunny sees segment-relative timestamps since we sliced at segment boundaries,\n * so we subtract the segment's CTS to get relative time.\n */\nexport function createByteRangeTiming(\n data: Record<number, TrackFragmentIndex>,\n): TimingModel {\n return {\n toContainerSeconds(\n timeMs: number,\n segmentId: number,\n track: TrackRef,\n ): number {\n const trackId =\n typeof track.id === \"number\" ? track.id : Number.parseInt(track.id, 10);\n const trackData = data[trackId];\n if (!trackData) throw new Error(\"Track not found\");\n\n const segment = trackData.segments[segmentId];\n if (!segment) throw new Error(\"Segment not found\");\n\n const segmentStartMs = (segment.cts / trackData.timescale) * 1000;\n return (timeMs - segmentStartMs) / 1000;\n },\n };\n}\n\n/**\n * For JIT transcoded segments (JitMediaEngine).\n * Segments are self-contained — just convert ms to seconds.\n */\nexport function createJitTiming(): TimingModel {\n return {\n toContainerSeconds(\n timeMs: number,\n _segmentId: number,\n _track: TrackRef,\n ): number {\n return timeMs / 1000;\n },\n };\n}\n"],"mappings":";;;;;;AAgBA,SAAgB,sBACd,MACa;AACb,QAAO,EACL,mBACE,QACA,WACA,OACQ;EAGR,MAAM,YAAY,KADhB,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK,OAAO,SAAS,MAAM,IAAI,GAAG;AAEzE,MAAI,CAAC,UAAW,OAAM,IAAI,MAAM,kBAAkB;EAElD,MAAM,UAAU,UAAU,SAAS;AACnC,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,oBAAoB;AAGlD,UAAQ,SADgB,QAAQ,MAAM,UAAU,YAAa,OAC1B;IAEtC;;;;;;AAOH,SAAgB,kBAA+B;AAC7C,QAAO,EACL,mBACE,QACA,YACA,QACQ;AACR,SAAO,SAAS;IAEnB"}
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Pure function with explicit dependencies
|
|
5
5
|
*/
|
|
6
6
|
const fetchAudioSegmentData = async (segmentIds, mediaEngine, signal) => {
|
|
7
|
-
const
|
|
8
|
-
if (!
|
|
7
|
+
const audioTrack = mediaEngine.tracks.audio;
|
|
8
|
+
if (!audioTrack) throw new Error("Audio track not available");
|
|
9
9
|
const segmentData = /* @__PURE__ */ new Map();
|
|
10
10
|
const fetchPromises = segmentIds.map(async (segmentId) => {
|
|
11
|
-
return [segmentId, await mediaEngine.fetchMediaSegment(segmentId,
|
|
11
|
+
return [segmentId, await mediaEngine.transport.fetchMediaSegment(segmentId, audioTrack, signal)];
|
|
12
12
|
});
|
|
13
13
|
const fetchedSegments = await Promise.all(fetchPromises);
|
|
14
14
|
signal.throwIfAborted();
|
|
@@ -31,11 +31,12 @@ 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.getMediaEngine(signal);
|
|
33
33
|
signal.throwIfAborted();
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const audioTrack = mediaEngine?.tracks.audio;
|
|
35
|
+
if (!audioTrack) return;
|
|
36
|
+
const initSegment = await mediaEngine.transport.fetchInitSegment(audioTrack, signal);
|
|
36
37
|
signal.throwIfAborted();
|
|
37
38
|
if (!initSegment) return;
|
|
38
|
-
const segmentRanges = mediaEngine.
|
|
39
|
+
const segmentRanges = mediaEngine.index.segmentsInRange(fromMs, toMs, audioTrack);
|
|
39
40
|
if (segmentRanges.length === 0) throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);
|
|
40
41
|
const segmentIds = segmentRanges.map((r) => r.segmentId);
|
|
41
42
|
const segmentData = await fetchAudioSegmentData(segmentIds, mediaEngine, signal);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioSpanUtils.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/AudioSpanUtils.ts"],"sourcesContent":["import type {\n AudioSpan,\n MediaEngine,\n SegmentTimeRange,\n} from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\n\n/**\n * Fetch audio segment data using MediaEngine\n * Pure function with explicit dependencies\n */\nconst fetchAudioSegmentData = async (\n segmentIds: number[],\n mediaEngine: MediaEngine,\n signal: AbortSignal,\n): Promise<Map<number, ArrayBuffer>> => {\n const
|
|
1
|
+
{"version":3,"file":"AudioSpanUtils.js","names":[],"sources":["../../../../src/elements/EFMedia/shared/AudioSpanUtils.ts"],"sourcesContent":["import type {\n AudioSpan,\n MediaEngine,\n SegmentTimeRange,\n} from \"../../../transcoding/types\";\nimport type { EFMedia } from \"../../EFMedia\";\n\n/**\n * Fetch audio segment data using MediaEngine\n * Pure function with explicit dependencies\n */\nconst fetchAudioSegmentData = async (\n segmentIds: number[],\n mediaEngine: MediaEngine,\n signal: AbortSignal,\n): Promise<Map<number, ArrayBuffer>> => {\n const audioTrack = mediaEngine.tracks.audio;\n if (!audioTrack) {\n throw new Error(\"Audio track not available\");\n }\n\n const segmentData = new Map<number, ArrayBuffer>();\n\n const fetchPromises = segmentIds.map(async (segmentId) => {\n const arrayBuffer = await mediaEngine.transport.fetchMediaSegment(\n segmentId,\n audioTrack,\n signal,\n );\n return [segmentId, arrayBuffer] as [number, ArrayBuffer];\n });\n\n const fetchedSegments = await Promise.all(fetchPromises);\n signal.throwIfAborted();\n\n for (const [segmentId, arrayBuffer] of fetchedSegments) {\n segmentData.set(segmentId, arrayBuffer);\n }\n\n return segmentData;\n};\n\n/**\n * Create audio span blob from init segment and media segments\n * Pure function for blob creation\n */\nconst createAudioSpanBlob = (\n initSegment: ArrayBuffer,\n mediaSegments: ArrayBuffer[],\n): Blob => {\n const chunks = [initSegment, ...mediaSegments];\n return new Blob(chunks, { type: \"audio/mp4\" });\n};\n\n/**\n * Fetch audio spanning a time range\n * Main function that orchestrates segment calculation, fetching, and blob creation\n */\nexport const fetchAudioSpanningTime = async (\n host: EFMedia,\n fromMs: number,\n toMs: number,\n signal: AbortSignal,\n): Promise<AudioSpan | undefined> => {\n // Validate inputs\n if (fromMs >= toMs || fromMs < 0) {\n throw new Error(`Invalid time range: fromMs=${fromMs}, toMs=${toMs}`);\n }\n\n // Get media engine using the new async method\n const mediaEngine = await host.getMediaEngine(signal);\n signal.throwIfAborted();\n\n const audioTrack = mediaEngine?.tracks.audio;\n if (!audioTrack) {\n return undefined;\n }\n\n // Fetch the init segment\n const initSegment = await mediaEngine.transport.fetchInitSegment(\n audioTrack,\n signal,\n );\n signal.throwIfAborted();\n\n if (!initSegment) {\n return undefined;\n }\n\n // Calculate segments needed\n const segmentRanges = mediaEngine.index.segmentsInRange(\n fromMs,\n toMs,\n audioTrack,\n );\n\n if (segmentRanges.length === 0) {\n throw new Error(`No segments found for time range ${fromMs}-${toMs}ms`);\n }\n\n // Fetch segment data\n const segmentIds = segmentRanges.map((r: SegmentTimeRange) => r.segmentId);\n const segmentData = await fetchAudioSegmentData(\n segmentIds,\n mediaEngine,\n signal,\n );\n\n // Create ordered array of segments\n const orderedSegments = segmentIds.map((id: number) => {\n const segment = segmentData.get(id);\n if (!segment) {\n throw new Error(`Missing segment data for segment ID ${id}`);\n }\n return segment;\n });\n\n // Create blob\n const blob = createAudioSpanBlob(initSegment, orderedSegments);\n\n // Calculate actual time boundaries\n const actualStartMs = Math.min(\n ...segmentRanges.map((r: SegmentTimeRange) => r.startMs),\n );\n const actualEndMs = Math.max(\n ...segmentRanges.map((r: SegmentTimeRange) => r.endMs),\n );\n\n return {\n startMs: actualStartMs,\n endMs: actualEndMs,\n blob,\n };\n};\n"],"mappings":";;;;;AAWA,MAAM,wBAAwB,OAC5B,YACA,aACA,WACsC;CACtC,MAAM,aAAa,YAAY,OAAO;AACtC,KAAI,CAAC,WACH,OAAM,IAAI,MAAM,4BAA4B;CAG9C,MAAM,8BAAc,IAAI,KAA0B;CAElD,MAAM,gBAAgB,WAAW,IAAI,OAAO,cAAc;AAMxD,SAAO,CAAC,WALY,MAAM,YAAY,UAAU,kBAC9C,WACA,YACA,OACD,CAC8B;GAC/B;CAEF,MAAM,kBAAkB,MAAM,QAAQ,IAAI,cAAc;AACxD,QAAO,gBAAgB;AAEvB,MAAK,MAAM,CAAC,WAAW,gBAAgB,gBACrC,aAAY,IAAI,WAAW,YAAY;AAGzC,QAAO;;;;;;AAOT,MAAM,uBACJ,aACA,kBACS;CACT,MAAM,SAAS,CAAC,aAAa,GAAG,cAAc;AAC9C,QAAO,IAAI,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;;;;;;AAOhD,MAAa,yBAAyB,OACpC,MACA,QACA,MACA,WACmC;AAEnC,KAAI,UAAU,QAAQ,SAAS,EAC7B,OAAM,IAAI,MAAM,8BAA8B,OAAO,SAAS,OAAO;CAIvE,MAAM,cAAc,MAAM,KAAK,eAAe,OAAO;AACrD,QAAO,gBAAgB;CAEvB,MAAM,aAAa,aAAa,OAAO;AACvC,KAAI,CAAC,WACH;CAIF,MAAM,cAAc,MAAM,YAAY,UAAU,iBAC9C,YACA,OACD;AACD,QAAO,gBAAgB;AAEvB,KAAI,CAAC,YACH;CAIF,MAAM,gBAAgB,YAAY,MAAM,gBACtC,QACA,MACA,WACD;AAED,KAAI,cAAc,WAAW,EAC3B,OAAM,IAAI,MAAM,oCAAoC,OAAO,GAAG,KAAK,IAAI;CAIzE,MAAM,aAAa,cAAc,KAAK,MAAwB,EAAE,UAAU;CAC1E,MAAM,cAAc,MAAM,sBACxB,YACA,aACA,OACD;CAYD,MAAM,OAAO,oBAAoB,aATT,WAAW,KAAK,OAAe;EACrD,MAAM,UAAU,YAAY,IAAI,GAAG;AACnC,MAAI,CAAC,QACH,OAAM,IAAI,MAAM,uCAAuC,KAAK;AAE9D,SAAO;GACP,CAG4D;AAU9D,QAAO;EACL,SARoB,KAAK,IACzB,GAAG,cAAc,KAAK,MAAwB,EAAE,QAAQ,CACzD;EAOC,OANkB,KAAK,IACvB,GAAG,cAAc,KAAK,MAAwB,EAAE,MAAM,CACvD;EAKC;EACD"}
|
|
@@ -3,30 +3,23 @@ import { DEFAULT_MEDIABUNNY_TIMEOUT_MS, withTimeout } from "./timeoutUtils.js";
|
|
|
3
3
|
import { ALL_FORMATS, BlobSource, CanvasSink, Input } from "mediabunny";
|
|
4
4
|
|
|
5
5
|
//#region src/elements/EFMedia/shared/ThumbnailExtractor.ts
|
|
6
|
-
/**
|
|
7
|
-
* Shared thumbnail extraction logic for all MediaEngine implementations
|
|
8
|
-
* Eliminates code duplication and provides consistent behavior
|
|
9
|
-
*/
|
|
10
6
|
var ThumbnailExtractor = class {
|
|
11
7
|
constructor(mediaEngine) {
|
|
12
8
|
this.mediaEngine = mediaEngine;
|
|
13
9
|
}
|
|
14
|
-
|
|
15
|
-
* Extract thumbnails at multiple timestamps efficiently using segment batching
|
|
16
|
-
*/
|
|
17
|
-
async extractThumbnails(timestamps, rendition, durationMs, signal) {
|
|
10
|
+
async extractThumbnails(timestamps, track, durationMs, signal) {
|
|
18
11
|
if (timestamps.length === 0) return [];
|
|
19
12
|
const validTimestamps = timestamps.filter((timeMs) => timeMs >= 0 && timeMs <= durationMs);
|
|
20
13
|
if (validTimestamps.length === 0) {
|
|
21
14
|
console.warn(`ThumbnailExtractor: All timestamps out of bounds (0-${durationMs}ms)`);
|
|
22
15
|
return timestamps.map(() => null);
|
|
23
16
|
}
|
|
24
|
-
const segmentGroups = this.groupTimestampsBySegment(validTimestamps,
|
|
17
|
+
const segmentGroups = this.groupTimestampsBySegment(validTimestamps, track);
|
|
25
18
|
const results = /* @__PURE__ */ new Map();
|
|
26
19
|
for (const [segmentId, segmentTimestamps] of segmentGroups) {
|
|
27
20
|
signal?.throwIfAborted();
|
|
28
21
|
try {
|
|
29
|
-
const segmentResults = await this.extractSegmentThumbnails(segmentId, segmentTimestamps,
|
|
22
|
+
const segmentResults = await this.extractSegmentThumbnails(segmentId, segmentTimestamps, track, signal);
|
|
30
23
|
for (const [timestamp, thumbnail] of segmentResults) results.set(timestamp, thumbnail);
|
|
31
24
|
} catch (error) {
|
|
32
25
|
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
@@ -39,45 +32,38 @@ var ThumbnailExtractor = class {
|
|
|
39
32
|
return results.get(t) || null;
|
|
40
33
|
});
|
|
41
34
|
}
|
|
42
|
-
|
|
43
|
-
* Group timestamps by segment ID for efficient batch processing
|
|
44
|
-
*/
|
|
45
|
-
groupTimestampsBySegment(timestamps, rendition) {
|
|
35
|
+
groupTimestampsBySegment(timestamps, track) {
|
|
46
36
|
const segmentGroups = /* @__PURE__ */ new Map();
|
|
47
37
|
for (const timeMs of timestamps) try {
|
|
48
|
-
const segmentId = this.mediaEngine.
|
|
38
|
+
const segmentId = this.mediaEngine.index.segmentAt(timeMs, track);
|
|
49
39
|
if (segmentId !== void 0) {
|
|
50
40
|
if (!segmentGroups.has(segmentId)) segmentGroups.set(segmentId, []);
|
|
51
|
-
|
|
52
|
-
if (!segmentGroup) segmentGroups.set(segmentId, []);
|
|
53
|
-
segmentGroup.push(timeMs);
|
|
41
|
+
segmentGroups.get(segmentId).push(timeMs);
|
|
54
42
|
}
|
|
55
43
|
} catch (error) {
|
|
56
44
|
console.warn(`ThumbnailExtractor: Could not compute segment for timestamp ${timeMs}:`, error);
|
|
57
45
|
}
|
|
58
46
|
return segmentGroups;
|
|
59
47
|
}
|
|
60
|
-
|
|
61
|
-
* Extract thumbnails for a specific segment using CanvasSink
|
|
62
|
-
*/
|
|
63
|
-
async extractSegmentThumbnails(segmentId, timestamps, rendition, signal) {
|
|
48
|
+
async extractSegmentThumbnails(segmentId, timestamps, track, signal) {
|
|
64
49
|
const results = /* @__PURE__ */ new Map();
|
|
65
50
|
try {
|
|
66
51
|
signal?.throwIfAborted();
|
|
67
|
-
const initP = this.mediaEngine.fetchInitSegment(
|
|
68
|
-
const mediaP = this.mediaEngine.fetchMediaSegment(segmentId,
|
|
52
|
+
const initP = this.mediaEngine.transport.fetchInitSegment(track, signal);
|
|
53
|
+
const mediaP = this.mediaEngine.transport.fetchMediaSegment(segmentId, track, signal);
|
|
69
54
|
initP.catch(() => {});
|
|
70
55
|
mediaP.catch(() => {});
|
|
71
56
|
const [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);
|
|
72
57
|
signal?.throwIfAborted();
|
|
73
58
|
const segmentBlob = new Blob([initSegment, mediaSegment]);
|
|
74
|
-
|
|
59
|
+
const renditionId = typeof track.id === "string" ? track.id : void 0;
|
|
60
|
+
let input = globalInputCache.get(track.src, segmentId, renditionId);
|
|
75
61
|
if (!input) {
|
|
76
62
|
input = new Input({
|
|
77
63
|
formats: ALL_FORMATS,
|
|
78
64
|
source: new BlobSource(segmentBlob)
|
|
79
65
|
});
|
|
80
|
-
globalInputCache.set(
|
|
66
|
+
globalInputCache.set(track.src, segmentId, input, renditionId);
|
|
81
67
|
}
|
|
82
68
|
const videoTrack = await withTimeout(input.getPrimaryVideoTrack(), 5e3, "ThumbnailExtractor.getPrimaryVideoTrack", signal);
|
|
83
69
|
if (!videoTrack) {
|
|
@@ -86,7 +72,7 @@ var ThumbnailExtractor = class {
|
|
|
86
72
|
}
|
|
87
73
|
const sink = new CanvasSink(videoTrack);
|
|
88
74
|
const sortedTimestamps = [...timestamps].sort((a, b) => a - b);
|
|
89
|
-
const relativeTimestamps = this.
|
|
75
|
+
const relativeTimestamps = sortedTimestamps.map((ms) => this.mediaEngine.timing.toContainerSeconds(ms, segmentId, track));
|
|
90
76
|
const timestampResults = [];
|
|
91
77
|
const canvasIterator = sink.canvasesAtTimestamps(relativeTimestamps);
|
|
92
78
|
for await (const result of canvasIterator) {
|
|
@@ -113,13 +99,6 @@ var ThumbnailExtractor = class {
|
|
|
113
99
|
}
|
|
114
100
|
return results;
|
|
115
101
|
}
|
|
116
|
-
/**
|
|
117
|
-
* Convert global timestamps to segment-relative timestamps for mediabunny
|
|
118
|
-
* This is where the main difference between JIT and Asset engines lies
|
|
119
|
-
*/
|
|
120
|
-
convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
|
|
121
|
-
return this.mediaEngine.convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition);
|
|
122
|
-
}
|
|
123
102
|
};
|
|
124
103
|
|
|
125
104
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThumbnailExtractor.js","names":["mediaEngine: BaseMediaEngine"],"sources":["../../../../src/elements/EFMedia/shared/ThumbnailExtractor.ts"],"sourcesContent":["import { ALL_FORMATS, BlobSource, CanvasSink, Input } from \"mediabunny\";\nimport type {\n ThumbnailResult,\n VideoRendition,\n} from \"../../../transcoding/types/index.js\";\nimport type { BaseMediaEngine } from \"../BaseMediaEngine.js\";\nimport { globalInputCache } from \"./GlobalInputCache.js\";\nimport { withTimeout, DEFAULT_MEDIABUNNY_TIMEOUT_MS } from \"./timeoutUtils.js\";\n\n/**\n * Shared thumbnail extraction logic for all MediaEngine implementations\n * Eliminates code duplication and provides consistent behavior\n */\nexport class ThumbnailExtractor {\n constructor(private mediaEngine: BaseMediaEngine) {}\n\n /**\n * Extract thumbnails at multiple timestamps efficiently using segment batching\n */\n async extractThumbnails(\n timestamps: number[],\n rendition: VideoRendition,\n durationMs: number,\n signal?: AbortSignal,\n ): Promise<(ThumbnailResult | null)[]> {\n if (timestamps.length === 0) {\n return [];\n }\n\n // Validate and filter timestamps within bounds\n const validTimestamps = timestamps.filter(\n (timeMs) => timeMs >= 0 && timeMs <= durationMs,\n );\n\n if (validTimestamps.length === 0) {\n console.warn(\n `ThumbnailExtractor: All timestamps out of bounds (0-${durationMs}ms)`,\n );\n return timestamps.map(() => null);\n }\n\n // Group timestamps by segment for batch processing\n const segmentGroups = this.groupTimestampsBySegment(\n validTimestamps,\n rendition,\n );\n\n // Extract batched by segment using CanvasSink\n const results = new Map<number, ThumbnailResult | null>();\n\n for (const [segmentId, segmentTimestamps] of segmentGroups) {\n // Check abort before processing each segment\n signal?.throwIfAborted();\n\n try {\n const segmentResults = await this.extractSegmentThumbnails(\n segmentId,\n segmentTimestamps,\n rendition,\n signal,\n );\n\n for (const [timestamp, thumbnail] of segmentResults) {\n results.set(timestamp, thumbnail);\n }\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n console.warn(\n `ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`,\n error,\n );\n // Mark all timestamps in this segment as failed\n for (const timestamp of segmentTimestamps) {\n results.set(timestamp, null);\n }\n }\n }\n\n // Return in original order, null for any that failed or were out of bounds\n return timestamps.map((t) => {\n // If timestamp was out of bounds, return null\n if (t < 0 || t > durationMs) {\n return null;\n }\n return results.get(t) || null;\n });\n }\n\n /**\n * Group timestamps by segment ID for efficient batch processing\n */\n private groupTimestampsBySegment(\n timestamps: number[],\n rendition: VideoRendition,\n ): Map<number, number[]> {\n const segmentGroups = new Map<number, number[]>();\n\n for (const timeMs of timestamps) {\n try {\n const segmentId = this.mediaEngine.computeSegmentId(timeMs, rendition);\n if (segmentId !== undefined) {\n if (!segmentGroups.has(segmentId)) {\n segmentGroups.set(segmentId, []);\n }\n const segmentGroup = segmentGroups.get(segmentId) ?? [];\n if (!segmentGroup) {\n segmentGroups.set(segmentId, []);\n }\n segmentGroup.push(timeMs);\n }\n } catch (error) {\n console.warn(\n `ThumbnailExtractor: Could not compute segment for timestamp ${timeMs}:`,\n error,\n );\n }\n }\n\n return segmentGroups;\n }\n\n /**\n * Extract thumbnails for a specific segment using CanvasSink\n */\n private async extractSegmentThumbnails(\n segmentId: number,\n timestamps: number[],\n rendition: VideoRendition,\n signal?: AbortSignal,\n ): Promise<Map<number, ThumbnailResult | null>> {\n const results = new Map<number, ThumbnailResult | null>();\n\n try {\n // Check abort before starting segment fetch\n signal?.throwIfAborted();\n\n const initP = this.mediaEngine.fetchInitSegment(rendition, signal!);\n const mediaP = this.mediaEngine.fetchMediaSegment(\n segmentId,\n rendition,\n signal!,\n );\n initP.catch(() => {});\n mediaP.catch(() => {});\n const [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);\n\n // Check abort after potentially slow network operations\n signal?.throwIfAborted();\n\n // Create Input for this segment using global shared cache\n const segmentBlob = new Blob([initSegment, mediaSegment]);\n\n let input = globalInputCache.get(rendition.src, segmentId, rendition.id);\n if (!input) {\n input = new Input({\n formats: ALL_FORMATS,\n source: new BlobSource(segmentBlob),\n });\n globalInputCache.set(rendition.src, segmentId, input, rendition.id);\n }\n\n // Set up CanvasSink for batched extraction\n const videoTrack = await withTimeout(\n input.getPrimaryVideoTrack(),\n 5000,\n \"ThumbnailExtractor.getPrimaryVideoTrack\",\n signal,\n );\n if (!videoTrack) {\n // No video track - return nulls for all timestamps\n for (const timestamp of timestamps) {\n results.set(timestamp, null);\n }\n return results;\n }\n\n const sink = new CanvasSink(videoTrack);\n\n // IMPORTANT: Sort timestamps for mediabunny - it expects monotonically sorted timestamps\n // Create array of {original, sorted} to map back after extraction\n const sortedTimestamps = [...timestamps].sort((a, b) => a - b);\n\n // Convert sorted global timestamps to segment-relative (in seconds for mediabunny)\n const relativeTimestamps = this.convertToSegmentRelativeTimestamps(\n sortedTimestamps,\n segmentId,\n rendition,\n );\n\n // Batch extract all thumbnails for this segment (in sorted order)\n const timestampResults = [];\n const canvasIterator = sink.canvasesAtTimestamps(relativeTimestamps);\n for await (const result of canvasIterator) {\n // Wrap each iteration with timeout to prevent hangs\n const canvasResult = await withTimeout(\n Promise.resolve(result),\n DEFAULT_MEDIABUNNY_TIMEOUT_MS,\n \"ThumbnailExtractor canvasesAtTimestamps iteration\",\n signal,\n );\n timestampResults.push(canvasResult);\n }\n\n // Map results back to original (sorted) timestamps\n for (let i = 0; i < sortedTimestamps.length; i++) {\n const globalTimestamp = sortedTimestamps[i];\n if (globalTimestamp === undefined) {\n continue;\n }\n\n const result = timestampResults[i];\n\n if (result?.canvas) {\n const canvas = result.canvas;\n if (\n canvas instanceof HTMLCanvasElement ||\n canvas instanceof OffscreenCanvas\n ) {\n results.set(globalTimestamp, {\n timestamp: globalTimestamp,\n thumbnail: canvas,\n });\n } else {\n results.set(globalTimestamp, null);\n }\n } else {\n results.set(globalTimestamp, null);\n }\n }\n } catch (error) {\n // If aborted, re-throw to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Thumbnail extraction can fail for various non-fatal reasons (network issues,\n // missing segments, transcoding not ready). Log as warning and return nulls.\n console.warn(\n `ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`,\n error,\n );\n // Return nulls for all timestamps on error\n for (const timestamp of timestamps) {\n results.set(timestamp, null);\n }\n }\n\n return results;\n }\n\n /**\n * Convert global timestamps to segment-relative timestamps for mediabunny\n * This is where the main difference between JIT and Asset engines lies\n */\n private convertToSegmentRelativeTimestamps(\n globalTimestamps: number[],\n segmentId: number,\n rendition: VideoRendition,\n ): number[] {\n return this.mediaEngine.convertToSegmentRelativeTimestamps(\n globalTimestamps,\n segmentId,\n rendition,\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ThumbnailExtractor.js","names":["mediaEngine: MediaEngine"],"sources":["../../../../src/elements/EFMedia/shared/ThumbnailExtractor.ts"],"sourcesContent":["import { ALL_FORMATS, BlobSource, CanvasSink, Input } from \"mediabunny\";\nimport type { ThumbnailResult } from \"../../../transcoding/types/index.js\";\nimport type { MediaEngine } from \"../MediaEngine.js\";\nimport type { TrackRef } from \"../SegmentIndex.js\";\nimport { globalInputCache } from \"./GlobalInputCache.js\";\nimport { withTimeout, DEFAULT_MEDIABUNNY_TIMEOUT_MS } from \"./timeoutUtils.js\";\n\nexport class ThumbnailExtractor {\n constructor(private mediaEngine: MediaEngine) {}\n\n async extractThumbnails(\n timestamps: number[],\n track: TrackRef,\n durationMs: number,\n signal?: AbortSignal,\n ): Promise<(ThumbnailResult | null)[]> {\n if (timestamps.length === 0) {\n return [];\n }\n\n const validTimestamps = timestamps.filter(\n (timeMs) => timeMs >= 0 && timeMs <= durationMs,\n );\n\n if (validTimestamps.length === 0) {\n console.warn(\n `ThumbnailExtractor: All timestamps out of bounds (0-${durationMs}ms)`,\n );\n return timestamps.map(() => null);\n }\n\n const segmentGroups = this.groupTimestampsBySegment(validTimestamps, track);\n const results = new Map<number, ThumbnailResult | null>();\n\n for (const [segmentId, segmentTimestamps] of segmentGroups) {\n signal?.throwIfAborted();\n\n try {\n const segmentResults = await this.extractSegmentThumbnails(\n segmentId,\n segmentTimestamps,\n track,\n signal,\n );\n\n for (const [timestamp, thumbnail] of segmentResults) {\n results.set(timestamp, thumbnail);\n }\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n console.warn(\n `ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`,\n error,\n );\n for (const timestamp of segmentTimestamps) {\n results.set(timestamp, null);\n }\n }\n }\n\n return timestamps.map((t) => {\n if (t < 0 || t > durationMs) {\n return null;\n }\n return results.get(t) || null;\n });\n }\n\n private groupTimestampsBySegment(\n timestamps: number[],\n track: TrackRef,\n ): Map<number, number[]> {\n const segmentGroups = new Map<number, number[]>();\n\n for (const timeMs of timestamps) {\n try {\n const segmentId = this.mediaEngine.index.segmentAt(timeMs, track);\n if (segmentId !== undefined) {\n if (!segmentGroups.has(segmentId)) {\n segmentGroups.set(segmentId, []);\n }\n const segmentGroup = segmentGroups.get(segmentId)!;\n segmentGroup.push(timeMs);\n }\n } catch (error) {\n console.warn(\n `ThumbnailExtractor: Could not compute segment for timestamp ${timeMs}:`,\n error,\n );\n }\n }\n\n return segmentGroups;\n }\n\n private async extractSegmentThumbnails(\n segmentId: number,\n timestamps: number[],\n track: TrackRef,\n signal?: AbortSignal,\n ): Promise<Map<number, ThumbnailResult | null>> {\n const results = new Map<number, ThumbnailResult | null>();\n\n try {\n signal?.throwIfAborted();\n\n const initP = this.mediaEngine.transport.fetchInitSegment(track, signal!);\n const mediaP = this.mediaEngine.transport.fetchMediaSegment(\n segmentId,\n track,\n signal!,\n );\n initP.catch(() => {});\n mediaP.catch(() => {});\n const [initSegment, mediaSegment] = await Promise.all([initP, mediaP]);\n\n signal?.throwIfAborted();\n\n const segmentBlob = new Blob([initSegment, mediaSegment]);\n const renditionId = typeof track.id === \"string\" ? track.id : undefined;\n\n let input = globalInputCache.get(track.src, segmentId, renditionId);\n if (!input) {\n input = new Input({\n formats: ALL_FORMATS,\n source: new BlobSource(segmentBlob),\n });\n globalInputCache.set(track.src, segmentId, input, renditionId);\n }\n\n const videoTrack = await withTimeout(\n input.getPrimaryVideoTrack(),\n 5000,\n \"ThumbnailExtractor.getPrimaryVideoTrack\",\n signal,\n );\n if (!videoTrack) {\n for (const timestamp of timestamps) {\n results.set(timestamp, null);\n }\n return results;\n }\n\n const sink = new CanvasSink(videoTrack);\n const sortedTimestamps = [...timestamps].sort((a, b) => a - b);\n\n const relativeTimestamps = sortedTimestamps.map((ms) =>\n this.mediaEngine.timing.toContainerSeconds(ms, segmentId, track),\n );\n\n const timestampResults = [];\n const canvasIterator = sink.canvasesAtTimestamps(relativeTimestamps);\n for await (const result of canvasIterator) {\n const canvasResult = await withTimeout(\n Promise.resolve(result),\n DEFAULT_MEDIABUNNY_TIMEOUT_MS,\n \"ThumbnailExtractor canvasesAtTimestamps iteration\",\n signal,\n );\n timestampResults.push(canvasResult);\n }\n\n for (let i = 0; i < sortedTimestamps.length; i++) {\n const globalTimestamp = sortedTimestamps[i];\n if (globalTimestamp === undefined) {\n continue;\n }\n\n const result = timestampResults[i];\n\n if (result?.canvas) {\n const canvas = result.canvas;\n if (\n canvas instanceof HTMLCanvasElement ||\n canvas instanceof OffscreenCanvas\n ) {\n results.set(globalTimestamp, {\n timestamp: globalTimestamp,\n thumbnail: canvas,\n });\n } else {\n results.set(globalTimestamp, null);\n }\n } else {\n results.set(globalTimestamp, null);\n }\n }\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n console.warn(\n `ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`,\n error,\n );\n for (const timestamp of timestamps) {\n results.set(timestamp, null);\n }\n }\n\n return results;\n }\n}\n"],"mappings":"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"}
|
|
@@ -3,8 +3,9 @@ import { EFSourceMixinInterface } from "./EFSourceMixin.js";
|
|
|
3
3
|
import { TemporalMixinInterface } from "./EFTemporal.js";
|
|
4
4
|
import { FetchMixinInterface } from "./FetchMixin.js";
|
|
5
5
|
import { ControllableInterface } from "../gui/Controllable.js";
|
|
6
|
-
import { AudioSpan, MediaEngine } from "../transcoding/types/index.js";
|
|
7
6
|
import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
|
|
7
|
+
import { MediaEngine } from "./EFMedia/MediaEngine.js";
|
|
8
|
+
import { AudioSpan } from "../transcoding/types/index.js";
|
|
8
9
|
import * as lit0 from "lit";
|
|
9
10
|
import { LitElement, PropertyValueMap } from "lit";
|
|
10
11
|
|