@editframe/elements 0.20.3-beta.0 → 0.21.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DelayedLoadingState.js +0 -27
- package/dist/EF_FRAMEGEN.d.ts +5 -3
- package/dist/EF_FRAMEGEN.js +51 -29
- package/dist/_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js +7 -0
- package/dist/elements/ContextProxiesController.js +2 -22
- package/dist/elements/EFAudio.js +4 -8
- package/dist/elements/EFCaptions.js +59 -84
- package/dist/elements/EFImage.js +5 -6
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -4
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +4 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +41 -32
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +10 -2
- package/dist/elements/EFMedia/BaseMediaEngine.js +57 -67
- package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -76
- package/dist/elements/EFMedia/JitMediaEngine.js +22 -23
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +4 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +2 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +9 -7
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +2 -12
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +2 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +6 -3
- package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +5 -17
- package/dist/elements/EFMedia/shared/BufferUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/BufferUtils.js +2 -13
- package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
- package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +1 -1
- package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
- package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +1 -9
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +1 -2
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +2 -16
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +3 -8
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +0 -2
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -7
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +0 -2
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -3
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -71
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +8 -12
- package/dist/elements/EFMedia.d.ts +2 -1
- package/dist/elements/EFMedia.js +26 -23
- package/dist/elements/EFSourceMixin.js +5 -7
- package/dist/elements/EFSurface.js +6 -9
- package/dist/elements/EFTemporal.js +19 -37
- package/dist/elements/EFThumbnailStrip.js +16 -59
- package/dist/elements/EFTimegroup.js +96 -91
- package/dist/elements/EFVideo.d.ts +6 -2
- package/dist/elements/EFVideo.js +142 -107
- package/dist/elements/EFWaveform.js +18 -27
- package/dist/elements/SampleBuffer.js +2 -5
- package/dist/elements/TargetController.js +3 -3
- package/dist/elements/durationConverter.js +4 -4
- package/dist/elements/updateAnimations.js +14 -35
- package/dist/gui/ContextMixin.js +23 -52
- package/dist/gui/EFConfiguration.js +7 -7
- package/dist/gui/EFControls.js +5 -5
- package/dist/gui/EFFilmstrip.js +77 -98
- package/dist/gui/EFFitScale.js +5 -6
- package/dist/gui/EFFocusOverlay.js +4 -4
- package/dist/gui/EFPreview.js +4 -4
- package/dist/gui/EFScrubber.js +9 -9
- package/dist/gui/EFTimeDisplay.js +5 -5
- package/dist/gui/EFToggleLoop.js +4 -4
- package/dist/gui/EFTogglePlay.js +5 -5
- package/dist/gui/EFWorkbench.js +5 -5
- package/dist/gui/TWMixin2.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/otel/BridgeSpanExporter.d.ts +13 -0
- package/dist/otel/BridgeSpanExporter.js +87 -0
- package/dist/otel/setupBrowserTracing.d.ts +12 -0
- package/dist/otel/setupBrowserTracing.js +30 -0
- package/dist/otel/tracingHelpers.d.ts +34 -0
- package/dist/otel/tracingHelpers.js +113 -0
- package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
- package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
- package/dist/transcoding/types/index.d.ts +6 -4
- package/dist/transcoding/utils/UrlGenerator.js +2 -19
- package/dist/utils/LRUCache.js +6 -53
- package/package.json +10 -2
- package/src/elements/EFCaptions.browsertest.ts +2 -0
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +6 -4
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +25 -23
- package/src/elements/EFMedia/AssetMediaEngine.ts +81 -43
- package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +94 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +120 -60
- package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
- package/src/elements/EFMedia/JitMediaEngine.ts +20 -6
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +5 -2
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +0 -5
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +2 -1
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +18 -8
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +4 -16
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +4 -2
- package/src/elements/EFMedia/audioTasks/makeAudioTasksVideoOnly.browsertest.ts +95 -0
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -6
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +5 -4
- package/src/elements/EFMedia/shared/BufferUtils.ts +7 -3
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +1 -1
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +41 -42
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +0 -23
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -9
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +3 -2
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +17 -15
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.ts +0 -5
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -125
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +2 -5
- package/src/elements/EFMedia.ts +18 -2
- package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +2 -1
- package/src/elements/EFTimegroup.browsertest.ts +10 -8
- package/src/elements/EFTimegroup.ts +165 -77
- package/src/elements/EFVideo.browsertest.ts +19 -27
- package/src/elements/EFVideo.ts +203 -101
- package/src/otel/BridgeSpanExporter.ts +150 -0
- package/src/otel/setupBrowserTracing.ts +68 -0
- package/src/otel/tracingHelpers.ts +251 -0
- package/src/transcoding/types/index.ts +6 -4
- package/types.json +1 -1
package/dist/elements/EFImage.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
2
|
+
import { __decorate } from "../_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js";
|
|
2
3
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
3
4
|
import { EFTemporal } from "./EFTemporal.js";
|
|
4
5
|
import { FetchMixin } from "./FetchMixin.js";
|
|
5
6
|
import { Task } from "@lit/task";
|
|
6
7
|
import { LitElement, css, html } from "lit";
|
|
7
8
|
import { customElement, property } from "lit/decorators.js";
|
|
8
|
-
import _decorate from "@oxc-project/runtime/helpers/decorate";
|
|
9
9
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
10
|
-
|
|
10
|
+
var EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitElement), { assetType: "image_files" })) {
|
|
11
11
|
constructor(..._args) {
|
|
12
12
|
super(..._args);
|
|
13
13
|
this.imageRef = createRef();
|
|
@@ -65,8 +65,7 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
|
|
|
65
65
|
}
|
|
66
66
|
render() {
|
|
67
67
|
const assetPath = this.assetPath();
|
|
68
|
-
|
|
69
|
-
return isDirectUrl ? html`<img ${ref(this.imageRef)} src=${assetPath} />` : html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
68
|
+
return this.isDirectUrl(assetPath) ? html`<img ${ref(this.imageRef)} src=${assetPath} />` : html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
70
69
|
}
|
|
71
70
|
isDirectUrl(src) {
|
|
72
71
|
return src.startsWith("http://") || src.startsWith("https://");
|
|
@@ -80,10 +79,10 @@ let EFImage = class EFImage$1 extends EFTemporal(EFSourceMixin(FetchMixin(LitEle
|
|
|
80
79
|
return this.hasExplicitDuration;
|
|
81
80
|
}
|
|
82
81
|
};
|
|
83
|
-
|
|
82
|
+
__decorate([property({
|
|
84
83
|
type: String,
|
|
85
84
|
attribute: "asset-id",
|
|
86
85
|
reflect: true
|
|
87
86
|
})], EFImage.prototype, "assetId", null);
|
|
88
|
-
EFImage =
|
|
87
|
+
EFImage = __decorate([customElement("ef-image")], EFImage);
|
|
89
88
|
export { EFImage };
|
|
@@ -2,8 +2,7 @@ import { AssetMediaEngine } from "./AssetMediaEngine.js";
|
|
|
2
2
|
var AssetIdMediaEngine = class AssetIdMediaEngine extends AssetMediaEngine {
|
|
3
3
|
static async fetchByAssetId(host, _urlGenerator, assetId, apiHost) {
|
|
4
4
|
const url = `${apiHost}/api/v1/isobmff_files/${assetId}/index`;
|
|
5
|
-
const
|
|
6
|
-
const data = await response.json();
|
|
5
|
+
const data = await (await host.fetch(url)).json();
|
|
7
6
|
return new AssetIdMediaEngine(host, assetId, data, apiHost);
|
|
8
7
|
}
|
|
9
8
|
constructor(host, assetId, data, apiHost) {
|
|
@@ -11,8 +10,7 @@ var AssetIdMediaEngine = class AssetIdMediaEngine extends AssetMediaEngine {
|
|
|
11
10
|
this.assetId = assetId;
|
|
12
11
|
this.apiHost = apiHost;
|
|
13
12
|
this.data = data;
|
|
14
|
-
|
|
15
|
-
this.durationMs = longestFragment * 1e3;
|
|
13
|
+
this.durationMs = Object.values(this.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0) * 1e3;
|
|
16
14
|
}
|
|
17
15
|
get initSegmentPaths() {
|
|
18
16
|
const paths = {};
|
|
@@ -13,14 +13,14 @@ export declare class AssetMediaEngine extends BaseMediaEngine implements MediaEn
|
|
|
13
13
|
get audioTrackIndex(): import('../../../../assets/src/index.ts').AudioTrackFragmentIndex | undefined;
|
|
14
14
|
get videoTrackIndex(): import('../../../../assets/src/index.ts').VideoTrackFragmentIndex | undefined;
|
|
15
15
|
get videoRendition(): {
|
|
16
|
-
trackId: number
|
|
16
|
+
trackId: number;
|
|
17
17
|
src: string;
|
|
18
18
|
startTimeOffsetMs: number | undefined;
|
|
19
|
-
};
|
|
19
|
+
} | undefined;
|
|
20
20
|
get audioRendition(): {
|
|
21
|
-
trackId: number
|
|
21
|
+
trackId: number;
|
|
22
22
|
src: string;
|
|
23
|
-
};
|
|
23
|
+
} | undefined;
|
|
24
24
|
get initSegmentPaths(): InitSegmentPaths;
|
|
25
25
|
get templates(): {
|
|
26
26
|
initSegment: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSpan } from "../../otel/tracingHelpers.js";
|
|
1
2
|
import { BaseMediaEngine } from "./BaseMediaEngine.js";
|
|
2
3
|
import { convertToScaledTime, roundToMilliseconds } from "./shared/PrecisionUtils.js";
|
|
3
4
|
var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
@@ -10,10 +11,8 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
|
10
11
|
static async fetch(host, urlGenerator, src) {
|
|
11
12
|
const engine = new AssetMediaEngine(host, src);
|
|
12
13
|
const url = urlGenerator.generateTrackFragmentIndexUrl(src);
|
|
13
|
-
|
|
14
|
-
engine.
|
|
15
|
-
const longestFragment = Object.values(engine.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0);
|
|
16
|
-
engine.durationMs = longestFragment * 1e3;
|
|
14
|
+
engine.data = await engine.fetchManifest(url);
|
|
15
|
+
engine.durationMs = Object.values(engine.data).reduce((max, fragment) => Math.max(max, fragment.duration / fragment.timescale), 0) * 1e3;
|
|
17
16
|
if (src.startsWith("/")) engine.src = src.slice(1);
|
|
18
17
|
return engine;
|
|
19
18
|
}
|
|
@@ -24,15 +23,19 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
|
24
23
|
return Object.values(this.data).find((track) => track.type === "video");
|
|
25
24
|
}
|
|
26
25
|
get videoRendition() {
|
|
26
|
+
const videoTrack = this.videoTrackIndex;
|
|
27
|
+
if (!videoTrack || videoTrack.track === void 0) return;
|
|
27
28
|
return {
|
|
28
|
-
trackId:
|
|
29
|
+
trackId: videoTrack.track,
|
|
29
30
|
src: this.src,
|
|
30
|
-
startTimeOffsetMs:
|
|
31
|
+
startTimeOffsetMs: videoTrack.startTimeOffsetMs
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
get audioRendition() {
|
|
35
|
+
const audioTrack = this.audioTrackIndex;
|
|
36
|
+
if (!audioTrack || audioTrack.track === void 0) return;
|
|
34
37
|
return {
|
|
35
|
-
trackId:
|
|
38
|
+
trackId: audioTrack.track,
|
|
36
39
|
src: this.src
|
|
37
40
|
};
|
|
38
41
|
}
|
|
@@ -63,25 +66,37 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
|
63
66
|
return `/@ef-track/${this.src}?trackId=${trackId}&segmentId=${segmentId}`;
|
|
64
67
|
}
|
|
65
68
|
async fetchInitSegment(rendition, signal) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
return withSpan("assetEngine.fetchInitSegment", {
|
|
70
|
+
trackId: rendition.trackId || -1,
|
|
71
|
+
src: rendition.src
|
|
72
|
+
}, void 0, async (span) => {
|
|
73
|
+
if (!rendition.trackId) throw new Error("[fetchInitSegment] Track ID is required for asset metadata");
|
|
74
|
+
const url = this.buildInitSegmentUrl(rendition.trackId);
|
|
75
|
+
const initSegment = this.data[rendition.trackId]?.initSegment;
|
|
76
|
+
if (!initSegment) throw new Error("Init segment not found");
|
|
77
|
+
span.setAttribute("offset", initSegment.offset);
|
|
78
|
+
span.setAttribute("size", initSegment.size);
|
|
79
|
+
const headers = { Range: `bytes=${initSegment.offset}-${initSegment.offset + initSegment.size - 1}` };
|
|
80
|
+
return this.fetchMediaWithHeaders(url, headers, signal);
|
|
81
|
+
});
|
|
72
82
|
}
|
|
73
83
|
async fetchMediaSegment(segmentId, rendition, signal) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
return withSpan("assetEngine.fetchMediaSegment", {
|
|
85
|
+
segmentId,
|
|
86
|
+
trackId: rendition.trackId || -1,
|
|
87
|
+
src: rendition.src
|
|
88
|
+
}, void 0, async (span) => {
|
|
89
|
+
if (!rendition.trackId) throw new Error("[fetchMediaSegment] Track ID is required for asset metadata");
|
|
90
|
+
if (segmentId === void 0) throw new Error("Segment ID is not available");
|
|
91
|
+
const url = this.buildMediaSegmentUrl(rendition.trackId, segmentId);
|
|
92
|
+
const mediaSegment = this.data[rendition.trackId]?.segments[segmentId];
|
|
93
|
+
if (!mediaSegment) throw new Error("Media segment not found");
|
|
94
|
+
span.setAttribute("offset", mediaSegment.offset);
|
|
95
|
+
span.setAttribute("size", mediaSegment.size);
|
|
96
|
+
const headers = { Range: `bytes=${mediaSegment.offset}-${mediaSegment.offset + mediaSegment.size - 1}` };
|
|
97
|
+
return this.fetchMediaWithHeaders(url, headers, signal);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
85
100
|
calculateAudioSegmentRange(fromMs, toMs, rendition, _durationMs) {
|
|
86
101
|
if (fromMs >= toMs || !rendition.trackId) {
|
|
87
102
|
console.warn(`calculateAudioSegmentRange: invalid fromMs ${fromMs} toMs ${toMs} rendition ${JSON.stringify(rendition)}`);
|
|
@@ -145,13 +160,7 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
|
145
160
|
}
|
|
146
161
|
return nearestSegmentIndex;
|
|
147
162
|
}
|
|
148
|
-
getScrubVideoRendition() {
|
|
149
|
-
return void 0;
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Get preferred buffer configuration for this media engine
|
|
153
|
-
* AssetMediaEngine uses lower buffering since segments are already optimized
|
|
154
|
-
*/
|
|
163
|
+
getScrubVideoRendition() {}
|
|
155
164
|
getBufferConfig() {
|
|
156
165
|
return {
|
|
157
166
|
videoBufferDurationMs: 2e3,
|
|
@@ -162,7 +171,7 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
|
162
171
|
}
|
|
163
172
|
convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
|
|
164
173
|
{
|
|
165
|
-
if (!rendition.trackId) throw new Error("
|
|
174
|
+
if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
|
|
166
175
|
const trackData = this.data[rendition.trackId];
|
|
167
176
|
if (!trackData) throw new Error("Track not found");
|
|
168
177
|
const segment = trackData.segments?.[segmentId];
|
|
@@ -10,8 +10,16 @@ export declare abstract class BaseMediaEngine {
|
|
|
10
10
|
constructor(host: EFMedia);
|
|
11
11
|
abstract get videoRendition(): VideoRendition | undefined;
|
|
12
12
|
abstract get audioRendition(): AudioRendition | undefined;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Get video rendition if available. Returns undefined for audio-only assets.
|
|
15
|
+
* Callers should handle undefined gracefully.
|
|
16
|
+
*/
|
|
17
|
+
getVideoRendition(): VideoRendition | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Get audio rendition if available. Returns undefined for video-only assets.
|
|
20
|
+
* Callers should handle undefined gracefully.
|
|
21
|
+
*/
|
|
22
|
+
getAudioRendition(): AudioRendition | undefined;
|
|
15
23
|
/**
|
|
16
24
|
* Generate cache key for segment requests
|
|
17
25
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSpan } from "../../otel/tracingHelpers.js";
|
|
1
2
|
import { RequestDeduplicator } from "../../transcoding/cache/RequestDeduplicator.js";
|
|
2
3
|
import { SizeAwareLRUCache } from "../../utils/LRUCache.js";
|
|
3
4
|
const mediaCache = new SizeAwareLRUCache(100 * 1024 * 1024);
|
|
@@ -7,53 +8,72 @@ var BaseMediaEngine = class {
|
|
|
7
8
|
this.host = host;
|
|
8
9
|
}
|
|
9
10
|
getVideoRendition() {
|
|
10
|
-
if (!this.videoRendition) throw new Error("No video rendition available");
|
|
11
11
|
return this.videoRendition;
|
|
12
12
|
}
|
|
13
13
|
getAudioRendition() {
|
|
14
|
-
if (!this.audioRendition) throw new Error("No audio rendition available");
|
|
15
14
|
return this.audioRendition;
|
|
16
15
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Generate cache key for segment requests
|
|
19
|
-
*/
|
|
20
16
|
getSegmentCacheKey(segmentId, rendition) {
|
|
21
17
|
return `${rendition.src}-${rendition.id}-${segmentId}-${rendition.trackId}`;
|
|
22
18
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Unified fetch method with caching and global deduplication
|
|
25
|
-
* All requests (media, manifest, init segments) go through this method
|
|
26
|
-
*/
|
|
27
19
|
async fetchWithCache(url, options) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
20
|
+
return withSpan("mediaEngine.fetchWithCache", {
|
|
21
|
+
url: url.length > 100 ? `${url.substring(0, 100)}...` : url,
|
|
22
|
+
responseType: options.responseType,
|
|
23
|
+
hasHeaders: !!options.headers
|
|
24
|
+
}, void 0, async (span) => {
|
|
25
|
+
const t0 = performance.now();
|
|
26
|
+
const { responseType, headers, signal } = options;
|
|
27
|
+
const cacheKey = headers ? `${url}:${JSON.stringify(headers)}` : url;
|
|
28
|
+
const t1 = performance.now();
|
|
29
|
+
const cached = mediaCache.get(cacheKey);
|
|
30
|
+
const t2 = performance.now();
|
|
31
|
+
span.setAttribute("cacheLookupMs", Math.round((t2 - t1) * 1e3) / 1e3);
|
|
32
|
+
if (cached) {
|
|
33
|
+
span.setAttribute("cacheHit", true);
|
|
34
|
+
if (signal) {
|
|
35
|
+
const t3 = performance.now();
|
|
36
|
+
const result$1 = await this.handleAbortForCachedRequest(cached, signal);
|
|
37
|
+
const t4 = performance.now();
|
|
38
|
+
span.setAttribute("handleAbortMs", Math.round((t4 - t3) * 100) / 100);
|
|
39
|
+
span.setAttribute("totalCacheHitMs", Math.round((t4 - t0) * 100) / 100);
|
|
40
|
+
return result$1;
|
|
41
|
+
}
|
|
42
|
+
span.setAttribute("totalCacheHitMs", Math.round((t2 - t0) * 100) / 100);
|
|
43
|
+
return cached;
|
|
44
|
+
}
|
|
45
|
+
span.setAttribute("cacheHit", false);
|
|
46
|
+
const promise = globalRequestDeduplicator.executeRequest(cacheKey, async () => {
|
|
47
|
+
const fetchStart = performance.now();
|
|
48
|
+
try {
|
|
49
|
+
const response = await this.host.fetch(url, { headers });
|
|
50
|
+
const fetchEnd = performance.now();
|
|
51
|
+
span.setAttribute("fetchMs", fetchEnd - fetchStart);
|
|
52
|
+
if (responseType === "json") return response.json();
|
|
53
|
+
const buffer = await response.arrayBuffer();
|
|
54
|
+
span.setAttribute("sizeBytes", buffer.byteLength);
|
|
55
|
+
return buffer;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof DOMException && error.name === "AbortError") mediaCache.delete(cacheKey);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
mediaCache.set(cacheKey, promise);
|
|
62
|
+
promise.catch((error) => {
|
|
41
63
|
if (error instanceof DOMException && error.name === "AbortError") mediaCache.delete(cacheKey);
|
|
42
|
-
|
|
64
|
+
});
|
|
65
|
+
if (signal) {
|
|
66
|
+
const result$1 = await this.handleAbortForCachedRequest(promise, signal);
|
|
67
|
+
const tEnd$1 = performance.now();
|
|
68
|
+
span.setAttribute("totalFetchMs", Math.round((tEnd$1 - t0) * 100) / 100);
|
|
69
|
+
return result$1;
|
|
43
70
|
}
|
|
71
|
+
const result = await promise;
|
|
72
|
+
const tEnd = performance.now();
|
|
73
|
+
span.setAttribute("totalFetchMs", Math.round((tEnd - t0) * 100) / 100);
|
|
74
|
+
return result;
|
|
44
75
|
});
|
|
45
|
-
|
|
46
|
-
promise.catch((error) => {
|
|
47
|
-
if (error instanceof DOMException && error.name === "AbortError") mediaCache.delete(cacheKey);
|
|
48
|
-
});
|
|
49
|
-
if (signal) return this.handleAbortForCachedRequest(promise, signal);
|
|
50
|
-
return promise;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Handles abort logic for a cached request without affecting the underlying fetch
|
|
54
|
-
* This allows multiple instances to share the same cached request while each
|
|
55
|
-
* manages their own abort behavior
|
|
56
|
-
*/
|
|
76
|
+
}
|
|
57
77
|
handleAbortForCachedRequest(promise, signal) {
|
|
58
78
|
if (signal.aborted) throw new DOMException("Aborted", "AbortError");
|
|
59
79
|
return Promise.race([promise, new Promise((_, reject) => {
|
|
@@ -93,39 +113,22 @@ var BaseMediaEngine = class {
|
|
|
93
113
|
async fetchMediaCacheWithHeaders(url, headers, signal) {
|
|
94
114
|
return this.fetchMediaWithHeaders(url, headers, signal);
|
|
95
115
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Fetch media segment with built-in deduplication
|
|
98
|
-
* Now uses global deduplication for all requests
|
|
99
|
-
*/
|
|
100
116
|
async fetchMediaSegmentWithDeduplication(segmentId, rendition, _signal) {
|
|
101
117
|
const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
|
|
102
118
|
return globalRequestDeduplicator.executeRequest(cacheKey, async () => {
|
|
103
119
|
return this.fetchMediaSegment(segmentId, rendition);
|
|
104
120
|
});
|
|
105
121
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Check if a segment is currently being fetched
|
|
108
|
-
*/
|
|
109
122
|
isSegmentBeingFetched(segmentId, rendition) {
|
|
110
123
|
const cacheKey = this.getSegmentCacheKey(segmentId, rendition);
|
|
111
124
|
return globalRequestDeduplicator.isPending(cacheKey);
|
|
112
125
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Get count of active segment requests (for debugging/monitoring)
|
|
115
|
-
*/
|
|
116
126
|
getActiveSegmentRequestCount() {
|
|
117
127
|
return globalRequestDeduplicator.getPendingCount();
|
|
118
128
|
}
|
|
119
|
-
/**
|
|
120
|
-
* Cancel all active segment requests (for cleanup)
|
|
121
|
-
*/
|
|
122
129
|
cancelAllSegmentRequests() {
|
|
123
130
|
globalRequestDeduplicator.clear();
|
|
124
131
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Calculate audio segments needed for a time range
|
|
127
|
-
* Each media engine implements this based on their segment structure
|
|
128
|
-
*/
|
|
129
132
|
calculateAudioSegmentRange(fromMs, toMs, rendition, durationMs) {
|
|
130
133
|
if (fromMs >= toMs) return [];
|
|
131
134
|
const segments = [];
|
|
@@ -163,37 +166,24 @@ var BaseMediaEngine = class {
|
|
|
163
166
|
}
|
|
164
167
|
return segments;
|
|
165
168
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Check if a segment is cached for a given rendition
|
|
168
|
-
* This needs to check the URL-based cache since that's where segments are actually stored
|
|
169
|
-
*/
|
|
170
169
|
isSegmentCached(segmentId, rendition) {
|
|
171
170
|
try {
|
|
172
171
|
const maybeJitEngine = this;
|
|
173
172
|
if (maybeJitEngine.urlGenerator && typeof maybeJitEngine.urlGenerator.generateSegmentUrl === "function") {
|
|
174
173
|
if (!rendition.id) return false;
|
|
175
174
|
const segmentUrl = maybeJitEngine.urlGenerator.generateSegmentUrl(segmentId, rendition.id, maybeJitEngine);
|
|
176
|
-
|
|
177
|
-
return urlIsCached;
|
|
175
|
+
return mediaCache.has(segmentUrl);
|
|
178
176
|
}
|
|
179
177
|
const cacheKey = `${rendition.src}-${rendition.id || "default"}-${segmentId}-${rendition.trackId}`;
|
|
180
|
-
|
|
181
|
-
return isCached;
|
|
178
|
+
return mediaCache.has(cacheKey);
|
|
182
179
|
} catch (error) {
|
|
183
180
|
console.warn(`🎬 BaseMediaEngine: Error checking if segment ${segmentId} is cached:`, error);
|
|
184
181
|
return false;
|
|
185
182
|
}
|
|
186
183
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Get cached segment IDs from a list for a given rendition
|
|
189
|
-
*/
|
|
190
184
|
getCachedSegments(segmentIds, rendition) {
|
|
191
185
|
return new Set(segmentIds.filter((id) => this.isSegmentCached(id, rendition)));
|
|
192
186
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Extract thumbnail canvases at multiple timestamps efficiently
|
|
195
|
-
* Default implementation provides helpful error information
|
|
196
|
-
*/
|
|
197
187
|
async extractThumbnails(timestamps) {
|
|
198
188
|
const engineName = this.constructor.name;
|
|
199
189
|
console.warn(`${engineName}: extractThumbnails not properly implemented. This MediaEngine type does not support thumbnail generation. Supported engines: JitMediaEngine. Requested ${timestamps.length} thumbnail${timestamps.length === 1 ? "" : "s"}.`);
|