@editframe/elements 0.18.22-beta.0 → 0.18.26-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/elements/EFMedia/AssetMediaEngine.d.ts +2 -1
- package/dist/elements/EFMedia/AssetMediaEngine.js +3 -0
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +9 -0
- package/dist/elements/EFMedia/BaseMediaEngine.js +31 -0
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +1 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +12 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +11 -5
- package/dist/elements/EFMedia/shared/BufferUtils.d.ts +19 -18
- package/dist/elements/EFMedia/shared/BufferUtils.js +24 -44
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +8 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +5 -5
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.d.ts +25 -0
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +42 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.d.ts +8 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +70 -0
- package/dist/elements/EFMedia/videoTasks/{makeVideoInitSegmentFetchTask.d.ts → makeScrubVideoInitSegmentFetchTask.d.ts} +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js +21 -0
- package/dist/elements/EFMedia/videoTasks/{makeVideoInputTask.d.ts → makeScrubVideoInputTask.d.ts} +1 -1
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +27 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.d.ts +6 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +52 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js +23 -0
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/{makeVideoSegmentIdTask.js → makeScrubVideoSegmentIdTask.js} +9 -4
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.d.ts +6 -0
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +112 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +11 -5
- package/dist/elements/EFMedia.d.ts +0 -10
- package/dist/elements/EFMedia.js +1 -17
- package/dist/elements/EFVideo.d.ts +11 -9
- package/dist/elements/EFVideo.js +31 -23
- package/dist/gui/EFConfiguration.d.ts +1 -0
- package/dist/gui/EFConfiguration.js +5 -0
- package/dist/gui/EFFilmstrip.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/transcoding/types/index.d.ts +11 -0
- package/package.json +2 -2
- package/src/elements/EFCaptions.ts +1 -1
- package/src/elements/EFImage.ts +1 -1
- package/src/elements/EFMedia/AssetMediaEngine.ts +6 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +60 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +18 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +185 -59
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +19 -6
- package/src/elements/EFMedia/shared/BufferUtils.ts +71 -85
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +151 -112
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +12 -5
- package/src/elements/EFMedia/videoTasks/ScrubInputCache.ts +61 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.ts +113 -0
- package/src/elements/EFMedia/videoTasks/{makeVideoInitSegmentFetchTask.ts → makeScrubVideoInitSegmentFetchTask.ts} +15 -3
- package/src/elements/EFMedia/videoTasks/{makeVideoInputTask.ts → makeScrubVideoInputTask.ts} +11 -10
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +118 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.ts +44 -0
- package/src/elements/EFMedia/videoTasks/{makeVideoSegmentIdTask.ts → makeScrubVideoSegmentIdTask.ts} +14 -6
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +258 -0
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +19 -5
- package/src/elements/EFMedia.browsertest.ts +74 -11
- package/src/elements/EFMedia.ts +1 -23
- package/src/elements/EFVideo.browsertest.ts +204 -80
- package/src/elements/EFVideo.ts +38 -26
- package/src/elements/TargetController.browsertest.ts +1 -1
- package/src/gui/EFConfiguration.ts +4 -1
- package/src/gui/EFFilmstrip.ts +4 -4
- package/src/gui/EFFocusOverlay.ts +1 -1
- package/src/gui/EFPreview.ts +3 -4
- package/src/gui/EFScrubber.ts +1 -1
- package/src/gui/EFTimeDisplay.ts +1 -1
- package/src/gui/EFToggleLoop.ts +1 -1
- package/src/gui/EFTogglePlay.ts +1 -1
- package/src/gui/EFWorkbench.ts +1 -1
- package/src/transcoding/types/index.ts +16 -0
- package/test/__cache__/GET__api_v1_transcode_scrub_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__6ff5127ebeda578a679474347fbd6137/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_scrub_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__6ff5127ebeda578a679474347fbd6137/metadata.json +16 -0
- package/test/__cache__/GET__api_v1_transcode_scrub_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__f6d4793fc9ff854ee9a738917fb64a53/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_scrub_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__f6d4793fc9ff854ee9a738917fb64a53/metadata.json +16 -0
- package/test/cache-integration-verification.browsertest.ts +84 -0
- package/types.json +1 -1
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +0 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +0 -16
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +0 -27
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +0 -7
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +0 -34
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +0 -4
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +0 -28
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +0 -9
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +0 -4
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +0 -233
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +0 -555
- package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +0 -59
- package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +0 -55
- package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +0 -65
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +0 -57
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +0 -43
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +0 -56
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
2
|
+
import { Task } from "@lit/task";
|
|
3
|
+
const makeScrubVideoSegmentFetchTask = (host) => {
|
|
4
|
+
return new Task(host, {
|
|
5
|
+
args: () => [host.mediaEngineTask.value, host.scrubVideoSegmentIdTask.value],
|
|
6
|
+
onError: (error) => {
|
|
7
|
+
console.error("scrubVideoSegmentFetchTask error", error);
|
|
8
|
+
},
|
|
9
|
+
onComplete: (_value) => {},
|
|
10
|
+
task: async (_, { signal }) => {
|
|
11
|
+
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
|
+
const segmentId = await host.scrubVideoSegmentIdTask.taskComplete;
|
|
13
|
+
if (segmentId === void 0) throw new Error("Scrub segment ID is not available for video");
|
|
14
|
+
const scrubRendition = mediaEngine.getScrubVideoRendition();
|
|
15
|
+
if (!scrubRendition) throw new Error("No scrub rendition available");
|
|
16
|
+
return mediaEngine.fetchMediaSegment(segmentId, {
|
|
17
|
+
...scrubRendition,
|
|
18
|
+
src: mediaEngine.src
|
|
19
|
+
}, signal);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
export { makeScrubVideoSegmentFetchTask };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Task } from '@lit/task';
|
|
2
|
+
import { MediaEngine } from '../../../transcoding/types';
|
|
3
|
+
import { EFVideo } from '../../EFVideo';
|
|
4
|
+
export declare const makeScrubVideoSegmentIdTask: (host: EFVideo) => Task<readonly [MediaEngine | undefined, number], number | undefined>;
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
2
2
|
import { Task } from "@lit/task";
|
|
3
|
-
const
|
|
3
|
+
const makeScrubVideoSegmentIdTask = (host) => {
|
|
4
4
|
return new Task(host, {
|
|
5
5
|
args: () => [host.mediaEngineTask.value, host.desiredSeekTimeMs],
|
|
6
6
|
onError: (error) => {
|
|
7
|
-
console.error("
|
|
7
|
+
console.error("scrubVideoSegmentIdTask error", error);
|
|
8
8
|
},
|
|
9
9
|
onComplete: (_value) => {},
|
|
10
10
|
task: async ([, targetSeekTimeMs], { signal }) => {
|
|
11
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
12
|
signal.throwIfAborted();
|
|
13
|
-
|
|
13
|
+
const scrubRendition = mediaEngine.getScrubVideoRendition();
|
|
14
|
+
if (!scrubRendition) return void 0;
|
|
15
|
+
return mediaEngine.computeSegmentId(targetSeekTimeMs, {
|
|
16
|
+
...scrubRendition,
|
|
17
|
+
src: mediaEngine.src
|
|
18
|
+
});
|
|
14
19
|
}
|
|
15
20
|
});
|
|
16
21
|
};
|
|
17
|
-
export {
|
|
22
|
+
export { makeScrubVideoSegmentIdTask };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Task } from '@lit/task';
|
|
2
|
+
import { VideoSample } from 'mediabunny';
|
|
3
|
+
import { EFVideo } from '../../EFVideo';
|
|
4
|
+
type UnifiedVideoSeekTask = Task<readonly [number], VideoSample | undefined>;
|
|
5
|
+
export declare const makeUnifiedVideoSeekTask: (host: EFVideo) => UnifiedVideoSeekTask;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { getLatestMediaEngine } from "../tasks/makeMediaEngineTask.js";
|
|
2
|
+
import { ScrubInputCache } from "./ScrubInputCache.js";
|
|
3
|
+
import { Task } from "@lit/task";
|
|
4
|
+
const scrubInputCache = new ScrubInputCache();
|
|
5
|
+
const makeUnifiedVideoSeekTask = (host) => {
|
|
6
|
+
return new Task(host, {
|
|
7
|
+
args: () => [host.desiredSeekTimeMs],
|
|
8
|
+
onError: (error) => {
|
|
9
|
+
console.error("unifiedVideoSeekTask error", error);
|
|
10
|
+
},
|
|
11
|
+
onComplete: (_value) => {},
|
|
12
|
+
task: async ([desiredSeekTimeMs], { signal }) => {
|
|
13
|
+
signal.throwIfAborted();
|
|
14
|
+
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
15
|
+
if (!mediaEngine) return void 0;
|
|
16
|
+
const mainRendition = mediaEngine.videoRendition;
|
|
17
|
+
if (mainRendition) {
|
|
18
|
+
const mainSegmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, mainRendition);
|
|
19
|
+
if (mainSegmentId !== void 0 && mediaEngine.isSegmentCached(mainSegmentId, mainRendition)) return await getMainVideoSample(host, mediaEngine, desiredSeekTimeMs, signal);
|
|
20
|
+
}
|
|
21
|
+
const scrubSample = await tryGetScrubSample(mediaEngine, desiredSeekTimeMs, signal);
|
|
22
|
+
if (scrubSample || signal.aborted) {
|
|
23
|
+
if (scrubSample) startMainQualityUpgrade(host, mediaEngine, desiredSeekTimeMs, signal).catch(() => {});
|
|
24
|
+
return scrubSample;
|
|
25
|
+
}
|
|
26
|
+
return await getMainVideoSample(host, mediaEngine, desiredSeekTimeMs, signal);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Try to get scrub sample from cache (instant if available)
|
|
32
|
+
*/
|
|
33
|
+
async function tryGetScrubSample(mediaEngine, desiredSeekTimeMs, signal) {
|
|
34
|
+
try {
|
|
35
|
+
let scrubRendition;
|
|
36
|
+
if ("data" in mediaEngine && mediaEngine.data?.videoRenditions) scrubRendition = mediaEngine.data.videoRenditions.find((r) => r.id === "scrub");
|
|
37
|
+
if (!scrubRendition) return void 0;
|
|
38
|
+
const scrubRenditionWithSrc = {
|
|
39
|
+
...scrubRendition,
|
|
40
|
+
src: mediaEngine.src
|
|
41
|
+
};
|
|
42
|
+
const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, scrubRenditionWithSrc);
|
|
43
|
+
if (segmentId === void 0) return void 0;
|
|
44
|
+
const isCached = mediaEngine.isSegmentCached(segmentId, scrubRenditionWithSrc);
|
|
45
|
+
if (!isCached) return void 0;
|
|
46
|
+
const scrubInput = await scrubInputCache.getOrCreateInput(segmentId, async () => {
|
|
47
|
+
const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(scrubRenditionWithSrc, signal), mediaEngine.fetchMediaSegment(segmentId, scrubRenditionWithSrc)]);
|
|
48
|
+
if (!initSegment || !mediaSegment || signal.aborted) return void 0;
|
|
49
|
+
const { BufferedSeekingInput } = await import("../BufferedSeekingInput.js");
|
|
50
|
+
const { EFMedia } = await import("../../EFMedia.js");
|
|
51
|
+
return new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
|
|
52
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
53
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
54
|
+
startTimeOffsetMs: scrubRendition.startTimeOffsetMs
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
if (!scrubInput) return void 0;
|
|
58
|
+
const videoTrack = await scrubInput.getFirstVideoTrack();
|
|
59
|
+
if (!videoTrack) return void 0;
|
|
60
|
+
const sample = await scrubInput.seek(videoTrack.id, desiredSeekTimeMs);
|
|
61
|
+
return sample;
|
|
62
|
+
} catch (_error) {
|
|
63
|
+
if (signal.aborted) return void 0;
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get main video sample (slower path with fetching)
|
|
69
|
+
*/
|
|
70
|
+
async function getMainVideoSample(_host, mediaEngine, desiredSeekTimeMs, signal) {
|
|
71
|
+
try {
|
|
72
|
+
const segmentId = mediaEngine.computeSegmentId(desiredSeekTimeMs, mediaEngine.getVideoRendition());
|
|
73
|
+
if (segmentId === void 0) return void 0;
|
|
74
|
+
const [initSegment, mediaSegment] = await Promise.all([mediaEngine.fetchInitSegment(mediaEngine.getVideoRendition(), signal), mediaEngine.fetchMediaSegment(segmentId, mediaEngine.getVideoRendition(), signal)]);
|
|
75
|
+
if (!initSegment || !mediaSegment) return void 0;
|
|
76
|
+
signal.throwIfAborted();
|
|
77
|
+
const { BufferedSeekingInput } = await import("../BufferedSeekingInput.js");
|
|
78
|
+
const { EFMedia } = await import("../../EFMedia.js");
|
|
79
|
+
const videoRendition = mediaEngine.videoRendition;
|
|
80
|
+
const startTimeOffsetMs = videoRendition?.startTimeOffsetMs;
|
|
81
|
+
const mainInput = new BufferedSeekingInput(await new Blob([initSegment, mediaSegment]).arrayBuffer(), {
|
|
82
|
+
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
83
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
84
|
+
startTimeOffsetMs
|
|
85
|
+
});
|
|
86
|
+
const videoTrack = await mainInput.getFirstVideoTrack();
|
|
87
|
+
if (!videoTrack) return void 0;
|
|
88
|
+
signal.throwIfAborted();
|
|
89
|
+
const sample = await mainInput.seek(videoTrack.id, desiredSeekTimeMs);
|
|
90
|
+
return sample;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (signal.aborted) return void 0;
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Start background upgrade to main quality (non-blocking)
|
|
98
|
+
*/
|
|
99
|
+
async function startMainQualityUpgrade(host, mediaEngine, targetSeekTimeMs, signal) {
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
101
|
+
if (signal.aborted || host.desiredSeekTimeMs !== targetSeekTimeMs) return;
|
|
102
|
+
const mainSample = await getMainVideoSample(host, mediaEngine, targetSeekTimeMs, signal);
|
|
103
|
+
if (mainSample && !signal.aborted && host.desiredSeekTimeMs === targetSeekTimeMs) {
|
|
104
|
+
const videoFrame = mainSample.toVideoFrame();
|
|
105
|
+
try {
|
|
106
|
+
host.displayFrame(videoFrame, targetSeekTimeMs);
|
|
107
|
+
} finally {
|
|
108
|
+
videoFrame.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export { makeUnifiedVideoSeekTask };
|
|
@@ -6,12 +6,12 @@ import { Task } from "@lit/task";
|
|
|
6
6
|
const makeVideoBufferTask = (host) => {
|
|
7
7
|
let currentState = {
|
|
8
8
|
currentSeekTimeMs: 0,
|
|
9
|
+
requestedSegments: /* @__PURE__ */ new Set(),
|
|
9
10
|
activeRequests: /* @__PURE__ */ new Set(),
|
|
10
|
-
cachedSegments: /* @__PURE__ */ new Set(),
|
|
11
11
|
requestQueue: []
|
|
12
12
|
};
|
|
13
13
|
return new Task(host, {
|
|
14
|
-
autoRun: EF_INTERACTIVE,
|
|
14
|
+
autoRun: EF_INTERACTIVE && !EF_RENDERING(),
|
|
15
15
|
args: () => [host.desiredSeekTimeMs],
|
|
16
16
|
onError: (error) => {
|
|
17
17
|
console.error("videoBufferTask error", error);
|
|
@@ -20,19 +20,25 @@ const makeVideoBufferTask = (host) => {
|
|
|
20
20
|
currentState = value;
|
|
21
21
|
},
|
|
22
22
|
task: async ([seekTimeMs], { signal }) => {
|
|
23
|
+
if (EF_RENDERING()) return currentState;
|
|
23
24
|
const currentConfig = {
|
|
24
25
|
bufferDurationMs: host.videoBufferDurationMs,
|
|
25
26
|
maxParallelFetches: host.maxVideoBufferFetches,
|
|
26
|
-
enableBuffering: host.enableVideoBuffering
|
|
27
|
+
enableBuffering: host.enableVideoBuffering
|
|
27
28
|
};
|
|
28
29
|
return manageMediaBuffer(seekTimeMs, currentConfig, currentState, host.intrinsicDurationMs || 1e4, signal, {
|
|
29
30
|
computeSegmentId: async (timeMs, rendition) => {
|
|
30
31
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
31
32
|
return mediaEngine.computeSegmentId(timeMs, rendition);
|
|
32
33
|
},
|
|
33
|
-
|
|
34
|
+
prefetchSegment: async (segmentId, rendition) => {
|
|
34
35
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
35
|
-
|
|
36
|
+
await mediaEngine.fetchMediaSegment(segmentId, rendition);
|
|
37
|
+
},
|
|
38
|
+
isSegmentCached: (segmentId, rendition) => {
|
|
39
|
+
const mediaEngine = host.mediaEngineTask.value;
|
|
40
|
+
if (!mediaEngine) return false;
|
|
41
|
+
return mediaEngine.isSegmentCached(segmentId, rendition);
|
|
36
42
|
},
|
|
37
43
|
getRendition: async () => {
|
|
38
44
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
@@ -84,15 +84,5 @@ export declare class EFMedia extends EFMedia_base {
|
|
|
84
84
|
* Now powered by clean, testable utility functions
|
|
85
85
|
*/
|
|
86
86
|
fetchAudioSpanningTime(fromMs: number, toMs: number, signal?: AbortSignal): Promise<AudioSpan>;
|
|
87
|
-
/**
|
|
88
|
-
* Check if an audio segment is cached in the unified buffer system
|
|
89
|
-
* Now uses the same caching approach as video for consistency
|
|
90
|
-
*/
|
|
91
|
-
getCachedAudioSegment(segmentId: number): boolean;
|
|
92
|
-
/**
|
|
93
|
-
* Get cached audio segments from the unified buffer system
|
|
94
|
-
* Now uses the same caching approach as video for consistency
|
|
95
|
-
*/
|
|
96
|
-
getCachedAudioSegments(segmentIds: number[]): Set<number>;
|
|
97
87
|
}
|
|
98
88
|
export {};
|
package/dist/elements/EFMedia.js
CHANGED
|
@@ -28,7 +28,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
28
28
|
constructor(..._args) {
|
|
29
29
|
super(..._args);
|
|
30
30
|
this.currentTimeMs = 0;
|
|
31
|
-
this.audioBufferDurationMs =
|
|
31
|
+
this.audioBufferDurationMs = 1e4;
|
|
32
32
|
this.maxAudioBufferFetches = 2;
|
|
33
33
|
this.enableAudioBuffering = true;
|
|
34
34
|
this.mute = false;
|
|
@@ -128,22 +128,6 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
128
128
|
async fetchAudioSpanningTime(fromMs, toMs, signal = new AbortController().signal) {
|
|
129
129
|
return fetchAudioSpanningTime(this, fromMs, toMs, signal);
|
|
130
130
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Check if an audio segment is cached in the unified buffer system
|
|
133
|
-
* Now uses the same caching approach as video for consistency
|
|
134
|
-
*/
|
|
135
|
-
getCachedAudioSegment(segmentId) {
|
|
136
|
-
return this.audioBufferTask.value?.cachedSegments.has(segmentId) ?? false;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Get cached audio segments from the unified buffer system
|
|
140
|
-
* Now uses the same caching approach as video for consistency
|
|
141
|
-
*/
|
|
142
|
-
getCachedAudioSegments(segmentIds) {
|
|
143
|
-
const bufferState = this.audioBufferTask.value;
|
|
144
|
-
if (!bufferState) return /* @__PURE__ */ new Set();
|
|
145
|
-
return new Set(segmentIds.filter((id) => bufferState.cachedSegments.has(id)));
|
|
146
|
-
}
|
|
147
131
|
};
|
|
148
132
|
_decorate([property({ type: Number })], EFMedia.prototype, "currentTimeMs", void 0);
|
|
149
133
|
_decorate([property({
|
|
@@ -28,12 +28,14 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
28
28
|
* @domAttribute "enable-video-buffering"
|
|
29
29
|
*/
|
|
30
30
|
enableVideoBuffering: boolean;
|
|
31
|
-
|
|
32
|
-
videoInitSegmentFetchTask: Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined], ArrayBuffer>;
|
|
33
|
-
videoSegmentFetchTask: Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number | undefined], ArrayBuffer>;
|
|
34
|
-
videoInputTask: import('./EFMedia/shared/MediaTaskUtils.ts').InputTask;
|
|
35
|
-
videoSeekTask: Task<readonly [number, import('./EFMedia/BufferedSeekingInput.ts').BufferedSeekingInput | undefined], import('mediabunny').VideoSample | undefined>;
|
|
31
|
+
unifiedVideoSeekTask: Task<readonly [number], import('mediabunny').VideoSample | undefined>;
|
|
36
32
|
videoBufferTask: Task<readonly [number], import('./EFMedia/videoTasks/makeVideoBufferTask.ts').VideoBufferState>;
|
|
33
|
+
scrubVideoBufferTask: Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined], unknown>;
|
|
34
|
+
scrubVideoInputTask: import('./EFMedia/shared/MediaTaskUtils.ts').InputTask;
|
|
35
|
+
scrubVideoSeekTask: Task<readonly [number], import('mediabunny').VideoSample | undefined>;
|
|
36
|
+
scrubVideoSegmentIdTask: Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number], number | undefined>;
|
|
37
|
+
scrubVideoSegmentFetchTask: Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined, number | undefined], ArrayBuffer>;
|
|
38
|
+
scrubVideoInitSegmentFetchTask: Task<readonly [import('../transcoding/types/index.ts').MediaEngine | undefined], ArrayBuffer>;
|
|
37
39
|
/**
|
|
38
40
|
* Delayed loading state manager for user feedback
|
|
39
41
|
*/
|
|
@@ -69,7 +71,7 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
69
71
|
/**
|
|
70
72
|
* Display a video frame on the canvas
|
|
71
73
|
*/
|
|
72
|
-
|
|
74
|
+
displayFrame(frame: VideoFrame, seekToMs: number): number;
|
|
73
75
|
/**
|
|
74
76
|
* Check if we're in production rendering mode (EF_FRAMEGEN active) vs preview mode
|
|
75
77
|
*/
|
|
@@ -79,10 +81,10 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
79
81
|
*/
|
|
80
82
|
private isFrameRenderingActive;
|
|
81
83
|
/**
|
|
82
|
-
* Legacy getter for fragment index task
|
|
83
|
-
* Still used by EFCaptions
|
|
84
|
+
* Legacy getter for fragment index task
|
|
85
|
+
* Still used by EFCaptions - maps to unified video seek task
|
|
84
86
|
*/
|
|
85
|
-
get fragmentIndexTask(): Task<readonly [import('
|
|
87
|
+
get fragmentIndexTask(): Task<readonly [number], import('mediabunny').VideoSample | undefined>;
|
|
86
88
|
/**
|
|
87
89
|
* Clean up resources when component is disconnected
|
|
88
90
|
*/
|
package/dist/elements/EFVideo.js
CHANGED
|
@@ -2,12 +2,14 @@ import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
|
2
2
|
import { EFMedia } from "./EFMedia.js";
|
|
3
3
|
import { TWMixin } from "../gui/TWMixin2.js";
|
|
4
4
|
import { DelayedLoadingState } from "../DelayedLoadingState.js";
|
|
5
|
+
import { makeScrubVideoBufferTask } from "./EFMedia/videoTasks/makeScrubVideoBufferTask.js";
|
|
6
|
+
import { makeScrubVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoInitSegmentFetchTask.js";
|
|
7
|
+
import { makeScrubVideoInputTask } from "./EFMedia/videoTasks/makeScrubVideoInputTask.js";
|
|
8
|
+
import { makeScrubVideoSeekTask } from "./EFMedia/videoTasks/makeScrubVideoSeekTask.js";
|
|
9
|
+
import { makeScrubVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentFetchTask.js";
|
|
10
|
+
import { makeScrubVideoSegmentIdTask } from "./EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js";
|
|
11
|
+
import { makeUnifiedVideoSeekTask } from "./EFMedia/videoTasks/makeUnifiedVideoSeekTask.js";
|
|
5
12
|
import { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.js";
|
|
6
|
-
import { makeVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js";
|
|
7
|
-
import { makeVideoInputTask } from "./EFMedia/videoTasks/makeVideoInputTask.js";
|
|
8
|
-
import { makeVideoSeekTask } from "./EFMedia/videoTasks/makeVideoSeekTask.js";
|
|
9
|
-
import { makeVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoSegmentFetchTask.js";
|
|
10
|
-
import { makeVideoSegmentIdTask } from "./EFMedia/videoTasks/makeVideoSegmentIdTask.js";
|
|
11
13
|
import { Task } from "@lit/task";
|
|
12
14
|
import debug from "debug";
|
|
13
15
|
import { css, html } from "lit";
|
|
@@ -80,15 +82,17 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
80
82
|
constructor() {
|
|
81
83
|
super();
|
|
82
84
|
this.canvasRef = createRef();
|
|
83
|
-
this.videoBufferDurationMs =
|
|
85
|
+
this.videoBufferDurationMs = 1e4;
|
|
84
86
|
this.maxVideoBufferFetches = 2;
|
|
85
87
|
this.enableVideoBuffering = true;
|
|
86
|
-
this.
|
|
87
|
-
this.videoInitSegmentFetchTask = makeVideoInitSegmentFetchTask(this);
|
|
88
|
-
this.videoSegmentFetchTask = makeVideoSegmentFetchTask(this);
|
|
89
|
-
this.videoInputTask = makeVideoInputTask(this);
|
|
90
|
-
this.videoSeekTask = makeVideoSeekTask(this);
|
|
88
|
+
this.unifiedVideoSeekTask = makeUnifiedVideoSeekTask(this);
|
|
91
89
|
this.videoBufferTask = makeVideoBufferTask(this);
|
|
90
|
+
this.scrubVideoBufferTask = makeScrubVideoBufferTask(this);
|
|
91
|
+
this.scrubVideoInputTask = makeScrubVideoInputTask(this);
|
|
92
|
+
this.scrubVideoSeekTask = makeScrubVideoSeekTask(this);
|
|
93
|
+
this.scrubVideoSegmentIdTask = makeScrubVideoSegmentIdTask(this);
|
|
94
|
+
this.scrubVideoSegmentFetchTask = makeScrubVideoSegmentFetchTask(this);
|
|
95
|
+
this.scrubVideoInitSegmentFetchTask = makeScrubVideoInitSegmentFetchTask(this);
|
|
92
96
|
this.loadingState = {
|
|
93
97
|
isLoading: false,
|
|
94
98
|
operation: null,
|
|
@@ -102,7 +106,7 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
102
106
|
},
|
|
103
107
|
onComplete: () => {},
|
|
104
108
|
task: async ([_desiredSeekTimeMs], { signal }) => {
|
|
105
|
-
await this.
|
|
109
|
+
await this.unifiedVideoSeekTask.taskComplete;
|
|
106
110
|
await this.paintTask.taskComplete;
|
|
107
111
|
if (signal.aborted) return;
|
|
108
112
|
}
|
|
@@ -114,16 +118,20 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
114
118
|
},
|
|
115
119
|
onComplete: () => {},
|
|
116
120
|
task: async ([_seekToMs], { signal }) => {
|
|
117
|
-
await this.videoSeekTask.taskComplete;
|
|
118
121
|
const isProductionRendering = this.isInProductionRenderingMode();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
try {
|
|
123
|
+
await this.unifiedVideoSeekTask.taskComplete;
|
|
124
|
+
const videoSample = this.unifiedVideoSeekTask.value;
|
|
125
|
+
if (videoSample) {
|
|
126
|
+
const videoFrame = videoSample.toVideoFrame();
|
|
127
|
+
try {
|
|
128
|
+
this.displayFrame(videoFrame, _seekToMs);
|
|
129
|
+
} finally {
|
|
130
|
+
videoFrame.close();
|
|
131
|
+
}
|
|
126
132
|
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.warn("Unified video pipeline error:", error);
|
|
127
135
|
}
|
|
128
136
|
if (!isProductionRendering) {
|
|
129
137
|
if (!this.rootTimegroup || this.rootTimegroup.currentTimeMs === 0 && this.desiredSeekTimeMs === 0) return;
|
|
@@ -243,11 +251,11 @@ let EFVideo = class EFVideo$1 extends TWMixin(EFMedia) {
|
|
|
243
251
|
return currentTime >= renderStartTime;
|
|
244
252
|
}
|
|
245
253
|
/**
|
|
246
|
-
* Legacy getter for fragment index task
|
|
247
|
-
* Still used by EFCaptions
|
|
254
|
+
* Legacy getter for fragment index task
|
|
255
|
+
* Still used by EFCaptions - maps to unified video seek task
|
|
248
256
|
*/
|
|
249
257
|
get fragmentIndexTask() {
|
|
250
|
-
return this.
|
|
258
|
+
return this.unifiedVideoSeekTask;
|
|
251
259
|
}
|
|
252
260
|
/**
|
|
253
261
|
* Clean up resources when component is disconnected
|
|
@@ -7,6 +7,7 @@ let EFConfiguration = class EFConfiguration$1 extends LitElement {
|
|
|
7
7
|
constructor(..._args) {
|
|
8
8
|
super(..._args);
|
|
9
9
|
this.efConfiguration = this;
|
|
10
|
+
this.mediaEngine = "cloud";
|
|
10
11
|
}
|
|
11
12
|
static {
|
|
12
13
|
this.styles = [css`
|
|
@@ -28,5 +29,9 @@ _decorate([property({
|
|
|
28
29
|
type: String,
|
|
29
30
|
attribute: "signing-url"
|
|
30
31
|
})], EFConfiguration.prototype, "signingURL", void 0);
|
|
32
|
+
_decorate([property({
|
|
33
|
+
type: String,
|
|
34
|
+
attribute: "media-engine"
|
|
35
|
+
})], EFConfiguration.prototype, "mediaEngine", void 0);
|
|
31
36
|
EFConfiguration = _decorate([customElement("ef-configuration")], EFConfiguration);
|
|
32
37
|
export { EFConfiguration, efConfigurationContext };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LitElement, PropertyValueMap, ReactiveController, TemplateResult
|
|
1
|
+
import { LitElement, nothing, PropertyValueMap, ReactiveController, TemplateResult } from 'lit';
|
|
2
2
|
import { EFAudio } from '../elements/EFAudio.js';
|
|
3
3
|
import { EFImage } from '../elements/EFImage.js';
|
|
4
4
|
import { TemporalMixinInterface } from '../elements/EFTemporal.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { EFImage } from './elements/EFImage.js';
|
|
|
3
3
|
export type { EFMedia } from './elements/EFMedia.js';
|
|
4
4
|
export { EFAudio } from './elements/EFAudio.js';
|
|
5
5
|
export { EFVideo } from './elements/EFVideo.js';
|
|
6
|
-
export { EFCaptions, EFCaptionsActiveWord,
|
|
6
|
+
export { EFCaptions, EFCaptionsActiveWord, EFCaptionsAfterActiveWord, EFCaptionsBeforeActiveWord, EFCaptionsSegment, } from './elements/EFCaptions.js';
|
|
7
7
|
export { EFWaveform } from './elements/EFWaveform.js';
|
|
8
8
|
export { EFConfiguration } from './gui/EFConfiguration.ts';
|
|
9
9
|
export { EFWorkbench } from './gui/EFWorkbench.js';
|
|
@@ -214,6 +214,17 @@ export interface MediaEngine {
|
|
|
214
214
|
* Get the audio rendition, or throws if no audio rendition is available
|
|
215
215
|
*/
|
|
216
216
|
getAudioRendition: () => AudioRendition;
|
|
217
|
+
/**
|
|
218
|
+
* Check if a segment is cached for a given rendition
|
|
219
|
+
*/
|
|
220
|
+
isSegmentCached: (segmentId: number, rendition: AudioRendition | VideoRendition) => boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Get scrub video rendition if available, otherwise return undefined
|
|
223
|
+
* Each engine implements this based on their capabilities:
|
|
224
|
+
* - JitMediaEngine: looks for "scrub" rendition in manifest
|
|
225
|
+
* - AssetMediaEngine: returns regular video rendition (no separate scrub)
|
|
226
|
+
*/
|
|
227
|
+
getScrubVideoRendition(): VideoRendition | undefined;
|
|
217
228
|
/**
|
|
218
229
|
* Calculate audio segments needed for a time range
|
|
219
230
|
* Each media engine implements this based on their segment structure
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.26-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"license": "UNLICENSED",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@bramus/style-observer": "^1.3.0",
|
|
30
|
-
"@editframe/assets": "0.18.
|
|
30
|
+
"@editframe/assets": "0.18.26-beta.0",
|
|
31
31
|
"@lit/context": "^1.1.2",
|
|
32
32
|
"@lit/task": "^1.0.1",
|
|
33
33
|
"d3": "^7.9.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Task } from "@lit/task";
|
|
2
|
-
import { LitElement, type PropertyValueMap
|
|
2
|
+
import { css, html, LitElement, type PropertyValueMap } from "lit";
|
|
3
3
|
import { customElement, property } from "lit/decorators.js";
|
|
4
4
|
import type { GetISOBMFFFileTranscriptionResult } from "../../../api/src/index.js";
|
|
5
5
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
package/src/elements/EFImage.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Task } from "@lit/task";
|
|
2
|
-
import {
|
|
2
|
+
import { css, html, LitElement } from "lit";
|
|
3
3
|
import { customElement, property } from "lit/decorators.js";
|
|
4
4
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
5
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
InitSegmentPaths,
|
|
6
6
|
MediaEngine,
|
|
7
7
|
SegmentTimeRange,
|
|
8
|
+
VideoRendition,
|
|
8
9
|
} from "../../transcoding/types";
|
|
9
10
|
import type { UrlGenerator } from "../../transcoding/utils/UrlGenerator";
|
|
10
11
|
import type { EFMedia } from "../EFMedia";
|
|
@@ -277,4 +278,9 @@ export class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
|
277
278
|
|
|
278
279
|
return nearestSegmentIndex;
|
|
279
280
|
}
|
|
281
|
+
|
|
282
|
+
getScrubVideoRendition(): VideoRendition | undefined {
|
|
283
|
+
// AssetMediaEngine only has one video rendition - return it for scrub too
|
|
284
|
+
return this.videoRendition;
|
|
285
|
+
}
|
|
280
286
|
}
|
|
@@ -333,4 +333,64 @@ export abstract class BaseMediaEngine {
|
|
|
333
333
|
|
|
334
334
|
return segments;
|
|
335
335
|
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Check if a segment is cached for a given rendition
|
|
339
|
+
* This needs to check the URL-based cache since that's where segments are actually stored
|
|
340
|
+
*/
|
|
341
|
+
isSegmentCached(
|
|
342
|
+
segmentId: number,
|
|
343
|
+
rendition: AudioRendition | VideoRendition,
|
|
344
|
+
): boolean {
|
|
345
|
+
try {
|
|
346
|
+
// Check if this is a JIT engine by looking for urlGenerator property
|
|
347
|
+
const maybeJitEngine = this as any;
|
|
348
|
+
if (
|
|
349
|
+
maybeJitEngine.urlGenerator &&
|
|
350
|
+
typeof maybeJitEngine.urlGenerator.generateSegmentUrl === "function"
|
|
351
|
+
) {
|
|
352
|
+
// This is a JIT engine - generate the URL and check URL-based cache
|
|
353
|
+
if (!rendition.id) {
|
|
354
|
+
console.log(
|
|
355
|
+
`🎬 BaseMediaEngine: No rendition ID for segment ${segmentId}`,
|
|
356
|
+
);
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const segmentUrl = maybeJitEngine.urlGenerator.generateSegmentUrl(
|
|
361
|
+
segmentId,
|
|
362
|
+
rendition.id,
|
|
363
|
+
maybeJitEngine,
|
|
364
|
+
);
|
|
365
|
+
const urlIsCached = mediaCache.has(segmentUrl);
|
|
366
|
+
|
|
367
|
+
return urlIsCached;
|
|
368
|
+
}
|
|
369
|
+
// For other engine types, fall back to the old segment-based key approach
|
|
370
|
+
const cacheKey = `${rendition.src}-${rendition.id || "default"}-${segmentId}-${rendition.trackId}`;
|
|
371
|
+
const isCached = mediaCache.has(cacheKey);
|
|
372
|
+
console.log(
|
|
373
|
+
`🎬 BaseMediaEngine: Non-JIT engine, using segment key: "${cacheKey}", result: ${isCached}`,
|
|
374
|
+
);
|
|
375
|
+
return isCached;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.warn(
|
|
378
|
+
`🎬 BaseMediaEngine: Error checking if segment ${segmentId} is cached:`,
|
|
379
|
+
error,
|
|
380
|
+
);
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Get cached segment IDs from a list for a given rendition
|
|
387
|
+
*/
|
|
388
|
+
getCachedSegments(
|
|
389
|
+
segmentIds: number[],
|
|
390
|
+
rendition: AudioRendition | VideoRendition,
|
|
391
|
+
): Set<number> {
|
|
392
|
+
return new Set(
|
|
393
|
+
segmentIds.filter((id) => this.isSegmentCached(id, rendition)),
|
|
394
|
+
);
|
|
395
|
+
}
|
|
336
396
|
}
|
|
@@ -172,4 +172,22 @@ export class JitMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
|
172
172
|
|
|
173
173
|
return segmentIndex + 1; // Convert 0-based to 1-based
|
|
174
174
|
}
|
|
175
|
+
|
|
176
|
+
getScrubVideoRendition(): VideoRendition | undefined {
|
|
177
|
+
if (!this.data.videoRenditions) return undefined;
|
|
178
|
+
|
|
179
|
+
const scrubManifestRendition = this.data.videoRenditions.find(
|
|
180
|
+
(r) => r.id === "scrub",
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (!scrubManifestRendition) return this.videoRendition; // Fallback to main
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
id: scrubManifestRendition.id as any,
|
|
187
|
+
trackId: undefined,
|
|
188
|
+
src: this.src,
|
|
189
|
+
segmentDurationMs: scrubManifestRendition.segmentDurationMs,
|
|
190
|
+
segmentDurationsMs: scrubManifestRendition.segmentDurationsMs,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
175
193
|
}
|