@editframe/elements 0.38.0 → 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/canvas/EFCanvas.d.ts +4 -4
- package/dist/canvas/EFCanvasItem.d.ts +4 -4
- package/dist/canvas/overlays/SelectionOverlay.d.ts +2 -2
- package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
- 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/EFPanZoom.d.ts +4 -4
- package/dist/elements/EFSourceMixin.js +1 -1
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFSurface.d.ts +4 -4
- 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/EFActiveRootTemporal.d.ts +4 -4
- 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/EFControls.d.ts +2 -2
- package/dist/gui/EFDial.d.ts +4 -4
- package/dist/gui/EFFocusOverlay.d.ts +4 -4
- package/dist/gui/EFOverlayItem.d.ts +4 -4
- package/dist/gui/EFOverlayLayer.d.ts +4 -4
- package/dist/gui/EFPause.d.ts +4 -4
- package/dist/gui/EFPlay.d.ts +4 -4
- package/dist/gui/EFResizableBox.d.ts +4 -4
- package/dist/gui/EFScrubber.d.ts +4 -4
- package/dist/gui/EFTimeDisplay.d.ts +4 -4
- package/dist/gui/EFTimelineRuler.d.ts +4 -4
- package/dist/gui/EFToggleLoop.d.ts +4 -4
- package/dist/gui/EFTogglePlay.d.ts +4 -4
- package/dist/gui/EFTransformHandles.d.ts +4 -4
- 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/EFTimeline.js.map +1 -1
- package/dist/gui/timeline/EFTimelineRow.d.ts +2 -2
- 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/gui/tree/EFTree.d.ts +4 -4
- package/dist/gui/tree/EFTreeItem.d.ts +4 -4
- 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/renderTimegroupToCanvas.d.ts +144 -0
- package/dist/preview/renderTimegroupToCanvas.js +56 -3
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.types.d.ts +22 -1
- package/dist/preview/renderTimegroupToVideo.d.ts +27 -0
- package/dist/preview/renderTimegroupToVideo.js +13 -5
- 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/preview/renderers.d.ts +56 -0
- package/dist/preview/renderers.js +13 -1
- package/dist/preview/renderers.js.map +1 -1
- package/dist/preview/rendering/inlineImages.d.ts +13 -0
- package/dist/preview/rendering/inlineImages.js +7 -1
- package/dist/preview/rendering/inlineImages.js.map +1 -1
- package/dist/preview/rendering/loadImage.d.ts +8 -0
- package/dist/render/EFRenderAPI.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 +26 -2
- package/test/setup.ts +1 -1
- package/test/useAssetMSW.ts +0 -100
- package/tsdown.config.ts +6 -1
- 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
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { AssetMediaEngine } from "./AssetMediaEngine.js";
|
|
2
|
-
|
|
3
|
-
//#region src/elements/EFMedia/FileMediaEngine.ts
|
|
4
|
-
var FileMediaEngine = class FileMediaEngine extends AssetMediaEngine {
|
|
5
|
-
static async fetchByFileId(host, _urlGenerator, fileId, apiHost, requiredTracks = "both", signal) {
|
|
6
|
-
const url = `${apiHost}/api/v1/files/${fileId}/index`;
|
|
7
|
-
const response = await host.fetch(url, { signal });
|
|
8
|
-
signal?.throwIfAborted();
|
|
9
|
-
const contentType = response.headers.get("content-type");
|
|
10
|
-
if (!response.ok || contentType && !contentType.includes("application/json")) {
|
|
11
|
-
const text = await response.clone().text();
|
|
12
|
-
if (!response.ok) throw new Error(`Failed to fetch asset index: ${response.status} ${text}`);
|
|
13
|
-
throw new Error(`Expected JSON but got ${contentType}: ${text.substring(0, 100)}`);
|
|
14
|
-
}
|
|
15
|
-
let data;
|
|
16
|
-
try {
|
|
17
|
-
data = await response.json();
|
|
18
|
-
signal?.throwIfAborted();
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
21
|
-
throw new Error(`Failed to parse JSON response: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
-
}
|
|
23
|
-
const engine = new FileMediaEngine(host, fileId, data, apiHost, _urlGenerator);
|
|
24
|
-
signal?.throwIfAborted();
|
|
25
|
-
if (signal) {
|
|
26
|
-
const videoTrack = engine.getVideoTrackIndex();
|
|
27
|
-
const audioTrack = engine.getAudioTrackIndex();
|
|
28
|
-
const needsVideo = requiredTracks === "video" || requiredTracks === "both";
|
|
29
|
-
const needsAudio = requiredTracks === "audio" || requiredTracks === "both";
|
|
30
|
-
if (needsVideo && videoTrack && videoTrack.track !== void 0) try {
|
|
31
|
-
await engine.fetchInitSegment({
|
|
32
|
-
trackId: videoTrack.track,
|
|
33
|
-
src: engine.src
|
|
34
|
-
}, signal);
|
|
35
|
-
} catch (error) {
|
|
36
|
-
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
37
|
-
if (error instanceof Error && (error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch") && error.message.includes("401"))) throw new Error(`Video segments require authentication: ${error.message}`);
|
|
38
|
-
}
|
|
39
|
-
signal?.throwIfAborted();
|
|
40
|
-
if (needsAudio && audioTrack && audioTrack.track !== void 0) try {
|
|
41
|
-
await engine.fetchInitSegment({
|
|
42
|
-
trackId: audioTrack.track,
|
|
43
|
-
src: engine.src
|
|
44
|
-
}, signal);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
47
|
-
if (error instanceof Error && (error.message.includes("401") || error.message.includes("UNAUTHORIZED") || error.message.includes("Failed to fetch") && error.message.includes("401"))) throw new Error(`Audio segments require authentication: ${error.message}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return engine;
|
|
51
|
-
}
|
|
52
|
-
static {
|
|
53
|
-
this.fetchByAssetId = FileMediaEngine.fetchByFileId;
|
|
54
|
-
}
|
|
55
|
-
constructor(host, fileId, data, apiHost, urlGenerator) {
|
|
56
|
-
super(host, fileId, urlGenerator);
|
|
57
|
-
this.apiHost = apiHost;
|
|
58
|
-
this.fileId = fileId;
|
|
59
|
-
this.data = data;
|
|
60
|
-
this.durationMs = Object.values(this.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0) * 1e3;
|
|
61
|
-
this.templates = {
|
|
62
|
-
initSegment: `${apiHost}/api/v1/files/${fileId}/tracks/{trackId}`,
|
|
63
|
-
mediaSegment: `${apiHost}/api/v1/files/${fileId}/tracks/{trackId}`
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
/** @deprecated Use fileId instead */
|
|
67
|
-
get assetId() {
|
|
68
|
-
return this.fileId;
|
|
69
|
-
}
|
|
70
|
-
getInitSegmentPaths() {
|
|
71
|
-
const paths = {};
|
|
72
|
-
const audioTrack = this.getAudioTrackIndex();
|
|
73
|
-
const videoTrack = this.getVideoTrackIndex();
|
|
74
|
-
if (audioTrack !== void 0) paths.audio = {
|
|
75
|
-
path: `${this.apiHost}/api/v1/files/${this.fileId}/tracks/${audioTrack.track}`,
|
|
76
|
-
pos: audioTrack.initSegment.offset,
|
|
77
|
-
size: audioTrack.initSegment.size
|
|
78
|
-
};
|
|
79
|
-
if (videoTrack !== void 0) paths.video = {
|
|
80
|
-
path: `${this.apiHost}/api/v1/files/${this.fileId}/tracks/${videoTrack.track}`,
|
|
81
|
-
pos: videoTrack.initSegment.offset,
|
|
82
|
-
size: videoTrack.initSegment.size
|
|
83
|
-
};
|
|
84
|
-
return paths;
|
|
85
|
-
}
|
|
86
|
-
buildInitSegmentUrl(trackId) {
|
|
87
|
-
return `${this.apiHost}/api/v1/files/${this.fileId}/tracks/${trackId}`;
|
|
88
|
-
}
|
|
89
|
-
buildMediaSegmentUrl(trackId, _segmentId) {
|
|
90
|
-
return `${this.apiHost}/api/v1/files/${this.fileId}/tracks/${trackId}`;
|
|
91
|
-
}
|
|
92
|
-
async fetchInitSegment(rendition, signal) {
|
|
93
|
-
if (!rendition.trackId) throw new Error("[fetchInitSegment] Track ID is required for file-based media");
|
|
94
|
-
const trackData = this.data[rendition.trackId];
|
|
95
|
-
if (!trackData) throw new Error(`Track ${rendition.trackId} not found`);
|
|
96
|
-
const { offset, size } = trackData.initSegment;
|
|
97
|
-
const url = this.buildInitSegmentUrl(rendition.trackId);
|
|
98
|
-
return (await this.fetchMedia(url, signal)).slice(offset, offset + size);
|
|
99
|
-
}
|
|
100
|
-
async fetchMediaSegment(segmentId, rendition, signal) {
|
|
101
|
-
if (!rendition.trackId) throw new Error("[fetchMediaSegment] Track ID is required for file-based media");
|
|
102
|
-
const trackData = this.data[rendition.trackId];
|
|
103
|
-
if (!trackData) throw new Error(`Track ${rendition.trackId} not found`);
|
|
104
|
-
const segment = trackData.segments[segmentId];
|
|
105
|
-
if (!segment) throw new Error(`Segment ${segmentId} not found for track ${rendition.trackId}`);
|
|
106
|
-
const url = this.buildMediaSegmentUrl(rendition.trackId, segmentId);
|
|
107
|
-
return (await this.fetchMedia(url, signal)).slice(segment.offset, segment.offset + segment.size);
|
|
108
|
-
}
|
|
109
|
-
convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
|
|
110
|
-
if (!rendition.trackId) throw new Error("[convertToSegmentRelativeTimestamps] Track ID is required for asset metadata");
|
|
111
|
-
const trackData = this.data[rendition.trackId];
|
|
112
|
-
if (!trackData) throw new Error("Track not found");
|
|
113
|
-
const segment = trackData.segments?.[segmentId];
|
|
114
|
-
if (!segment) throw new Error("Segment not found");
|
|
115
|
-
const segmentStartMs = segment.cts / trackData.timescale * 1e3;
|
|
116
|
-
return globalTimestamps.map((globalMs) => (globalMs - segmentStartMs) / 1e3);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
//#endregion
|
|
121
|
-
export { FileMediaEngine };
|
|
122
|
-
//# sourceMappingURL=FileMediaEngine.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FileMediaEngine.js","names":["data: Record<number, TrackFragmentIndex>","apiHost: string","paths: InitSegmentPaths"],"sources":["../../../src/elements/EFMedia/FileMediaEngine.ts"],"sourcesContent":["import type { TrackFragmentIndex } from \"@editframe/assets\";\nimport type {\n InitSegmentPaths,\n MediaEngine,\n RenditionId,\n VideoRendition,\n} from \"../../transcoding/types\";\nimport type { UrlGenerator } from \"../../transcoding/utils/UrlGenerator\";\nimport type { EFMedia } from \"../EFMedia\";\nimport { AssetMediaEngine } from \"./AssetMediaEngine\";\n\nexport class FileMediaEngine extends AssetMediaEngine implements MediaEngine {\n static async fetchByFileId(\n host: EFMedia,\n _urlGenerator: UrlGenerator,\n fileId: string,\n apiHost: string,\n requiredTracks: \"audio\" | \"video\" | \"both\" = \"both\",\n signal?: AbortSignal,\n ) {\n const url = `${apiHost}/api/v1/files/${fileId}/index`;\n const response = await host.fetch(url, { signal });\n\n signal?.throwIfAborted();\n\n const contentType = response.headers.get(\"content-type\");\n\n if (\n !response.ok ||\n (contentType && !contentType.includes(\"application/json\"))\n ) {\n const text = await response.clone().text();\n if (!response.ok) {\n throw new Error(\n `Failed to fetch asset index: ${response.status} ${text}`,\n );\n }\n throw new Error(\n `Expected JSON but got ${contentType}: ${text.substring(0, 100)}`,\n );\n }\n\n let data: Record<number, TrackFragmentIndex>;\n try {\n data = (await response.json()) as Record<number, TrackFragmentIndex>;\n signal?.throwIfAborted();\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n throw new Error(\n `Failed to parse JSON response: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n const engine = new FileMediaEngine(\n host,\n fileId,\n data,\n apiHost,\n _urlGenerator,\n );\n\n signal?.throwIfAborted();\n\n if (signal) {\n const videoTrack = engine.getVideoTrackIndex();\n const audioTrack = engine.getAudioTrackIndex();\n const needsVideo =\n requiredTracks === \"video\" || requiredTracks === \"both\";\n const needsAudio =\n requiredTracks === \"audio\" || requiredTracks === \"both\";\n\n if (needsVideo && videoTrack && videoTrack.track !== undefined) {\n try {\n await engine.fetchInitSegment(\n { trackId: videoTrack.track, src: engine.src },\n signal,\n );\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n (error.message.includes(\"Failed to fetch\") &&\n error.message.includes(\"401\")))\n ) {\n throw new Error(\n `Video segments require authentication: ${error.message}`,\n );\n }\n }\n }\n\n signal?.throwIfAborted();\n\n if (needsAudio && audioTrack && audioTrack.track !== undefined) {\n try {\n await engine.fetchInitSegment(\n { trackId: audioTrack.track, src: engine.src },\n signal,\n );\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n if (\n error instanceof Error &&\n (error.message.includes(\"401\") ||\n error.message.includes(\"UNAUTHORIZED\") ||\n (error.message.includes(\"Failed to fetch\") &&\n error.message.includes(\"401\")))\n ) {\n throw new Error(\n `Audio segments require authentication: ${error.message}`,\n );\n }\n }\n }\n }\n\n return engine;\n }\n\n /** @deprecated Use fetchByFileId instead */\n static fetchByAssetId = FileMediaEngine.fetchByFileId;\n\n public fileId: string;\n\n constructor(\n host: EFMedia,\n fileId: string,\n data: Record<number, TrackFragmentIndex>,\n private apiHost: string,\n urlGenerator: UrlGenerator,\n ) {\n super(host, fileId, urlGenerator);\n this.fileId = fileId;\n this.data = data;\n\n const longestFragment = Object.values(this.data).reduce(\n (max, fragment) => Math.max(max, fragment.duration / fragment.timescale),\n 0,\n );\n this.durationMs = longestFragment * 1000;\n\n this.templates = {\n initSegment: `${apiHost}/api/v1/files/${fileId}/tracks/{trackId}`,\n mediaSegment: `${apiHost}/api/v1/files/${fileId}/tracks/{trackId}`,\n };\n }\n\n /** @deprecated Use fileId instead */\n get assetId(): string {\n return this.fileId;\n }\n\n getInitSegmentPaths(): InitSegmentPaths {\n const paths: InitSegmentPaths = {};\n const audioTrack = this.getAudioTrackIndex();\n const videoTrack = this.getVideoTrackIndex();\n\n if (audioTrack !== undefined) {\n paths.audio = {\n path: `${this.apiHost}/api/v1/files/${this.fileId}/tracks/${audioTrack.track}`,\n pos: audioTrack.initSegment.offset,\n size: audioTrack.initSegment.size,\n };\n }\n\n if (videoTrack !== undefined) {\n paths.video = {\n path: `${this.apiHost}/api/v1/files/${this.fileId}/tracks/${videoTrack.track}`,\n pos: videoTrack.initSegment.offset,\n size: videoTrack.initSegment.size,\n };\n }\n\n return paths;\n }\n\n templates!: { initSegment: string; mediaSegment: string };\n\n buildInitSegmentUrl(trackId: number) {\n return `${this.apiHost}/api/v1/files/${this.fileId}/tracks/${trackId}`;\n }\n\n buildMediaSegmentUrl(trackId: number, _segmentId: number) {\n return `${this.apiHost}/api/v1/files/${this.fileId}/tracks/${trackId}`;\n }\n\n async fetchInitSegment(\n rendition: { id?: RenditionId; trackId: number | undefined; src: string },\n signal: AbortSignal,\n ) {\n if (!rendition.trackId) {\n throw new Error(\n \"[fetchInitSegment] Track ID is required for file-based media\",\n );\n }\n\n const trackData = this.data[rendition.trackId];\n if (!trackData) {\n throw new Error(`Track ${rendition.trackId} not found`);\n }\n\n const { offset, size } = trackData.initSegment;\n const url = this.buildInitSegmentUrl(rendition.trackId);\n const fullTrack = await this.fetchMedia(url, signal);\n return fullTrack.slice(offset, offset + size);\n }\n\n async fetchMediaSegment(\n segmentId: number,\n rendition: { id?: RenditionId; trackId: number | undefined; src: string },\n signal: AbortSignal,\n ) {\n if (!rendition.trackId) {\n throw new Error(\n \"[fetchMediaSegment] Track ID is required for file-based media\",\n );\n }\n\n const trackData = this.data[rendition.trackId];\n if (!trackData) {\n throw new Error(`Track ${rendition.trackId} not found`);\n }\n\n const segment = trackData.segments[segmentId];\n if (!segment) {\n throw new Error(\n `Segment ${segmentId} not found for track ${rendition.trackId}`,\n );\n }\n\n const url = this.buildMediaSegmentUrl(rendition.trackId, segmentId);\n const fullTrack = await this.fetchMedia(url, signal);\n return fullTrack.slice(segment.offset, segment.offset + segment.size);\n }\n\n convertToSegmentRelativeTimestamps(\n globalTimestamps: number[],\n segmentId: number,\n rendition: VideoRendition,\n ): number[] {\n if (!rendition.trackId) {\n throw new Error(\n \"[convertToSegmentRelativeTimestamps] Track ID is required for asset metadata\",\n );\n }\n const trackData = this.data[rendition.trackId];\n if (!trackData) {\n throw new Error(\"Track not found\");\n }\n const segment = trackData.segments?.[segmentId];\n if (!segment) {\n throw new Error(\"Segment not found\");\n }\n const segmentStartMs = (segment.cts / trackData.timescale) * 1000;\n\n return globalTimestamps.map(\n (globalMs) => (globalMs - segmentStartMs) / 1000,\n );\n }\n}\n\n/** @deprecated Use FileMediaEngine instead */\nexport const AssetIdMediaEngine = FileMediaEngine;\n"],"mappings":"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"}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { ThumbnailExtractor } from "./shared/ThumbnailExtractor.js";
|
|
2
|
-
import { BaseMediaEngine, mediaCache } from "./BaseMediaEngine.js";
|
|
3
|
-
|
|
4
|
-
//#region src/elements/EFMedia/JitMediaEngine.ts
|
|
5
|
-
var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
|
|
6
|
-
static async fetch(host, urlGenerator, url, signal) {
|
|
7
|
-
const engine = new JitMediaEngine(host, urlGenerator);
|
|
8
|
-
const data = await engine.fetchManifest(url, signal);
|
|
9
|
-
signal?.throwIfAborted();
|
|
10
|
-
engine.data = data;
|
|
11
|
-
engine.durationMs = data.durationMs;
|
|
12
|
-
engine.src = data.sourceUrl;
|
|
13
|
-
engine.templates = data.endpoints;
|
|
14
|
-
return engine;
|
|
15
|
-
}
|
|
16
|
-
constructor(host, urlGenerator) {
|
|
17
|
-
super(host);
|
|
18
|
-
this.data = {};
|
|
19
|
-
this.durationMs = 0;
|
|
20
|
-
this.src = "";
|
|
21
|
-
this.urlGenerator = urlGenerator;
|
|
22
|
-
this.thumbnailExtractor = new ThumbnailExtractor(this);
|
|
23
|
-
}
|
|
24
|
-
#cachedVideoRendition = null;
|
|
25
|
-
#cachedAudioRendition = null;
|
|
26
|
-
getVideoRenditionInternal() {
|
|
27
|
-
return this.videoRendition;
|
|
28
|
-
}
|
|
29
|
-
getAudioRenditionInternal() {
|
|
30
|
-
return this.audioRendition;
|
|
31
|
-
}
|
|
32
|
-
get audioRendition() {
|
|
33
|
-
if (this.#cachedAudioRendition !== null) return this.#cachedAudioRendition;
|
|
34
|
-
if (!this.data.audioRenditions || this.data.audioRenditions.length === 0) {
|
|
35
|
-
this.#cachedAudioRendition = void 0;
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const rendition = this.data.audioRenditions[0];
|
|
39
|
-
if (!rendition) {
|
|
40
|
-
this.#cachedAudioRendition = void 0;
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
this.#cachedAudioRendition = {
|
|
44
|
-
id: rendition.id,
|
|
45
|
-
trackId: void 0,
|
|
46
|
-
src: this.data.sourceUrl,
|
|
47
|
-
segmentDurationMs: rendition.segmentDurationMs,
|
|
48
|
-
segmentDurationsMs: rendition.segmentDurationsMs,
|
|
49
|
-
startTimeOffsetMs: rendition.startTimeOffsetMs
|
|
50
|
-
};
|
|
51
|
-
return this.#cachedAudioRendition;
|
|
52
|
-
}
|
|
53
|
-
get videoRendition() {
|
|
54
|
-
if (this.#cachedVideoRendition !== null) return this.#cachedVideoRendition;
|
|
55
|
-
if (!this.data.videoRenditions || this.data.videoRenditions.length === 0) {
|
|
56
|
-
this.#cachedVideoRendition = void 0;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const rendition = this.data.videoRenditions[0];
|
|
60
|
-
if (!rendition) {
|
|
61
|
-
this.#cachedVideoRendition = void 0;
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
this.#cachedVideoRendition = {
|
|
65
|
-
id: rendition.id,
|
|
66
|
-
trackId: void 0,
|
|
67
|
-
src: this.data.sourceUrl,
|
|
68
|
-
segmentDurationMs: rendition.segmentDurationMs,
|
|
69
|
-
segmentDurationsMs: rendition.segmentDurationsMs,
|
|
70
|
-
startTimeOffsetMs: rendition.startTimeOffsetMs
|
|
71
|
-
};
|
|
72
|
-
return this.#cachedVideoRendition;
|
|
73
|
-
}
|
|
74
|
-
async fetchInitSegment(rendition, signal) {
|
|
75
|
-
if (!rendition.id) throw new Error("Rendition ID is required for JIT metadata");
|
|
76
|
-
const url = this.urlGenerator.generateSegmentUrl("init", rendition.id, this);
|
|
77
|
-
return this.fetchMedia(url, signal);
|
|
78
|
-
}
|
|
79
|
-
async fetchMediaSegment(segmentId, rendition, signal) {
|
|
80
|
-
if (!rendition.id) throw new Error("Rendition ID is required for JIT metadata");
|
|
81
|
-
const url = this.urlGenerator.generateSegmentUrl(segmentId, rendition.id, this);
|
|
82
|
-
return this.fetchMedia(url, signal);
|
|
83
|
-
}
|
|
84
|
-
computeSegmentId(desiredSeekTimeMs, rendition) {
|
|
85
|
-
if (desiredSeekTimeMs > this.durationMs) return;
|
|
86
|
-
if (rendition.segmentDurationsMs && rendition.segmentDurationsMs.length > 0) {
|
|
87
|
-
let cumulativeTime = 0;
|
|
88
|
-
for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {
|
|
89
|
-
const segmentDuration = rendition.segmentDurationsMs[i];
|
|
90
|
-
if (segmentDuration === void 0) throw new Error("Segment duration is required for JIT metadata");
|
|
91
|
-
const segmentStartMs = cumulativeTime;
|
|
92
|
-
const segmentEndMs = cumulativeTime + segmentDuration;
|
|
93
|
-
const includesEndTime = i === rendition.segmentDurationsMs.length - 1 && desiredSeekTimeMs === this.durationMs;
|
|
94
|
-
if (desiredSeekTimeMs >= segmentStartMs && (desiredSeekTimeMs < segmentEndMs || includesEndTime)) return i + 1;
|
|
95
|
-
cumulativeTime += segmentDuration;
|
|
96
|
-
if (cumulativeTime >= this.durationMs) break;
|
|
97
|
-
}
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (!rendition.segmentDurationMs) throw new Error("Segment duration is required for JIT metadata");
|
|
101
|
-
const segmentIndex = Math.floor(desiredSeekTimeMs / rendition.segmentDurationMs);
|
|
102
|
-
if (segmentIndex * rendition.segmentDurationMs >= this.durationMs) return;
|
|
103
|
-
return segmentIndex + 1;
|
|
104
|
-
}
|
|
105
|
-
getBufferConfig() {
|
|
106
|
-
return {
|
|
107
|
-
videoBufferDurationMs: 4e3,
|
|
108
|
-
audioBufferDurationMs: 4e3,
|
|
109
|
-
maxVideoBufferFetches: 2,
|
|
110
|
-
maxAudioBufferFetches: 2,
|
|
111
|
-
bufferThresholdMs: 3e4
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
getScrubVideoRendition() {
|
|
115
|
-
if (!this.data.videoRenditions) return void 0;
|
|
116
|
-
const scrubManifestRendition = this.data.videoRenditions.find((r) => r.id === "scrub");
|
|
117
|
-
if (!scrubManifestRendition) return this.getVideoRenditionInternal();
|
|
118
|
-
return {
|
|
119
|
-
id: scrubManifestRendition.id,
|
|
120
|
-
trackId: void 0,
|
|
121
|
-
src: this.src,
|
|
122
|
-
segmentDurationMs: scrubManifestRendition.segmentDurationMs,
|
|
123
|
-
segmentDurationsMs: scrubManifestRendition.segmentDurationsMs
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
isSegmentCached(segmentId, rendition) {
|
|
127
|
-
if (!rendition.id) return false;
|
|
128
|
-
const segmentUrl = this.urlGenerator.generateSegmentUrl(segmentId, rendition.id, this);
|
|
129
|
-
return mediaCache.has(segmentUrl);
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Extract thumbnail canvases using same rendition priority as video playback for frame alignment
|
|
133
|
-
*/
|
|
134
|
-
async extractThumbnails(timestamps, signal) {
|
|
135
|
-
let rendition;
|
|
136
|
-
try {
|
|
137
|
-
const mainRendition = this.getVideoRenditionInternal();
|
|
138
|
-
if (mainRendition) rendition = mainRendition;
|
|
139
|
-
else {
|
|
140
|
-
const scrubRendition = this.getScrubVideoRendition();
|
|
141
|
-
if (scrubRendition) rendition = scrubRendition;
|
|
142
|
-
else throw new Error("No video rendition available");
|
|
143
|
-
}
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.warn("JitMediaEngine: No video rendition available for thumbnails", error);
|
|
146
|
-
return timestamps.map(() => null);
|
|
147
|
-
}
|
|
148
|
-
return this.thumbnailExtractor.extractThumbnails(timestamps, rendition, this.durationMs, signal);
|
|
149
|
-
}
|
|
150
|
-
convertToSegmentRelativeTimestamps(globalTimestamps, _segmentId, _rendition) {
|
|
151
|
-
return globalTimestamps.map((timestamp) => timestamp / 1e3);
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
//#endregion
|
|
156
|
-
export { JitMediaEngine };
|
|
157
|
-
//# sourceMappingURL=JitMediaEngine.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"JitMediaEngine.js","names":["#cachedAudioRendition","#cachedVideoRendition","rendition: VideoRendition"],"sources":["../../../src/elements/EFMedia/JitMediaEngine.ts"],"sourcesContent":["import type {\n AudioRendition,\n MediaEngine,\n RenditionId,\n ThumbnailResult,\n VideoRendition,\n} from \"../../transcoding/types\";\nimport type { ManifestResponse } from \"../../transcoding/types/index.js\";\nimport type { UrlGenerator } from \"../../transcoding/utils/UrlGenerator\";\nimport type { EFMedia } from \"../EFMedia.js\";\nimport { BaseMediaEngine, mediaCache } from \"./BaseMediaEngine\";\nimport { ThumbnailExtractor } from \"./shared/ThumbnailExtractor.js\";\n\nexport class JitMediaEngine extends BaseMediaEngine implements MediaEngine {\n private urlGenerator: UrlGenerator;\n private data: ManifestResponse = {} as ManifestResponse;\n private thumbnailExtractor: ThumbnailExtractor;\n\n static async fetch(\n host: EFMedia,\n urlGenerator: UrlGenerator,\n url: string,\n signal?: AbortSignal,\n ) {\n const engine = new JitMediaEngine(host, urlGenerator);\n const data = await engine.fetchManifest(url, signal);\n\n // Check for abort after potentially slow network operation\n signal?.throwIfAborted();\n\n engine.data = data;\n // Set MediaEngine interface properties\n engine.durationMs = data.durationMs;\n engine.src = data.sourceUrl;\n engine.templates = data.endpoints;\n return engine;\n }\n\n // MediaEngine interface properties\n durationMs = 0;\n src = \"\";\n templates!: { initSegment: string; mediaSegment: string };\n\n constructor(host: EFMedia, urlGenerator: UrlGenerator) {\n super(host);\n this.urlGenerator = urlGenerator;\n this.thumbnailExtractor = new ThumbnailExtractor(this);\n }\n\n // Cache renditions to avoid recomputing on every access\n #cachedVideoRendition: VideoRendition | undefined | null = null;\n #cachedAudioRendition: AudioRendition | undefined | null = null;\n\n // Implement abstract methods required by BaseMediaEngine\n protected getVideoRenditionInternal(): VideoRendition | undefined {\n return this.videoRendition;\n }\n\n protected getAudioRenditionInternal(): AudioRendition | undefined {\n return this.audioRendition;\n }\n\n get audioRendition(): AudioRendition | undefined {\n if (this.#cachedAudioRendition !== null) {\n return this.#cachedAudioRendition;\n }\n if (!this.data.audioRenditions || this.data.audioRenditions.length === 0) {\n this.#cachedAudioRendition = undefined;\n return undefined;\n }\n\n const rendition = this.data.audioRenditions[0];\n if (!rendition) {\n this.#cachedAudioRendition = undefined;\n return undefined;\n }\n\n this.#cachedAudioRendition = {\n id: rendition.id as RenditionId,\n trackId: undefined,\n src: this.data.sourceUrl,\n segmentDurationMs: rendition.segmentDurationMs,\n segmentDurationsMs: rendition.segmentDurationsMs,\n startTimeOffsetMs: rendition.startTimeOffsetMs,\n };\n return this.#cachedAudioRendition;\n }\n\n get videoRendition(): VideoRendition | undefined {\n if (this.#cachedVideoRendition !== null) {\n return this.#cachedVideoRendition;\n }\n if (!this.data.videoRenditions || this.data.videoRenditions.length === 0) {\n this.#cachedVideoRendition = undefined;\n return undefined;\n }\n\n const rendition = this.data.videoRenditions[0];\n if (!rendition) {\n this.#cachedVideoRendition = undefined;\n return undefined;\n }\n\n this.#cachedVideoRendition = {\n id: rendition.id as RenditionId,\n trackId: undefined,\n src: this.data.sourceUrl,\n segmentDurationMs: rendition.segmentDurationMs,\n segmentDurationsMs: rendition.segmentDurationsMs,\n startTimeOffsetMs: rendition.startTimeOffsetMs,\n };\n return this.#cachedVideoRendition;\n }\n\n async fetchInitSegment(\n rendition: { id?: RenditionId; trackId: number | undefined; src: string },\n signal: AbortSignal,\n ) {\n if (!rendition.id) {\n throw new Error(\"Rendition ID is required for JIT metadata\");\n }\n const url = this.urlGenerator.generateSegmentUrl(\n \"init\",\n rendition.id,\n this,\n );\n\n // Use unified fetch method\n return this.fetchMedia(url, signal);\n }\n\n async fetchMediaSegment(\n segmentId: number,\n rendition: { id?: RenditionId; trackId: number | undefined; src: string },\n signal: AbortSignal,\n ) {\n if (!rendition.id) {\n throw new Error(\"Rendition ID is required for JIT metadata\");\n }\n const url = this.urlGenerator.generateSegmentUrl(\n segmentId,\n rendition.id,\n this,\n );\n return this.fetchMedia(url, signal);\n }\n\n computeSegmentId(\n desiredSeekTimeMs: number,\n rendition: VideoRendition | AudioRendition,\n ) {\n // Don't request segments beyond the actual file duration\n // Note: seeking to exactly durationMs should be allowed (it's the last moment of the file)\n if (desiredSeekTimeMs > this.durationMs) {\n return undefined;\n }\n\n // Use actual segment durations if available (more accurate)\n if (\n rendition.segmentDurationsMs &&\n rendition.segmentDurationsMs.length > 0\n ) {\n let cumulativeTime = 0;\n\n for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {\n const segmentDuration = rendition.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 // Check if the desired seek time falls within this segment\n // Special case: for the last segment, include the exact end time\n const isLastSegment = i === rendition.segmentDurationsMs.length - 1;\n const includesEndTime =\n isLastSegment && desiredSeekTimeMs === this.durationMs;\n\n if (\n desiredSeekTimeMs >= segmentStartMs &&\n (desiredSeekTimeMs < segmentEndMs || includesEndTime)\n ) {\n return i + 1; // Convert 0-based to 1-based segment ID\n }\n\n cumulativeTime += segmentDuration;\n\n // If we've reached or exceeded file duration, stop\n if (cumulativeTime >= this.durationMs) {\n break;\n }\n }\n\n // If we didn't find a segment, return undefined\n return undefined;\n }\n\n // Fall back to fixed duration calculation for backward compatibility\n if (!rendition.segmentDurationMs) {\n throw new Error(\"Segment duration is required for JIT metadata\");\n }\n\n const segmentIndex = Math.floor(\n desiredSeekTimeMs / rendition.segmentDurationMs,\n );\n\n // Calculate the actual segment start time\n const segmentStartMs = segmentIndex * rendition.segmentDurationMs;\n\n // If this segment would start at or beyond file duration, it doesn't exist\n if (segmentStartMs >= this.durationMs) {\n return undefined;\n }\n\n return segmentIndex + 1; // Convert 0-based to 1-based\n }\n\n getBufferConfig() {\n return {\n videoBufferDurationMs: 4000,\n audioBufferDurationMs: 4000,\n maxVideoBufferFetches: 2,\n maxAudioBufferFetches: 2,\n bufferThresholdMs: 30000,\n };\n }\n\n getScrubVideoRendition(): VideoRendition | undefined {\n if (!this.data.videoRenditions) return undefined;\n\n const scrubManifestRendition = this.data.videoRenditions.find(\n (r) => r.id === \"scrub\",\n );\n\n if (!scrubManifestRendition) return this.getVideoRenditionInternal(); // Fallback to main\n\n return {\n id: scrubManifestRendition.id as any,\n trackId: undefined,\n src: this.src,\n segmentDurationMs: scrubManifestRendition.segmentDurationMs,\n segmentDurationsMs: scrubManifestRendition.segmentDurationsMs,\n };\n }\n\n isSegmentCached(\n segmentId: number,\n rendition: AudioRendition | VideoRendition,\n ): boolean {\n if (!rendition.id) {\n return false;\n }\n\n const segmentUrl = this.urlGenerator.generateSegmentUrl(\n segmentId,\n rendition.id,\n this,\n );\n return mediaCache.has(segmentUrl);\n }\n\n /**\n * Extract thumbnail canvases using same rendition priority as video playback for frame alignment\n */\n async extractThumbnails(\n timestamps: number[],\n signal?: AbortSignal,\n ): Promise<(ThumbnailResult | null)[]> {\n // Use same rendition priority as video: try main rendition first for frame alignment\n let rendition: VideoRendition;\n try {\n const mainRendition = this.getVideoRenditionInternal();\n if (mainRendition) {\n rendition = mainRendition;\n } else {\n const scrubRendition = this.getScrubVideoRendition();\n if (scrubRendition) {\n rendition = scrubRendition;\n } else {\n throw new Error(\"No video rendition available\");\n }\n }\n } catch (error) {\n console.warn(\n \"JitMediaEngine: No video rendition available for thumbnails\",\n error,\n );\n return timestamps.map(() => null);\n }\n\n // Use shared thumbnail extraction logic\n return this.thumbnailExtractor.extractThumbnails(\n timestamps,\n rendition,\n this.durationMs,\n signal,\n );\n }\n\n convertToSegmentRelativeTimestamps(\n globalTimestamps: number[],\n _segmentId: number,\n _rendition: VideoRendition,\n ): number[] {\n return globalTimestamps.map((timestamp) => timestamp / 1000);\n }\n}\n"],"mappings":"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"}
|