@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
|
@@ -1,50 +1,26 @@
|
|
|
1
1
|
import { LRUCache } from "../../../utils/LRUCache.js";
|
|
2
|
-
/**
|
|
3
|
-
* Global cache for MediaBunny Input instances
|
|
4
|
-
* Shared across all MediaEngine instances to prevent duplicate decoding
|
|
5
|
-
* of the same segment data
|
|
6
|
-
*/
|
|
7
2
|
var GlobalInputCache = class {
|
|
8
3
|
constructor() {
|
|
9
4
|
this.cache = new LRUCache(50);
|
|
10
5
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Generate standardized cache key for Input objects
|
|
13
|
-
* Format: "input:{src}:{segmentId}:{renditionId}"
|
|
14
|
-
*/
|
|
15
6
|
generateKey(src, segmentId, renditionId) {
|
|
16
7
|
return `input:${src}:${segmentId}:${renditionId || "default"}`;
|
|
17
8
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Get cached Input object
|
|
20
|
-
*/
|
|
21
9
|
get(src, segmentId, renditionId) {
|
|
22
10
|
const key = this.generateKey(src, segmentId, renditionId);
|
|
23
11
|
return this.cache.get(key);
|
|
24
12
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Cache Input object
|
|
27
|
-
*/
|
|
28
13
|
set(src, segmentId, input, renditionId) {
|
|
29
14
|
const key = this.generateKey(src, segmentId, renditionId);
|
|
30
15
|
this.cache.set(key, input);
|
|
31
16
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Check if Input is cached
|
|
34
|
-
*/
|
|
35
17
|
has(src, segmentId, renditionId) {
|
|
36
18
|
const key = this.generateKey(src, segmentId, renditionId);
|
|
37
19
|
return this.cache.has(key);
|
|
38
20
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Clear all cached Input objects
|
|
41
|
-
*/
|
|
42
21
|
clear() {
|
|
43
22
|
this.cache.clear();
|
|
44
23
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Get cache statistics for debugging
|
|
47
|
-
*/
|
|
48
24
|
getStats() {
|
|
49
25
|
return {
|
|
50
26
|
size: this.cache.size,
|
|
@@ -20,4 +20,4 @@ export type SegmentFetchTask = Task<readonly [MediaEngine | undefined, number |
|
|
|
20
20
|
/**
|
|
21
21
|
* Generic task type for input creation
|
|
22
22
|
*/
|
|
23
|
-
export type InputTask = Task<readonly [ArrayBuffer, ArrayBuffer], BufferedSeekingInput>;
|
|
23
|
+
export type InputTask = Task<readonly [ArrayBuffer, ArrayBuffer], BufferedSeekingInput | undefined>;
|
|
@@ -1,27 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized precision utilities for consistent timing calculations across the media pipeline.
|
|
3
|
-
*
|
|
4
|
-
* The key insight is that floating-point precision errors can cause inconsistencies between:
|
|
5
|
-
* 1. Segment selection logic (in AssetMediaEngine.computeSegmentId)
|
|
6
|
-
* 2. Sample finding logic (in SampleBuffer.find)
|
|
7
|
-
* 3. Timeline mapping (in BufferedSeekingInput.seek)
|
|
8
|
-
*
|
|
9
|
-
* All timing calculations must use the same rounding strategy to ensure consistency.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Round time to millisecond precision to handle floating-point precision issues.
|
|
13
|
-
* Uses Math.round for consistent behavior across the entire pipeline.
|
|
14
|
-
*
|
|
15
|
-
* This function should be used for ALL time-related calculations that need to be
|
|
16
|
-
* compared between different parts of the system.
|
|
17
|
-
*/
|
|
18
1
|
const roundToMilliseconds = (timeMs) => {
|
|
19
2
|
return Math.round(timeMs * 1e3) / 1e3;
|
|
20
3
|
};
|
|
21
|
-
/**
|
|
22
|
-
* Convert media time (in seconds) to scaled time units using consistent rounding.
|
|
23
|
-
* This is used in segment selection to convert from milliseconds to timescale units.
|
|
24
|
-
*/
|
|
25
4
|
const convertToScaledTime = (timeMs, timescale) => {
|
|
26
5
|
const scaledTime = timeMs / 1e3 * timescale;
|
|
27
6
|
return Math.round(scaledTime);
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import { AudioRendition,
|
|
2
|
-
/**
|
|
3
|
-
* Get audio rendition from media engine, throwing if not available
|
|
4
|
-
*/
|
|
5
|
-
export declare const getAudioRendition: (mediaEngine: MediaEngine) => AudioRendition;
|
|
6
|
-
/**
|
|
7
|
-
* Get video rendition from media engine, throwing if not available
|
|
8
|
-
*/
|
|
9
|
-
export declare const getVideoRendition: (mediaEngine: MediaEngine) => VideoRendition;
|
|
1
|
+
import { AudioRendition, VideoRendition } from '../../../transcoding/types';
|
|
10
2
|
/**
|
|
11
3
|
* Calculate which segment contains a given timestamp
|
|
12
4
|
* Returns 1-based segment ID, or undefined if segmentDurationMs is not available
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { globalInputCache } from "./GlobalInputCache.js";
|
|
2
2
|
import { ALL_FORMATS, BlobSource, CanvasSink, Input } from "mediabunny";
|
|
3
|
-
/**
|
|
4
|
-
* Shared thumbnail extraction logic for all MediaEngine implementations
|
|
5
|
-
* Eliminates code duplication and provides consistent behavior
|
|
6
|
-
*/
|
|
7
3
|
var ThumbnailExtractor = class {
|
|
8
4
|
constructor(mediaEngine) {
|
|
9
5
|
this.mediaEngine = mediaEngine;
|
|
10
6
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Extract thumbnails at multiple timestamps efficiently using segment batching
|
|
13
|
-
*/
|
|
14
7
|
async extractThumbnails(timestamps, rendition, durationMs) {
|
|
15
8
|
if (timestamps.length === 0) return [];
|
|
16
9
|
const validTimestamps = timestamps.filter((timeMs) => timeMs >= 0 && timeMs <= durationMs);
|
|
@@ -32,9 +25,6 @@ var ThumbnailExtractor = class {
|
|
|
32
25
|
return results.get(t) || null;
|
|
33
26
|
});
|
|
34
27
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Group timestamps by segment ID for efficient batch processing
|
|
37
|
-
*/
|
|
38
28
|
groupTimestampsBySegment(timestamps, rendition) {
|
|
39
29
|
const segmentGroups = /* @__PURE__ */ new Map();
|
|
40
30
|
for (const timeMs of timestamps) try {
|
|
@@ -50,9 +40,6 @@ var ThumbnailExtractor = class {
|
|
|
50
40
|
}
|
|
51
41
|
return segmentGroups;
|
|
52
42
|
}
|
|
53
|
-
/**
|
|
54
|
-
* Extract thumbnails for a specific segment using CanvasSink
|
|
55
|
-
*/
|
|
56
43
|
async extractSegmentThumbnails(segmentId, timestamps, rendition) {
|
|
57
44
|
const results = /* @__PURE__ */ new Map();
|
|
58
45
|
try {
|
|
@@ -95,10 +82,6 @@ var ThumbnailExtractor = class {
|
|
|
95
82
|
}
|
|
96
83
|
return results;
|
|
97
84
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Convert global timestamps to segment-relative timestamps for mediabunny
|
|
100
|
-
* This is where the main difference between JIT and Asset engines lies
|
|
101
|
-
*/
|
|
102
85
|
convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
|
|
103
86
|
return this.mediaEngine.convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition);
|
|
104
87
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Task } from '@lit/task';
|
|
2
|
-
import { MediaEngine
|
|
2
|
+
import { MediaEngine } from '../../../transcoding/types';
|
|
3
3
|
import { EFMedia } from '../../EFMedia';
|
|
4
4
|
export declare const getLatestMediaEngine: (host: EFMedia, signal: AbortSignal) => Promise<MediaEngine>;
|
|
5
|
-
export declare const getVideoRendition: (mediaEngine: MediaEngine) => VideoRendition;
|
|
6
5
|
/**
|
|
7
6
|
* Core logic for creating a MediaEngine with explicit dependencies.
|
|
8
7
|
* Pure function that requires all dependencies to be provided.
|
|
@@ -9,15 +9,6 @@ const getLatestMediaEngine = async (host, signal) => {
|
|
|
9
9
|
if (!mediaEngine) throw new Error("Media engine is not available");
|
|
10
10
|
return mediaEngine;
|
|
11
11
|
};
|
|
12
|
-
const getVideoRendition = (mediaEngine) => {
|
|
13
|
-
const videoRendition = mediaEngine.videoRendition;
|
|
14
|
-
if (!videoRendition) throw new Error("No video track available in source");
|
|
15
|
-
return videoRendition;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Core logic for creating a MediaEngine with explicit dependencies.
|
|
19
|
-
* Pure function that requires all dependencies to be provided.
|
|
20
|
-
*/
|
|
21
12
|
const createMediaEngine = (host) => {
|
|
22
13
|
const { src, assetId, urlGenerator, apiHost } = host;
|
|
23
14
|
if (assetId !== null && assetId !== void 0 && assetId.trim() !== "") {
|
|
@@ -30,15 +21,10 @@ const createMediaEngine = (host) => {
|
|
|
30
21
|
}
|
|
31
22
|
const lowerSrc = src.toLowerCase();
|
|
32
23
|
if (!lowerSrc.startsWith("http://") && !lowerSrc.startsWith("https://")) return AssetMediaEngine.fetch(host, urlGenerator, src);
|
|
33
|
-
|
|
34
|
-
if (configuration?.mediaEngine === "local") return AssetMediaEngine.fetch(host, urlGenerator, src);
|
|
24
|
+
if (host.closest("ef-configuration")?.mediaEngine === "local") return AssetMediaEngine.fetch(host, urlGenerator, src);
|
|
35
25
|
const url = urlGenerator.generateManifestUrl(src);
|
|
36
26
|
return JitMediaEngine.fetch(host, urlGenerator, url);
|
|
37
27
|
};
|
|
38
|
-
/**
|
|
39
|
-
* Handle completion of media engine task - triggers necessary updates.
|
|
40
|
-
* Extracted for testability.
|
|
41
|
-
*/
|
|
42
28
|
const handleMediaEngineComplete = (host) => {
|
|
43
29
|
host.requestUpdate("intrinsicDurationMs");
|
|
44
30
|
host.requestUpdate("ownCurrentTimeMs");
|
|
@@ -57,4 +43,4 @@ const makeMediaEngineTask = (host) => {
|
|
|
57
43
|
}
|
|
58
44
|
});
|
|
59
45
|
};
|
|
60
|
-
export { getLatestMediaEngine,
|
|
46
|
+
export { getLatestMediaEngine, makeMediaEngineTask };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BufferedSeekingInput } from '../BufferedSeekingInput';
|
|
2
|
+
/**
|
|
3
|
+
* Cache for main video BufferedSeekingInput instances
|
|
4
|
+
* Main video segments are typically 2s long, so we can reuse the same input
|
|
5
|
+
* for multiple frames within that segment (e.g., 60 frames at 30fps)
|
|
6
|
+
*/
|
|
7
|
+
export declare class MainVideoInputCache {
|
|
8
|
+
private cache;
|
|
9
|
+
private maxCacheSize;
|
|
10
|
+
/**
|
|
11
|
+
* Create a cache key that uniquely identifies a segment
|
|
12
|
+
*/
|
|
13
|
+
private getCacheKey;
|
|
14
|
+
/**
|
|
15
|
+
* Get or create BufferedSeekingInput for a main video segment
|
|
16
|
+
*/
|
|
17
|
+
getOrCreateInput(src: string, segmentId: number, renditionId: string | undefined, createInputFn: () => Promise<BufferedSeekingInput | undefined>): Promise<BufferedSeekingInput | undefined>;
|
|
18
|
+
/**
|
|
19
|
+
* Clear the entire cache (called when video changes)
|
|
20
|
+
*/
|
|
21
|
+
clear(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Get cache statistics
|
|
24
|
+
*/
|
|
25
|
+
getStats(): {
|
|
26
|
+
size: number;
|
|
27
|
+
cacheKeys: string[];
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
var MainVideoInputCache = class {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
4
|
+
this.maxCacheSize = 10;
|
|
5
|
+
}
|
|
6
|
+
getCacheKey(src, segmentId, renditionId) {
|
|
7
|
+
return `${src}:${renditionId || "default"}:${segmentId}`;
|
|
8
|
+
}
|
|
9
|
+
async getOrCreateInput(src, segmentId, renditionId, createInputFn) {
|
|
10
|
+
const cacheKey = this.getCacheKey(src, segmentId, renditionId);
|
|
11
|
+
const cached = this.cache.get(cacheKey);
|
|
12
|
+
if (cached) return cached;
|
|
13
|
+
const input = await createInputFn();
|
|
14
|
+
if (!input) return;
|
|
15
|
+
this.cache.set(cacheKey, input);
|
|
16
|
+
if (this.cache.size > this.maxCacheSize) {
|
|
17
|
+
const oldestKey = this.cache.keys().next().value;
|
|
18
|
+
if (oldestKey !== void 0) this.cache.delete(oldestKey);
|
|
19
|
+
}
|
|
20
|
+
return input;
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.cache.clear();
|
|
24
|
+
}
|
|
25
|
+
getStats() {
|
|
26
|
+
return {
|
|
27
|
+
size: this.cache.size,
|
|
28
|
+
cacheKeys: Array.from(this.cache.keys())
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export { MainVideoInputCache };
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache for scrub BufferedSeekingInput instances
|
|
3
|
-
* Since scrub segments are 30s long, we can reuse the same input for many seeks
|
|
4
|
-
* within that time range, making scrub seeking very efficient
|
|
5
|
-
*/
|
|
6
1
|
var ScrubInputCache = class {
|
|
7
2
|
constructor() {
|
|
8
3
|
this.cache = /* @__PURE__ */ new Map();
|
|
9
4
|
this.maxCacheSize = 5;
|
|
10
5
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Get or create BufferedSeekingInput for a scrub segment
|
|
13
|
-
*/
|
|
14
6
|
async getOrCreateInput(segmentId, createInputFn) {
|
|
15
7
|
const cached = this.cache.get(segmentId);
|
|
16
8
|
if (cached) return cached;
|
|
17
9
|
const input = await createInputFn();
|
|
18
|
-
if (!input) return
|
|
10
|
+
if (!input) return;
|
|
19
11
|
this.cache.set(segmentId, input);
|
|
20
12
|
if (this.cache.size > this.maxCacheSize) {
|
|
21
13
|
const oldestKey = this.cache.keys().next().value;
|
|
@@ -23,15 +15,9 @@ var ScrubInputCache = class {
|
|
|
23
15
|
}
|
|
24
16
|
return input;
|
|
25
17
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Clear the entire cache (called when video changes)
|
|
28
|
-
*/
|
|
29
18
|
clear() {
|
|
30
19
|
this.cache.clear();
|
|
31
20
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Get cache statistics
|
|
34
|
-
*/
|
|
35
21
|
getStats() {
|
|
36
22
|
return {
|
|
37
23
|
size: this.cache.size,
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
+
import { EF_INTERACTIVE } from "../../../EF_INTERACTIVE.js";
|
|
1
2
|
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
3
|
import { manageMediaBuffer } from "../shared/BufferUtils.js";
|
|
3
4
|
import { Task } from "@lit/task";
|
|
4
|
-
/**
|
|
5
|
-
* Scrub video buffer task - aggressively preloads the ENTIRE scrub track
|
|
6
|
-
* Unlike main video buffering, this loads the full duration with higher concurrency
|
|
7
|
-
* for instant visual feedback during seeking
|
|
8
|
-
*/
|
|
9
5
|
const makeScrubVideoBufferTask = (host) => {
|
|
10
6
|
let currentState = {
|
|
11
7
|
currentSeekTimeMs: 0,
|
|
@@ -14,7 +10,7 @@ const makeScrubVideoBufferTask = (host) => {
|
|
|
14
10
|
requestQueue: []
|
|
15
11
|
};
|
|
16
12
|
return new Task(host, {
|
|
17
|
-
autoRun:
|
|
13
|
+
autoRun: EF_INTERACTIVE,
|
|
18
14
|
args: () => [host.mediaEngineTask.value],
|
|
19
15
|
onError: (error) => {
|
|
20
16
|
console.error("scrubVideoBufferTask error", error);
|
|
@@ -38,7 +34,7 @@ const makeScrubVideoBufferTask = (host) => {
|
|
|
38
34
|
} catch (error) {
|
|
39
35
|
console.warn("ScrubBuffer: Failed to cache scrub init segment:", error);
|
|
40
36
|
}
|
|
41
|
-
|
|
37
|
+
return await manageMediaBuffer(0, {
|
|
42
38
|
bufferDurationMs: mediaEngine.durationMs,
|
|
43
39
|
maxParallelFetches: 10,
|
|
44
40
|
enableBuffering: true,
|
|
@@ -58,7 +54,6 @@ const makeScrubVideoBufferTask = (host) => {
|
|
|
58
54
|
console.warn(`ScrubBuffer: ${message}`, error);
|
|
59
55
|
}
|
|
60
56
|
});
|
|
61
|
-
return newState;
|
|
62
57
|
} catch (error) {
|
|
63
58
|
if (signal.aborted) return currentState;
|
|
64
59
|
console.warn("ScrubBuffer failed:", error);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
3
|
const makeScrubVideoInitSegmentFetchTask = (host) => {
|
|
@@ -9,7 +8,6 @@ const makeScrubVideoInitSegmentFetchTask = (host) => {
|
|
|
9
8
|
},
|
|
10
9
|
onComplete: (_value) => {},
|
|
11
10
|
task: async ([_mediaEngine], { signal }) => {
|
|
12
|
-
if (EF_RENDERING()) return /* @__PURE__ */ new ArrayBuffer(0);
|
|
13
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
14
12
|
const scrubRendition = mediaEngine.getScrubVideoRendition();
|
|
15
13
|
if (!scrubRendition) throw new Error("No scrub rendition available");
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { BufferedSeekingInput } from "../BufferedSeekingInput.js";
|
|
3
2
|
import { EFMedia } from "../../EFMedia.js";
|
|
4
3
|
import { Task } from "@lit/task";
|
|
@@ -9,20 +8,22 @@ const makeScrubVideoInputTask = (host) => {
|
|
|
9
8
|
console.error("scrubVideoInputTask error", error);
|
|
10
9
|
},
|
|
11
10
|
onComplete: (_value) => {},
|
|
12
|
-
task: async () => {
|
|
13
|
-
if (EF_RENDERING()) console.info("Scrub not available in rendering mode");
|
|
11
|
+
task: async (_, { signal }) => {
|
|
14
12
|
const initSegment = await host.scrubVideoInitSegmentFetchTask.taskComplete;
|
|
13
|
+
if (signal.aborted) return void 0;
|
|
15
14
|
const segment = await host.scrubVideoSegmentFetchTask.taskComplete;
|
|
15
|
+
if (signal.aborted) return void 0;
|
|
16
16
|
if (!initSegment || !segment) throw new Error("Scrub init segment or segment is not available");
|
|
17
17
|
const mediaEngine = await host.mediaEngineTask.taskComplete;
|
|
18
|
-
|
|
19
|
-
const startTimeOffsetMs =
|
|
20
|
-
const
|
|
18
|
+
if (signal.aborted) return void 0;
|
|
19
|
+
const startTimeOffsetMs = mediaEngine.getScrubVideoRendition()?.startTimeOffsetMs;
|
|
20
|
+
const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
|
|
21
|
+
if (signal.aborted) return void 0;
|
|
22
|
+
return new BufferedSeekingInput(arrayBuffer, {
|
|
21
23
|
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
22
24
|
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
23
25
|
startTimeOffsetMs
|
|
24
26
|
});
|
|
25
|
-
return input;
|
|
26
27
|
}
|
|
27
28
|
});
|
|
28
29
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ScrubInputCache } from "./ScrubInputCache.js";
|
|
2
2
|
import { Task } from "@lit/task";
|
|
3
|
-
|
|
3
|
+
var scrubInputCache = new ScrubInputCache();
|
|
4
4
|
const makeScrubVideoSeekTask = (host) => {
|
|
5
5
|
return new Task(host, {
|
|
6
6
|
args: () => [host.desiredSeekTimeMs],
|
|
@@ -11,22 +11,21 @@ const makeScrubVideoSeekTask = (host) => {
|
|
|
11
11
|
task: async ([desiredSeekTimeMs], { signal }) => {
|
|
12
12
|
signal.throwIfAborted();
|
|
13
13
|
const mediaEngine = host.mediaEngineTask.value;
|
|
14
|
-
if (!mediaEngine) return
|
|
14
|
+
if (!mediaEngine) return;
|
|
15
15
|
const scrubRendition = mediaEngine.getScrubVideoRendition();
|
|
16
|
-
if (!scrubRendition) return
|
|
16
|
+
if (!scrubRendition) return;
|
|
17
17
|
const scrubRenditionWithSrc = {
|
|
18
18
|
...scrubRendition,
|
|
19
19
|
src: mediaEngine.src
|
|
20
20
|
};
|
|
21
21
|
const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, scrubRenditionWithSrc);
|
|
22
|
-
if (segmentId === void 0) return
|
|
23
|
-
|
|
24
|
-
if (!isCached) return void 0;
|
|
22
|
+
if (segmentId === void 0) return;
|
|
23
|
+
if (!mediaEngine.isSegmentCached(segmentId, scrubRenditionWithSrc)) return;
|
|
25
24
|
signal.throwIfAborted();
|
|
26
25
|
try {
|
|
27
26
|
const scrubInput = await scrubInputCache.getOrCreateInput(segmentId, async () => {
|
|
28
27
|
const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal), mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc)]);
|
|
29
|
-
if (!initSegment || !mediaSegment || signal.aborted) return
|
|
28
|
+
if (!initSegment || !mediaSegment || signal.aborted) return;
|
|
30
29
|
const { BufferedSeekingInput } = await import("../BufferedSeekingInput.js");
|
|
31
30
|
const { EFMedia } = await import("../../EFMedia.js");
|
|
32
31
|
return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
|
|
@@ -35,16 +34,16 @@ const makeScrubVideoSeekTask = (host) => {
|
|
|
35
34
|
startTimeOffsetMs: scrubRendition.startTimeOffsetMs
|
|
36
35
|
});
|
|
37
36
|
});
|
|
38
|
-
if (!scrubInput) return
|
|
37
|
+
if (!scrubInput) return;
|
|
38
|
+
if (signal.aborted) return;
|
|
39
39
|
const videoTrack = await scrubInput.getFirstVideoTrack();
|
|
40
|
-
if (!videoTrack) return
|
|
41
|
-
signal.
|
|
42
|
-
|
|
43
|
-
return sample;
|
|
40
|
+
if (!videoTrack) return;
|
|
41
|
+
if (signal.aborted) return;
|
|
42
|
+
return await scrubInput.seek(videoTrack.id, desiredSeekTimeMs);
|
|
44
43
|
} catch (error) {
|
|
45
44
|
if (signal.aborted) return void 0;
|
|
46
45
|
console.warn("Failed to get scrub video sample:", error);
|
|
47
|
-
return
|
|
46
|
+
return;
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
49
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
3
|
const makeScrubVideoSegmentFetchTask = (host) => {
|
|
@@ -9,7 +8,6 @@ const makeScrubVideoSegmentFetchTask = (host) => {
|
|
|
9
8
|
},
|
|
10
9
|
onComplete: (_value) => {},
|
|
11
10
|
task: async (_, { signal }) => {
|
|
12
|
-
if (EF_RENDERING()) return /* @__PURE__ */ new ArrayBuffer(0);
|
|
13
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
14
12
|
const segmentId = await host.scrubVideoSegmentIdTask.taskComplete;
|
|
15
13
|
if (segmentId === void 0) throw new Error("Scrub segment ID is not available for video");
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EF_RENDERING } from "../../../EF_RENDERING.js";
|
|
2
1
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
3
|
const makeScrubVideoSegmentIdTask = (host) => {
|
|
@@ -9,11 +8,10 @@ const makeScrubVideoSegmentIdTask = (host) => {
|
|
|
9
8
|
},
|
|
10
9
|
onComplete: (_value) => {},
|
|
11
10
|
task: async ([, targetSeekTimeMs], { signal }) => {
|
|
12
|
-
if (EF_RENDERING()) return void 0;
|
|
13
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
14
12
|
signal.throwIfAborted();
|
|
15
13
|
const scrubRendition = mediaEngine.getScrubVideoRendition();
|
|
16
|
-
if (!scrubRendition) return
|
|
14
|
+
if (!scrubRendition) return;
|
|
17
15
|
return mediaEngine.computeSegmentId(targetSeekTimeMs, {
|
|
18
16
|
...scrubRendition,
|
|
19
17
|
src: mediaEngine.src
|