@meframe/core 0.0.28 → 0.0.30-beta
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/Meframe.d.ts +2 -13
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +6 -100
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +35 -19
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +223 -134
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +15 -2
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +58 -38
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/cache/l2/L2Cache.d.ts.map +1 -1
- package/dist/cache/l2/L2Cache.js +5 -5
- package/dist/cache/l2/L2Cache.js.map +1 -1
- package/dist/cache/l2/L2OPFSStore.d.ts +37 -0
- package/dist/cache/l2/L2OPFSStore.d.ts.map +1 -0
- package/dist/cache/l2/L2OPFSStore.js +89 -0
- package/dist/cache/l2/L2OPFSStore.js.map +1 -0
- package/dist/cache/resource/AudioSampleCache.d.ts +52 -0
- package/dist/cache/resource/AudioSampleCache.d.ts.map +1 -0
- package/dist/cache/resource/AudioSampleCache.js +69 -0
- package/dist/cache/resource/AudioSampleCache.js.map +1 -0
- package/dist/cache/resource/ImageBitmapCache.d.ts +65 -0
- package/dist/cache/resource/ImageBitmapCache.d.ts.map +1 -0
- package/dist/cache/resource/ImageBitmapCache.js +101 -0
- package/dist/cache/resource/ImageBitmapCache.js.map +1 -0
- package/dist/cache/resource/MP4IndexCache.d.ts +48 -0
- package/dist/cache/resource/MP4IndexCache.d.ts.map +1 -0
- package/dist/cache/resource/MP4IndexCache.js +104 -0
- package/dist/cache/resource/MP4IndexCache.js.map +1 -0
- package/dist/cache/resource/ResourceCache.d.ts +46 -0
- package/dist/cache/resource/ResourceCache.d.ts.map +1 -0
- package/dist/cache/resource/ResourceCache.js +92 -0
- package/dist/cache/resource/ResourceCache.js.map +1 -0
- package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts +75 -0
- package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts.map +1 -0
- package/dist/cache/{l2/IndexedDBStore.js → storage/indexeddb/ChunkRecordStore.js} +3 -3
- package/dist/cache/storage/indexeddb/ChunkRecordStore.js.map +1 -0
- package/dist/cache/storage/opfs/OPFSManager.d.ts +54 -0
- package/dist/cache/storage/opfs/OPFSManager.d.ts.map +1 -0
- package/dist/cache/storage/opfs/OPFSManager.js +133 -0
- package/dist/cache/storage/opfs/OPFSManager.js.map +1 -0
- package/dist/cache/storage/opfs/types.d.ts +16 -0
- package/dist/cache/storage/opfs/types.d.ts.map +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +21 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/types.d.ts +28 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controllers/ExportController.d.ts +16 -0
- package/dist/controllers/ExportController.d.ts.map +1 -0
- package/dist/controllers/ExportController.js +44 -0
- package/dist/controllers/ExportController.js.map +1 -0
- package/dist/controllers/PlaybackController.d.ts +28 -4
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +117 -52
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/index.d.ts +2 -3
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/types.d.ts +0 -28
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/event/events.d.ts +8 -0
- package/dist/event/events.d.ts.map +1 -1
- package/dist/event/events.js +1 -0
- package/dist/event/events.js.map +1 -1
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +11 -6
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/RcFrame.d.ts +2 -0
- package/dist/model/RcFrame.d.ts.map +1 -1
- package/dist/model/RcFrame.js +3 -0
- package/dist/model/RcFrame.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts +35 -0
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -0
- package/dist/orchestrator/ExportScheduler.js +241 -0
- package/dist/orchestrator/ExportScheduler.js.map +1 -0
- package/dist/orchestrator/GlobalAudioSession.d.ts +21 -7
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +132 -140
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts +73 -0
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -0
- package/dist/orchestrator/OnDemandVideoSession.js +281 -0
- package/dist/orchestrator/OnDemandVideoSession.js.map +1 -0
- package/dist/orchestrator/Orchestrator.d.ts +22 -17
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +234 -301
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +3 -15
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/index.d.ts +0 -1
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/types.d.ts +4 -4
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/compose/FilterProcessor.d.ts +1 -1
- package/dist/stages/compose/FilterProcessor.d.ts.map +1 -1
- package/dist/stages/compose/FilterProcessor.js +226 -0
- package/dist/stages/compose/FilterProcessor.js.map +1 -0
- package/dist/stages/compose/FrameRateConverter.d.ts +68 -0
- package/dist/stages/compose/FrameRateConverter.d.ts.map +1 -0
- package/dist/stages/compose/LayerRenderer.d.ts +1 -1
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
- package/dist/stages/compose/LayerRenderer.js +270 -0
- package/dist/stages/compose/LayerRenderer.js.map +1 -0
- package/dist/stages/compose/TransitionProcessor.d.ts +1 -1
- package/dist/stages/compose/TransitionProcessor.d.ts.map +1 -1
- package/dist/stages/compose/TransitionProcessor.js +189 -0
- package/dist/stages/compose/TransitionProcessor.js.map +1 -0
- package/dist/stages/compose/VideoComposer.d.ts +6 -4
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
- package/dist/stages/compose/VideoComposer.js +229 -0
- package/dist/stages/compose/VideoComposer.js.map +1 -0
- package/dist/stages/compose/text-renderers/animation-utils.js +76 -0
- package/dist/stages/compose/text-renderers/animation-utils.js.map +1 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts +2 -2
- package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/basic-text-renderer.js +93 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/character-ktv-renderer.js +132 -0
- package/dist/stages/compose/text-renderers/character-ktv-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/word-by-word-renderer.js +128 -0
- package/dist/stages/compose/text-renderers/word-by-word-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/word-fancy-renderer.js +135 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.js.map +1 -0
- package/dist/stages/compose/text-utils/locale-detector.js +16 -0
- package/dist/stages/compose/text-utils/locale-detector.js.map +1 -0
- package/dist/stages/compose/text-utils/text-metrics.js +21 -0
- package/dist/stages/compose/text-utils/text-metrics.js.map +1 -0
- package/dist/stages/compose/text-utils/text-wrapper.js +225 -0
- package/dist/stages/compose/text-utils/text-wrapper.js.map +1 -0
- package/dist/stages/compose/types.d.ts +2 -1
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/stages/decode/BaseDecoder.js +0 -3
- package/dist/stages/decode/BaseDecoder.js.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts +5 -0
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/stages/demux/MP4Demuxer.js +281 -0
- package/dist/stages/demux/MP4Demuxer.js.map +1 -0
- package/dist/stages/demux/MP4IndexParser.d.ts +71 -0
- package/dist/stages/demux/MP4IndexParser.d.ts.map +1 -0
- package/dist/stages/demux/MP4IndexParser.js +416 -0
- package/dist/stages/demux/MP4IndexParser.js.map +1 -0
- package/dist/stages/demux/types.d.ts +48 -0
- package/dist/stages/demux/types.d.ts.map +1 -1
- package/dist/stages/encode/index.d.ts +0 -1
- package/dist/stages/encode/index.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +44 -2
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +281 -37
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/TaskManager.d.ts +6 -2
- package/dist/stages/load/TaskManager.d.ts.map +1 -1
- package/dist/stages/load/TaskManager.js +27 -4
- package/dist/stages/load/TaskManager.js.map +1 -1
- package/dist/stages/load/types.d.ts +7 -0
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.d.ts +2 -2
- package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.js +24 -13
- package/dist/stages/mux/MP4Muxer.js.map +1 -1
- package/dist/stages/mux/MuxManager.d.ts +10 -21
- package/dist/stages/mux/MuxManager.d.ts.map +1 -1
- package/dist/stages/mux/MuxManager.js +21 -162
- package/dist/stages/mux/MuxManager.js.map +1 -1
- package/dist/stages/mux/index.d.ts +0 -1
- package/dist/stages/mux/index.d.ts.map +1 -1
- package/dist/utils/binary-search.d.ts +12 -4
- package/dist/utils/binary-search.d.ts.map +1 -1
- package/dist/utils/binary-search.js +52 -6
- package/dist/utils/binary-search.js.map +1 -1
- package/dist/workers/{BaseDecoder.BWYu1W0B.js → BaseDecoder.CTW-vr29.js} +1 -4
- package/dist/workers/BaseDecoder.CTW-vr29.js.map +1 -0
- package/dist/workers/{MP4Demuxer.CFHDkPYc.js → MP4Demuxer.BEa6PLJm.js} +10 -3
- package/dist/workers/{MP4Demuxer.CFHDkPYc.js.map → MP4Demuxer.BEa6PLJm.js.map} +1 -1
- package/dist/workers/stages/compose/{video-compose.worker.M5uomNVr.js → video-compose.worker.DHQ8B105.js} +260 -83
- package/dist/workers/stages/compose/video-compose.worker.DHQ8B105.js.map +1 -0
- package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js → audio-decode.worker.CP8bXXa4.js} +2 -2
- package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js.map → audio-decode.worker.CP8bXXa4.js.map} +1 -1
- package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js → video-decode.worker.BIspTxgV.js} +2 -2
- package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js.map → video-decode.worker.BIspTxgV.js.map} +1 -1
- package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js → audio-demux.worker._VRQdLdv.js} +2 -2
- package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js.map → audio-demux.worker._VRQdLdv.js.map} +1 -1
- package/dist/workers/stages/demux/{video-demux.worker.D_WeHPkt.js → video-demux.worker.CSkxGtmx.js} +3 -19
- package/dist/workers/stages/demux/video-demux.worker.CSkxGtmx.js.map +1 -0
- package/dist/workers/worker-manifest.json +5 -5
- package/package.json +1 -1
- package/dist/cache/l2/IndexedDBStore.js.map +0 -1
- package/dist/cache/l2/OPFSStore.js +0 -131
- package/dist/cache/l2/OPFSStore.js.map +0 -1
- package/dist/controllers/PreRenderService.d.ts +0 -59
- package/dist/controllers/PreRenderService.d.ts.map +0 -1
- package/dist/controllers/PreRenderService.js +0 -185
- package/dist/controllers/PreRenderService.js.map +0 -1
- package/dist/controllers/PreRenderTaskQueue.d.ts +0 -21
- package/dist/controllers/PreRenderTaskQueue.d.ts.map +0 -1
- package/dist/orchestrator/ClipSessionManager.d.ts +0 -70
- package/dist/orchestrator/ClipSessionManager.d.ts.map +0 -1
- package/dist/orchestrator/ClipSessionManager.js +0 -158
- package/dist/orchestrator/ClipSessionManager.js.map +0 -1
- package/dist/stages/decode/AudioChunkDecoder.js +0 -169
- package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
- package/dist/stages/encode/ClipEncoderManager.d.ts +0 -64
- package/dist/stages/encode/ClipEncoderManager.d.ts.map +0 -1
- package/dist/stages/mux/OPFSWriter.d.ts +0 -46
- package/dist/stages/mux/OPFSWriter.d.ts.map +0 -1
- package/dist/utils/BackpressureAdapter.d.ts +0 -26
- package/dist/utils/BackpressureAdapter.d.ts.map +0 -1
- package/dist/utils/time-utils.js +0 -45
- package/dist/utils/time-utils.js.map +0 -1
- package/dist/workers/BaseDecoder.BWYu1W0B.js.map +0 -1
- package/dist/workers/stages/compose/video-compose.worker.M5uomNVr.js.map +0 -1
- package/dist/workers/stages/demux/video-demux.worker.D_WeHPkt.js.map +0 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { VideoChunkDecoder } from "../stages/decode/VideoChunkDecoder.js";
|
|
2
|
+
import { binarySearchOverlapping } from "../utils/binary-search.js";
|
|
3
|
+
class OnDemandVideoSession {
|
|
4
|
+
/**
|
|
5
|
+
* Static method to decode and cache first frame from extracted GOP chunks
|
|
6
|
+
* Used by ResourceLoader during streaming download for fast cover rendering
|
|
7
|
+
*/
|
|
8
|
+
static async decodeAndCacheFirstFrame(resourceId, chunks, index, clip, cacheManager, fps) {
|
|
9
|
+
if (chunks.length === 0) return;
|
|
10
|
+
const videoTrack = index.tracks.video;
|
|
11
|
+
if (!videoTrack) return;
|
|
12
|
+
try {
|
|
13
|
+
const decoder = new VideoChunkDecoder(`first-frame-${resourceId}`, {
|
|
14
|
+
codec: videoTrack.codec,
|
|
15
|
+
width: videoTrack.width,
|
|
16
|
+
height: videoTrack.height,
|
|
17
|
+
description: videoTrack.description,
|
|
18
|
+
hardwareAcceleration: "prefer-hardware",
|
|
19
|
+
thread: "main"
|
|
20
|
+
});
|
|
21
|
+
try {
|
|
22
|
+
const chunkStream = new ReadableStream({
|
|
23
|
+
start(controller) {
|
|
24
|
+
for (const chunk of chunks) {
|
|
25
|
+
controller.enqueue(chunk);
|
|
26
|
+
}
|
|
27
|
+
controller.close();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const frameStream = chunkStream.pipeThrough(decoder.createStream());
|
|
31
|
+
const reader = frameStream.getReader();
|
|
32
|
+
const { value } = await reader.read();
|
|
33
|
+
reader.releaseLock();
|
|
34
|
+
if (value) {
|
|
35
|
+
const frameDuration = Math.round(1e6 / fps);
|
|
36
|
+
cacheManager.addFrame(
|
|
37
|
+
value,
|
|
38
|
+
clip.id,
|
|
39
|
+
frameDuration,
|
|
40
|
+
clip.trackId ?? "main",
|
|
41
|
+
clip.startUs
|
|
42
|
+
// globalTimeUs = clipStartUs (first frame, relativeTime=0)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
} finally {
|
|
46
|
+
await decoder.close();
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.warn("[OnDemandVideoSession] Failed to decode first frame:", error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
clipId;
|
|
53
|
+
resourceId;
|
|
54
|
+
resourceCache;
|
|
55
|
+
mp4IndexCache;
|
|
56
|
+
cacheManager;
|
|
57
|
+
compositionModel;
|
|
58
|
+
fps;
|
|
59
|
+
globalTimeUs;
|
|
60
|
+
targetTimeUs;
|
|
61
|
+
decoder = null;
|
|
62
|
+
isDisposed = false;
|
|
63
|
+
decodedFrames = [];
|
|
64
|
+
constructor(config) {
|
|
65
|
+
this.clipId = config.clipId;
|
|
66
|
+
this.resourceId = config.resourceId;
|
|
67
|
+
this.resourceCache = config.resourceCache;
|
|
68
|
+
this.mp4IndexCache = config.mp4IndexCache;
|
|
69
|
+
this.cacheManager = config.cacheManager;
|
|
70
|
+
this.compositionModel = config.compositionModel;
|
|
71
|
+
this.fps = config.fps;
|
|
72
|
+
this.globalTimeUs = config.globalTimeUs;
|
|
73
|
+
this.targetTimeUs = config.targetTimeUs;
|
|
74
|
+
}
|
|
75
|
+
static async create(config) {
|
|
76
|
+
const session = new OnDemandVideoSession(config);
|
|
77
|
+
await session.init();
|
|
78
|
+
return session;
|
|
79
|
+
}
|
|
80
|
+
async init() {
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Decode a window range, write raw frames to L1 cache
|
|
84
|
+
*/
|
|
85
|
+
async decodeWindow(startUs, endUs) {
|
|
86
|
+
if (this.isDisposed) {
|
|
87
|
+
throw new Error("Session already disposed");
|
|
88
|
+
}
|
|
89
|
+
const resource = this.compositionModel.getResource(this.resourceId);
|
|
90
|
+
if (resource?.type === "image") {
|
|
91
|
+
const image = this.cacheManager.imageBitmapCache.get(resource.id);
|
|
92
|
+
if (image) {
|
|
93
|
+
const frame = new VideoFrame(image, {
|
|
94
|
+
timestamp: startUs,
|
|
95
|
+
duration: endUs - startUs
|
|
96
|
+
});
|
|
97
|
+
this.cacheManager.addFrame(
|
|
98
|
+
frame,
|
|
99
|
+
this.clipId,
|
|
100
|
+
endUs - startUs,
|
|
101
|
+
this.compositionModel.mainTrackId,
|
|
102
|
+
this.globalTimeUs
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const index = this.mp4IndexCache.get(this.resourceId);
|
|
108
|
+
if (!index) {
|
|
109
|
+
throw new Error(`No index found for resource ${this.resourceId}`);
|
|
110
|
+
}
|
|
111
|
+
const gopWindow = this.calculateGOPRangesForWindow(index, startUs, endUs);
|
|
112
|
+
if (gopWindow.gops.length === 0) {
|
|
113
|
+
console.warn("[OnDemandVideoSession] No GOP ranges for window", startUs, endUs);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const gopData = await this.resourceCache.readRange(
|
|
117
|
+
this.resourceId,
|
|
118
|
+
gopWindow.byteStart,
|
|
119
|
+
gopWindow.byteEnd
|
|
120
|
+
);
|
|
121
|
+
const chunks = await this.demuxGOPData(gopData, index, gopWindow);
|
|
122
|
+
await this.decodeChunks(chunks, index);
|
|
123
|
+
await this.cacheDecodedFrames(startUs, endUs);
|
|
124
|
+
}
|
|
125
|
+
calculateGOPRangesForWindow(index, startUs, endUs) {
|
|
126
|
+
if (!index.tracks.video) {
|
|
127
|
+
console.warn("[OnDemandVideoSession] No video track in index");
|
|
128
|
+
return { gops: [], byteStart: 0, byteEnd: 0 };
|
|
129
|
+
}
|
|
130
|
+
const { gopIndex, samples } = index.tracks.video;
|
|
131
|
+
const nearestKeyframe = this.mp4IndexCache.getNearestKeyframe(this.resourceId, startUs);
|
|
132
|
+
const decodeStartUs = nearestKeyframe?.timestamp ?? startUs;
|
|
133
|
+
const overlappingGOPs = binarySearchOverlapping(gopIndex, decodeStartUs, endUs, (gop, idx) => {
|
|
134
|
+
const nextGOP = gopIndex[idx + 1];
|
|
135
|
+
return {
|
|
136
|
+
start: gop.startTimeUs,
|
|
137
|
+
end: nextGOP ? nextGOP.startTimeUs : Infinity
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
if (overlappingGOPs.length === 0) {
|
|
141
|
+
console.warn("[OnDemandVideoSession] No GOP ranges for window", startUs, endUs);
|
|
142
|
+
return { gops: [], byteStart: 0, byteEnd: 0 };
|
|
143
|
+
}
|
|
144
|
+
let byteStart = Infinity;
|
|
145
|
+
let byteEnd = 0;
|
|
146
|
+
for (const gop of overlappingGOPs) {
|
|
147
|
+
const startSample = samples[gop.keyframeSampleIndex];
|
|
148
|
+
const endSampleIndex = gop.keyframeSampleIndex + gop.sampleCount - 1;
|
|
149
|
+
const endSample = samples[endSampleIndex];
|
|
150
|
+
if (startSample && endSample) {
|
|
151
|
+
byteStart = Math.min(byteStart, startSample.byteOffset);
|
|
152
|
+
byteEnd = Math.max(byteEnd, endSample.byteOffset + endSample.byteLength);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { gops: overlappingGOPs, byteStart, byteEnd };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extract video chunks from GOP data
|
|
159
|
+
*
|
|
160
|
+
* Directly use GOP sample indices to slice the samples array.
|
|
161
|
+
* This is O(k) where k is the number of samples in the window,
|
|
162
|
+
* vs O(n×m) for the old approach (n=all samples, m=GOP count).
|
|
163
|
+
*/
|
|
164
|
+
async demuxGOPData(data, index, gopWindow) {
|
|
165
|
+
const videoTrack = index.tracks.video;
|
|
166
|
+
if (!videoTrack) {
|
|
167
|
+
throw new Error("No video track in index");
|
|
168
|
+
}
|
|
169
|
+
const { samples } = videoTrack;
|
|
170
|
+
const chunks = [];
|
|
171
|
+
const dataView = new Uint8Array(data);
|
|
172
|
+
const baseByteOffset = gopWindow.byteStart;
|
|
173
|
+
for (const gop of gopWindow.gops) {
|
|
174
|
+
const startIdx = gop.keyframeSampleIndex;
|
|
175
|
+
const endIdx = startIdx + gop.sampleCount;
|
|
176
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
177
|
+
const sample = samples[i];
|
|
178
|
+
if (!sample) continue;
|
|
179
|
+
const relativeOffset = sample.byteOffset - baseByteOffset;
|
|
180
|
+
if (relativeOffset < 0 || relativeOffset + sample.byteLength > data.byteLength) {
|
|
181
|
+
console.warn("[OnDemandVideoSession] Sample outside buffer:", {
|
|
182
|
+
sampleOffset: sample.byteOffset,
|
|
183
|
+
sampleLength: sample.byteLength,
|
|
184
|
+
baseOffset: baseByteOffset,
|
|
185
|
+
relativeOffset,
|
|
186
|
+
bufferLength: data.byteLength
|
|
187
|
+
});
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const sampleData = dataView.slice(relativeOffset, relativeOffset + sample.byteLength);
|
|
191
|
+
const chunk = new EncodedVideoChunk({
|
|
192
|
+
type: sample.isKeyframe ? "key" : "delta",
|
|
193
|
+
timestamp: sample.timestamp,
|
|
194
|
+
duration: sample.duration,
|
|
195
|
+
data: sampleData
|
|
196
|
+
});
|
|
197
|
+
chunks.push(chunk);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return chunks;
|
|
201
|
+
}
|
|
202
|
+
async decodeChunks(chunks, index) {
|
|
203
|
+
const videoTrack = index.tracks.video;
|
|
204
|
+
if (!videoTrack) {
|
|
205
|
+
throw new Error("No video track in index");
|
|
206
|
+
}
|
|
207
|
+
this.decoder = new VideoChunkDecoder(`ondemand-${this.clipId}`, {
|
|
208
|
+
codec: videoTrack.codec,
|
|
209
|
+
width: videoTrack.width,
|
|
210
|
+
height: videoTrack.height,
|
|
211
|
+
description: videoTrack.description,
|
|
212
|
+
hardwareAcceleration: "prefer-hardware",
|
|
213
|
+
thread: "main"
|
|
214
|
+
});
|
|
215
|
+
const chunkStream = new ReadableStream({
|
|
216
|
+
start: (controller) => {
|
|
217
|
+
for (const chunk of chunks) {
|
|
218
|
+
controller.enqueue(chunk);
|
|
219
|
+
}
|
|
220
|
+
controller.close();
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
const frameStream = chunkStream.pipeThrough(this.decoder.createStream());
|
|
224
|
+
const reader = frameStream.getReader();
|
|
225
|
+
try {
|
|
226
|
+
while (true) {
|
|
227
|
+
const { done, value } = await reader.read();
|
|
228
|
+
if (done) break;
|
|
229
|
+
if (value) {
|
|
230
|
+
this.decodedFrames.push(value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} finally {
|
|
234
|
+
reader.releaseLock();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async cacheDecodedFrames(startUs, endUs) {
|
|
238
|
+
const framesToCache = this.decodedFrames.filter((f) => {
|
|
239
|
+
return f.timestamp >= startUs && f.timestamp < endUs;
|
|
240
|
+
});
|
|
241
|
+
for (const frame of framesToCache) {
|
|
242
|
+
try {
|
|
243
|
+
const originalTimestamp = frame.timestamp;
|
|
244
|
+
const originalDuration = frame.duration ?? Math.round(1e6 / this.fps);
|
|
245
|
+
const frameGlobalTime = this.globalTimeUs + (originalTimestamp - this.targetTimeUs);
|
|
246
|
+
this.cacheManager.addFrame(
|
|
247
|
+
frame,
|
|
248
|
+
this.clipId,
|
|
249
|
+
originalDuration,
|
|
250
|
+
this.compositionModel.mainTrackId,
|
|
251
|
+
frameGlobalTime
|
|
252
|
+
);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error("[OnDemandVideoSession] Cache error:", error);
|
|
255
|
+
frame.close();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (const frame of this.decodedFrames) {
|
|
259
|
+
if (!framesToCache.includes(frame)) {
|
|
260
|
+
frame.close();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
this.decodedFrames = [];
|
|
264
|
+
}
|
|
265
|
+
async dispose() {
|
|
266
|
+
if (this.isDisposed) return;
|
|
267
|
+
if (this.decoder && this.decoder.state !== "closed") {
|
|
268
|
+
await this.decoder.close();
|
|
269
|
+
this.decoder = null;
|
|
270
|
+
}
|
|
271
|
+
for (const frame of this.decodedFrames) {
|
|
272
|
+
frame.close();
|
|
273
|
+
}
|
|
274
|
+
this.decodedFrames = [];
|
|
275
|
+
this.isDisposed = true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
export {
|
|
279
|
+
OnDemandVideoSession
|
|
280
|
+
};
|
|
281
|
+
//# sourceMappingURL=OnDemandVideoSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OnDemandVideoSession.js","sources":["../../src/orchestrator/OnDemandVideoSession.ts"],"sourcesContent":["import type { CacheManager } from '../cache/CacheManager';\nimport type { ResourceCache } from '../cache/resource/ResourceCache';\nimport type { MP4IndexCache } from '../cache/resource/MP4IndexCache';\nimport type { MP4Index, GOP } from '../stages/demux/types';\nimport type { TimeUs } from '../model/types';\nimport type { CompositionModel, Clip } from '../model';\nimport { VideoChunkDecoder } from '../stages/decode/VideoChunkDecoder';\nimport { binarySearchOverlapping } from '../utils/binary-search';\n\ninterface GOPWindowResult {\n gops: GOP[];\n byteStart: number;\n byteEnd: number;\n}\n\ninterface OnDemandVideoSessionConfig {\n clipId: string;\n resourceId: string;\n targetTimeUs: TimeUs;\n globalTimeUs: TimeUs;\n resourceCache: ResourceCache;\n mp4IndexCache: MP4IndexCache;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n fps: number;\n}\n\n/**\n * OnDemandVideoSession - Main-thread on-demand decoder\n *\n * Strategy:\n * 1. Read GOP range from OPFS\n * 2. Demux using MP4Box (main thread)\n * 3. Decode using VideoDecoder (main thread)\n * 4. Write RAW VideoFrames (YUV) to L1 cache\n * 5. Dispose immediately after window completes\n *\n * Why main thread?\n * - Window is small (±2s, ~60-120 frames)\n * - Worker overhead (10-50ms) is significant for small workloads\n * - Decode is fast enough\n */\nexport class OnDemandVideoSession {\n /**\n * Static method to decode and cache first frame from extracted GOP chunks\n * Used by ResourceLoader during streaming download for fast cover rendering\n */\n static async decodeAndCacheFirstFrame(\n resourceId: string,\n chunks: EncodedVideoChunk[],\n index: MP4Index,\n clip: Clip,\n cacheManager: CacheManager,\n fps: number\n ): Promise<void> {\n if (chunks.length === 0) return;\n\n const videoTrack = index.tracks.video;\n if (!videoTrack) return;\n\n try {\n // Create temporary decoder\n const decoder = new VideoChunkDecoder(`first-frame-${resourceId}`, {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n hardwareAcceleration: 'prefer-hardware',\n thread: 'main',\n });\n\n try {\n // Create chunk stream\n const chunkStream = new ReadableStream<EncodedVideoChunk>({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(chunk);\n }\n controller.close();\n },\n });\n\n // Decode\n const frameStream = chunkStream.pipeThrough(decoder.createStream());\n\n // Read first frame only\n const reader = frameStream.getReader();\n const { value } = await reader.read();\n reader.releaseLock();\n\n if (value) {\n // Use CacheManager's unified interface to ensure event notification\n const frameDuration = Math.round(1_000_000 / fps);\n\n cacheManager.addFrame(\n value,\n clip.id,\n frameDuration,\n clip.trackId ?? 'main',\n clip.startUs // globalTimeUs = clipStartUs (first frame, relativeTime=0)\n );\n }\n } finally {\n await decoder.close();\n }\n } catch (error) {\n console.warn('[OnDemandVideoSession] Failed to decode first frame:', error);\n // Don't throw - this is a best-effort optimization\n }\n }\n private readonly clipId: string;\n private readonly resourceId: string;\n private readonly resourceCache: ResourceCache;\n private readonly mp4IndexCache: MP4IndexCache;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly fps: number;\n private readonly globalTimeUs: TimeUs;\n private readonly targetTimeUs: TimeUs;\n\n private decoder: VideoChunkDecoder | null = null;\n private isDisposed = false;\n private decodedFrames: VideoFrame[] = [];\n\n private constructor(config: OnDemandVideoSessionConfig) {\n this.clipId = config.clipId;\n this.resourceId = config.resourceId;\n this.resourceCache = config.resourceCache;\n this.mp4IndexCache = config.mp4IndexCache;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.fps = config.fps;\n this.globalTimeUs = config.globalTimeUs;\n this.targetTimeUs = config.targetTimeUs;\n }\n\n static async create(config: OnDemandVideoSessionConfig): Promise<OnDemandVideoSession> {\n const session = new OnDemandVideoSession(config);\n await session.init();\n return session;\n }\n\n private async init(): Promise<void> {\n // No special initialization needed for now\n }\n\n /**\n * Decode a window range, write raw frames to L1 cache\n */\n async decodeWindow(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n if (this.isDisposed) {\n throw new Error('Session already disposed');\n }\n\n // Check if resource is an image (no MP4 index)\n const resource = this.compositionModel.getResource(this.resourceId);\n if (resource?.type === 'image') {\n // Image clip: handled by direct ImageBitmap access in PlaybackController\n // No need to decode anything here\n const image = this.cacheManager.imageBitmapCache.get(resource.id);\n if (image) {\n const frame = new VideoFrame(image, {\n timestamp: startUs,\n duration: endUs - startUs,\n });\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n endUs - startUs,\n this.compositionModel.mainTrackId,\n this.globalTimeUs\n );\n }\n return;\n }\n\n // Video clip: decode from OPFS\n // Get MP4 index\n const index = this.mp4IndexCache.get(this.resourceId);\n if (!index) {\n throw new Error(`No index found for resource ${this.resourceId}`);\n }\n\n // Calculate GOP ranges needed (with binary search optimization)\n const gopWindow = this.calculateGOPRangesForWindow(index, startUs, endUs);\n if (gopWindow.gops.length === 0) {\n console.warn('[OnDemandVideoSession] No GOP ranges for window', startUs, endUs);\n return;\n }\n\n // Read GOP data from OPFS (merged byte range)\n const gopData = await this.resourceCache.readRange(\n this.resourceId,\n gopWindow.byteStart,\n gopWindow.byteEnd\n );\n\n // Extract chunks from GOP data (direct sample index slicing)\n const chunks = await this.demuxGOPData(gopData, index, gopWindow);\n\n // Decode chunks to frames\n await this.decodeChunks(chunks, index);\n\n // Write frames to L1\n await this.cacheDecodedFrames(startUs, endUs);\n }\n\n private calculateGOPRangesForWindow(\n index: MP4Index,\n startUs: TimeUs,\n endUs: TimeUs\n ): GOPWindowResult {\n if (!index.tracks.video) {\n console.warn('[OnDemandVideoSession] No video track in index');\n return { gops: [], byteStart: 0, byteEnd: 0 };\n }\n\n const { gopIndex, samples } = index.tracks.video;\n\n // Find GOP containing startUs (or the nearest keyframe before it)\n const nearestKeyframe = this.mp4IndexCache.getNearestKeyframe(this.resourceId, startUs);\n const decodeStartUs = nearestKeyframe?.timestamp ?? startUs;\n\n // Use binary search to find all overlapping GOPs\n const overlappingGOPs = binarySearchOverlapping(gopIndex, decodeStartUs, endUs, (gop, idx) => {\n const nextGOP = gopIndex[idx + 1];\n return {\n start: gop.startTimeUs,\n end: nextGOP ? nextGOP.startTimeUs : Infinity,\n };\n });\n\n if (overlappingGOPs.length === 0) {\n console.warn('[OnDemandVideoSession] No GOP ranges for window', startUs, endUs);\n return { gops: [], byteStart: 0, byteEnd: 0 };\n }\n\n // Calculate merged byte range for OPFS read\n let byteStart = Infinity;\n let byteEnd = 0;\n\n for (const gop of overlappingGOPs) {\n const startSample = samples[gop.keyframeSampleIndex];\n const endSampleIndex = gop.keyframeSampleIndex + gop.sampleCount - 1;\n const endSample = samples[endSampleIndex];\n\n if (startSample && endSample) {\n byteStart = Math.min(byteStart, startSample.byteOffset);\n byteEnd = Math.max(byteEnd, endSample.byteOffset + endSample.byteLength);\n }\n }\n\n return { gops: overlappingGOPs, byteStart, byteEnd };\n }\n\n /**\n * Extract video chunks from GOP data\n *\n * Directly use GOP sample indices to slice the samples array.\n * This is O(k) where k is the number of samples in the window,\n * vs O(n×m) for the old approach (n=all samples, m=GOP count).\n */\n private async demuxGOPData(\n data: ArrayBuffer,\n index: MP4Index,\n gopWindow: GOPWindowResult\n ): Promise<EncodedVideoChunk[]> {\n const videoTrack = index.tracks.video;\n if (!videoTrack) {\n throw new Error('No video track in index');\n }\n\n const { samples } = videoTrack;\n const chunks: EncodedVideoChunk[] = [];\n const dataView = new Uint8Array(data);\n const baseByteOffset = gopWindow.byteStart;\n\n // Direct sample index slicing - iterate only samples in the window\n for (const gop of gopWindow.gops) {\n const startIdx = gop.keyframeSampleIndex;\n const endIdx = startIdx + gop.sampleCount;\n\n // Extract samples for this GOP (direct array slice)\n for (let i = startIdx; i < endIdx; i++) {\n const sample = samples[i];\n if (!sample) continue;\n\n // Calculate relative offset within the data buffer\n const relativeOffset = sample.byteOffset - baseByteOffset;\n\n // Validate offset is within buffer\n if (relativeOffset < 0 || relativeOffset + sample.byteLength > data.byteLength) {\n console.warn('[OnDemandVideoSession] Sample outside buffer:', {\n sampleOffset: sample.byteOffset,\n sampleLength: sample.byteLength,\n baseOffset: baseByteOffset,\n relativeOffset,\n bufferLength: data.byteLength,\n });\n continue;\n }\n\n // Extract sample data\n const sampleData = dataView.slice(relativeOffset, relativeOffset + sample.byteLength);\n\n // Create EncodedVideoChunk\n const chunk = new EncodedVideoChunk({\n type: sample.isKeyframe ? 'key' : 'delta',\n timestamp: sample.timestamp,\n duration: sample.duration,\n data: sampleData,\n });\n\n chunks.push(chunk);\n }\n }\n\n return chunks;\n }\n\n private async decodeChunks(chunks: EncodedVideoChunk[], index: MP4Index): Promise<void> {\n const videoTrack = index.tracks.video;\n if (!videoTrack) {\n throw new Error('No video track in index');\n }\n\n // Create decoder (reuse VideoChunkDecoder like decodeFromL2)\n this.decoder = new VideoChunkDecoder(`ondemand-${this.clipId}`, {\n codec: videoTrack.codec,\n width: videoTrack.width,\n height: videoTrack.height,\n description: videoTrack.description,\n hardwareAcceleration: 'prefer-hardware',\n thread: 'main',\n });\n\n // Create stream from chunks\n const chunkStream = new ReadableStream<EncodedVideoChunk>({\n start: (controller) => {\n for (const chunk of chunks) {\n controller.enqueue(chunk);\n }\n controller.close();\n },\n });\n\n // Pipe through decoder\n const frameStream = chunkStream.pipeThrough(this.decoder.createStream());\n\n // Collect decoded frames\n const reader = frameStream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n this.decodedFrames.push(value);\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n private async cacheDecodedFrames(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n // Filter frames within window (include boundary frames)\n // Use original timestamp (no quantization)\n const framesToCache = this.decodedFrames.filter((f) => {\n // Exclude frames at window end boundary\n return f.timestamp >= startUs && f.timestamp < endUs;\n });\n\n for (const frame of framesToCache) {\n try {\n // Use original timestamp and duration (preserve source frame rate)\n const originalTimestamp = frame.timestamp;\n const originalDuration = frame.duration ?? Math.round(1_000_000 / this.fps);\n\n // Calculate global timestamp using original value\n const frameGlobalTime = this.globalTimeUs + (originalTimestamp - this.targetTimeUs);\n\n // Write RAW video frame to L1 with original timestamp, duration and global time\n // NOTE: addComposedFrame name is legacy, but now we store raw frames\n this.cacheManager.addFrame(\n frame,\n this.clipId,\n originalDuration,\n this.compositionModel.mainTrackId,\n frameGlobalTime\n );\n\n // Don't close frame here - it's now owned by L1 cache\n // (CacheManager.addComposedFrame clones it or takes ownership)\n } catch (error) {\n console.error('[OnDemandVideoSession] Cache error:', error);\n frame.close();\n }\n }\n\n // Clean up decoded frames that weren't cached\n for (const frame of this.decodedFrames) {\n if (!framesToCache.includes(frame)) {\n frame.close();\n }\n }\n this.decodedFrames = [];\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n\n // Close decoder (check state to avoid closing already-closed decoder)\n if (this.decoder && this.decoder.state !== 'closed') {\n await this.decoder.close();\n this.decoder = null;\n }\n\n // Clean up any remaining frames\n for (const frame of this.decodedFrames) {\n frame.close();\n }\n this.decodedFrames = [];\n\n this.isDisposed = true;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,6 @@ import { WorkerPool } from '../worker/WorkerPool';
|
|
|
3
3
|
import { ResourceLoader } from '../stages/load/ResourceLoader';
|
|
4
4
|
import { CacheManager } from '../cache/CacheManager';
|
|
5
5
|
import { IOrchestrator, OrchestratorConfig, RenderFrameOptions } from './types';
|
|
6
|
-
import { WorkerStatus, WorkerType } from '../worker/types';
|
|
7
6
|
import { CompositionModel, CompositionPatch, TimeUs, RcFrame } from '../model';
|
|
8
7
|
import { SetCompositionModelOptions } from '../model/types';
|
|
9
8
|
import { EventPayloadMap } from '../event/events';
|
|
@@ -11,6 +10,7 @@ import { CompositionPlanner } from './CompositionPlanner';
|
|
|
11
10
|
import { GlobalAudioSession } from './GlobalAudioSession';
|
|
12
11
|
import { MuxManager } from '../stages/mux/MuxManager';
|
|
13
12
|
import { ExportOptions } from '../types';
|
|
13
|
+
import { ExportScheduler } from './ExportScheduler';
|
|
14
14
|
|
|
15
15
|
export declare class Orchestrator implements IOrchestrator {
|
|
16
16
|
workers: WorkerPool;
|
|
@@ -21,16 +21,15 @@ export declare class Orchestrator implements IOrchestrator {
|
|
|
21
21
|
planner: CompositionPlanner;
|
|
22
22
|
audioSession: GlobalAudioSession;
|
|
23
23
|
muxManager: MuxManager;
|
|
24
|
-
|
|
24
|
+
exportScheduler: ExportScheduler;
|
|
25
25
|
private isInitialized;
|
|
26
26
|
private config;
|
|
27
|
-
private clipSessionManager;
|
|
28
|
-
private currentClipId;
|
|
29
27
|
private ensureCacheDebounceTimer;
|
|
30
|
-
private
|
|
28
|
+
private currentClipId;
|
|
31
29
|
readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;
|
|
32
30
|
constructor(config: OrchestratorConfig);
|
|
33
|
-
|
|
31
|
+
private setupResourceFirstFrameHandler;
|
|
32
|
+
private setupPreloadHandlers;
|
|
34
33
|
initialize(): Promise<void>;
|
|
35
34
|
on<K extends keyof EventPayloadMap>(event: K, handler: (payload: EventPayloadMap[K]) => void): void;
|
|
36
35
|
off<K extends keyof EventPayloadMap>(event: K, handler: (payload: EventPayloadMap[K]) => void): void;
|
|
@@ -38,17 +37,13 @@ export declare class Orchestrator implements IOrchestrator {
|
|
|
38
37
|
setCompositionModel(model: CompositionModel, options?: SetCompositionModelOptions): Promise<void>;
|
|
39
38
|
applyPatch(patch: CompositionPatch): Promise<void>;
|
|
40
39
|
private handleResourceStateChange;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
private ensureAudioFromL2;
|
|
44
|
-
private decodeFromL2;
|
|
40
|
+
getFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null>;
|
|
41
|
+
private preheatNextClip;
|
|
45
42
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* @param timeUs - Target time for cache window
|
|
49
|
-
* @param immediate - Skip debounce if true (used for initial load)
|
|
43
|
+
* Compose frame from OPFS resource (on-demand decoding)
|
|
44
|
+
* This is the new path for long clips with window caching
|
|
50
45
|
*/
|
|
51
|
-
|
|
46
|
+
private decodeFromResource;
|
|
52
47
|
/**
|
|
53
48
|
* Wait for clip cache to be ready for playback
|
|
54
49
|
* Returns true if minimum cache is ready, false if timeout
|
|
@@ -57,10 +52,20 @@ export declare class Orchestrator implements IOrchestrator {
|
|
|
57
52
|
minFrameCount?: number;
|
|
58
53
|
timeoutMs?: number;
|
|
59
54
|
}): Promise<boolean>;
|
|
60
|
-
renderClipForL2(clipId: string): Promise<boolean>;
|
|
61
|
-
private createSession;
|
|
62
55
|
dispose(): Promise<void>;
|
|
63
56
|
private buildWorkerConfigs;
|
|
64
57
|
export(model: CompositionModel, options: ExportOptions): Promise<Blob>;
|
|
58
|
+
/**
|
|
59
|
+
* Get render state for real-time composition
|
|
60
|
+
* Returns layers ready for VideoComposer
|
|
61
|
+
*/
|
|
62
|
+
getRenderState(timeUs: TimeUs, options?: RenderFrameOptions): Promise<{
|
|
63
|
+
layers: any[];
|
|
64
|
+
transition?: any;
|
|
65
|
+
} | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Materialize a serialized layer plan into concrete Layer
|
|
68
|
+
*/
|
|
69
|
+
private materializeLayer;
|
|
65
70
|
}
|
|
66
71
|
//# sourceMappingURL=Orchestrator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,
|
|
1
|
+
{"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"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"}
|