@meframe/core 0.0.4 → 0.0.6
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 +16 -7
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +106 -86
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +27 -10
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +92 -30
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/L2Cache.d.ts +31 -2
- package/dist/cache/L2Cache.d.ts.map +1 -1
- package/dist/cache/L2Cache.js +242 -44
- package/dist/cache/L2Cache.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +1 -1
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/controllers/PreRenderService.d.ts +31 -4
- package/dist/controllers/PreRenderService.d.ts.map +1 -1
- package/dist/controllers/PreRenderService.js +130 -10
- package/dist/controllers/PreRenderService.js.map +1 -1
- package/dist/event/events.d.ts +12 -3
- 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 +1 -1
- package/dist/model/CompositionModel.js +1 -1
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/patch.d.ts +1 -1
- package/dist/model/patch.js.map +1 -1
- package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +1858 -0
- package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
- package/dist/orchestrator/ClipSessionManager.d.ts +1 -2
- package/dist/orchestrator/ClipSessionManager.d.ts.map +1 -1
- package/dist/orchestrator/ClipSessionManager.js +1 -0
- package/dist/orchestrator/ClipSessionManager.js.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts +1 -1
- package/dist/orchestrator/CompositionPlanner.js +1 -1
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +10 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +83 -38
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +11 -4
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +70 -28
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.d.ts +28 -1
- package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.js +133 -5
- package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
- package/dist/stages/compose/VideoComposer.d.ts +1 -0
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/stages/encode/AudioChunkEncoder.d.ts +2 -1
- package/dist/stages/encode/AudioChunkEncoder.d.ts.map +1 -1
- package/dist/stages/encode/AudioChunkEncoder.js +41 -0
- package/dist/stages/encode/AudioChunkEncoder.js.map +1 -0
- package/dist/stages/encode/BaseEncoder.d.ts +7 -3
- package/dist/stages/encode/BaseEncoder.d.ts.map +1 -1
- package/dist/stages/encode/BaseEncoder.js +173 -0
- package/dist/stages/encode/BaseEncoder.js.map +1 -0
- package/dist/stages/encode/ClipEncoderManager.d.ts +64 -0
- package/dist/stages/encode/ClipEncoderManager.d.ts.map +1 -0
- package/dist/stages/encode/index.d.ts +1 -1
- package/dist/stages/encode/index.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +17 -12
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/TaskManager.d.ts +1 -1
- package/dist/stages/load/TaskManager.d.ts.map +1 -1
- package/dist/stages/load/TaskManager.js +2 -2
- package/dist/stages/load/TaskManager.js.map +1 -1
- package/dist/stages/load/types.d.ts +2 -2
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.d.ts +19 -38
- package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.js +60 -0
- package/dist/stages/mux/MP4Muxer.js.map +1 -0
- package/dist/stages/mux/MuxManager.d.ts +27 -0
- package/dist/stages/mux/MuxManager.d.ts.map +1 -0
- package/dist/stages/mux/MuxManager.js +148 -0
- package/dist/stages/mux/MuxManager.js.map +1 -0
- package/dist/stages/mux/index.d.ts +1 -0
- package/dist/stages/mux/index.d.ts.map +1 -1
- package/dist/stages/mux/types.d.ts +1 -0
- package/dist/stages/mux/types.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/worker/WorkerPool.d.ts.map +1 -1
- package/dist/worker/WorkerPool.js +2 -4
- package/dist/worker/WorkerPool.js.map +1 -1
- package/dist/worker/types.d.ts +1 -4
- package/dist/worker/types.d.ts.map +1 -1
- package/dist/worker/types.js +0 -3
- package/dist/worker/types.js.map +1 -1
- package/dist/worker/worker-event-whitelist.d.ts.map +1 -1
- package/dist/workers/MP4Demuxer.js +7049 -6
- package/dist/workers/MP4Demuxer.js.map +1 -1
- package/dist/workers/WorkerChannel.js +0 -3
- package/dist/workers/WorkerChannel.js.map +1 -1
- package/dist/workers/stages/compose/video-compose.worker.js +25 -14
- package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
- package/dist/workers/stages/decode/decode.worker.js +37 -28
- package/dist/workers/stages/decode/decode.worker.js.map +1 -1
- package/dist/workers/stages/demux/audio-demux.worker.js +4 -4
- package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -1
- package/dist/workers/stages/demux/video-demux.worker.js +9 -7
- package/dist/workers/stages/demux/video-demux.worker.js.map +1 -1
- package/dist/workers/stages/encode/encode.worker.js +192 -196
- package/dist/workers/stages/encode/encode.worker.js.map +1 -1
- package/package.json +2 -1
- package/dist/stages/encode/EncoderPool.d.ts +0 -28
- package/dist/stages/encode/EncoderPool.d.ts.map +0 -1
- package/dist/workers/mp4box.all.js +0 -7049
- package/dist/workers/mp4box.all.js.map +0 -1
- package/dist/workers/stages/mux/mux.worker.js +0 -501
- package/dist/workers/stages/mux/mux.worker.js.map +0 -1
package/dist/Meframe.d.ts
CHANGED
|
@@ -48,20 +48,29 @@ export declare class Meframe {
|
|
|
48
48
|
}): PreviewHandle;
|
|
49
49
|
/**
|
|
50
50
|
* Export the composition
|
|
51
|
+
* Uses L2 cache for fast export, muxes in main thread
|
|
51
52
|
*/
|
|
52
|
-
export(options: ExportOptions): Promise<
|
|
53
|
+
export(options: ExportOptions): Promise<Blob>;
|
|
53
54
|
/**
|
|
54
|
-
*
|
|
55
|
+
* Check export readiness
|
|
56
|
+
* Returns coverage info for L2 cache
|
|
55
57
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
getExportReadiness(): Promise<{
|
|
59
|
+
ready: boolean;
|
|
60
|
+
totalClips: number;
|
|
61
|
+
cachedClips: number;
|
|
62
|
+
missingClips: string[];
|
|
63
|
+
coverage: number;
|
|
64
|
+
}>;
|
|
61
65
|
/**
|
|
62
66
|
* Get current playback time
|
|
63
67
|
*/
|
|
64
68
|
getCurrentTime(): number;
|
|
69
|
+
/**
|
|
70
|
+
* Clear all L1/L2 cache data
|
|
71
|
+
* Useful for debugging or forcing re-render
|
|
72
|
+
*/
|
|
73
|
+
clearCache(): Promise<void>;
|
|
65
74
|
/**
|
|
66
75
|
* Clean up and destroy the instance
|
|
67
76
|
*/
|
package/dist/Meframe.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Meframe.d.ts","sourceRoot":"","sources":["../src/Meframe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"Meframe.d.ts","sourceRoot":"","sources":["../src/Meframe.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpE,qBAAa,OAAO;IAClB,wDAAwD;IACxD,KAAK,EAAE,YAAY,CAAU;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,2DAA2D;IAC3D,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACtC,oCAAoC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;IAExE,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAC,CAAsC;IAErD,OAAO;IAiBP;;OAEG;WACU,MAAM,CAAC,MAAM,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQjE;;OAEG;YACW,UAAU;IAyBxB;;OAEG;IACG,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBxF;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxD;;OAEG;IACH,YAAY,CAAC,OAAO,CAAC,EAAE;QACrB,MAAM,CAAC,EAAE,iBAAiB,GAAG,eAAe,CAAC;QAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,aAAa;IAkDjB;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA0FnD;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IAmCF;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAejC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB9B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAYhB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,WAAW;CAWpB"}
|
package/dist/Meframe.js
CHANGED
|
@@ -114,11 +114,25 @@ class Meframe {
|
|
|
114
114
|
}
|
|
115
115
|
if (!this.preRenderService) {
|
|
116
116
|
this.preRenderService = new PreRenderService(this.orchestrator, this.events);
|
|
117
|
+
this.preRenderService.start();
|
|
117
118
|
}
|
|
119
|
+
this.playbackController.on(MeframeEvent.PlaybackTimeUpdate, (payload) => {
|
|
120
|
+
this.preRenderService?.updatePlaybackTime(payload.timeUs);
|
|
121
|
+
});
|
|
122
|
+
this.playbackController.on(MeframeEvent.PlaybackPlay, () => {
|
|
123
|
+
this.preRenderService?.setPlaybackActive(true);
|
|
124
|
+
});
|
|
125
|
+
this.playbackController.on(MeframeEvent.PlaybackPause, () => {
|
|
126
|
+
this.preRenderService?.setPlaybackActive(false);
|
|
127
|
+
});
|
|
128
|
+
this.playbackController.on(MeframeEvent.PlaybackStop, () => {
|
|
129
|
+
this.preRenderService?.setPlaybackActive(false);
|
|
130
|
+
});
|
|
118
131
|
return this.playbackController;
|
|
119
132
|
}
|
|
120
133
|
/**
|
|
121
134
|
* Export the composition
|
|
135
|
+
* Uses L2 cache for fast export, muxes in main thread
|
|
122
136
|
*/
|
|
123
137
|
async export(options) {
|
|
124
138
|
this.ensureReady();
|
|
@@ -128,107 +142,102 @@ class Meframe {
|
|
|
128
142
|
if (!model) {
|
|
129
143
|
throw new Error("No composition model set");
|
|
130
144
|
}
|
|
131
|
-
const
|
|
132
|
-
|
|
145
|
+
const readiness = await this.getExportReadiness();
|
|
146
|
+
if (!readiness.ready) {
|
|
147
|
+
this.eventBus.emit(MeframeEvent.ExportPreparing, {
|
|
148
|
+
totalClips: readiness.totalClips,
|
|
149
|
+
cachedClips: readiness.cachedClips,
|
|
150
|
+
missingClips: readiness.missingClips
|
|
151
|
+
});
|
|
152
|
+
if (!this.preRenderService) {
|
|
153
|
+
this.preRenderService = new PreRenderService(this.orchestrator, this.events);
|
|
154
|
+
this.preRenderService.start();
|
|
155
|
+
}
|
|
156
|
+
const abortController = new AbortController();
|
|
157
|
+
const timeoutMs = 5 * 60 * 1e3;
|
|
158
|
+
const timeoutId = setTimeout(() => {
|
|
159
|
+
abortController.abort();
|
|
160
|
+
}, timeoutMs);
|
|
161
|
+
try {
|
|
162
|
+
await this.preRenderService.ensureClipsInL2(readiness.missingClips, {
|
|
163
|
+
onProgress: (completed, total) => {
|
|
164
|
+
this.eventBus.emit(MeframeEvent.ExportProgress, {
|
|
165
|
+
progress: completed / total,
|
|
166
|
+
stage: "preparing",
|
|
167
|
+
message: `Preparing cache: ${completed}/${total} clips`
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
signal: abortController.signal
|
|
171
|
+
});
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
174
|
+
throw new Error(`Export preparation timeout after ${timeoutMs / 1e3}s`);
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
} finally {
|
|
178
|
+
clearTimeout(timeoutId);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const width = options.width || model.renderConfig?.width || 720;
|
|
182
|
+
const height = options.height || model.renderConfig?.height || 1280;
|
|
133
183
|
const fps = options.fps || model.fps || 30;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const exportConfig = {
|
|
184
|
+
this.eventBus.emit(MeframeEvent.ExportStart, {
|
|
185
|
+
format: options.format || "mp4",
|
|
137
186
|
width,
|
|
138
187
|
height,
|
|
139
188
|
fps,
|
|
140
|
-
|
|
141
|
-
audioBitrate,
|
|
142
|
-
videoCodec: options.videoCodec || "h264",
|
|
143
|
-
audioCodec: options.audioCodec || "aac",
|
|
144
|
-
container: options.format
|
|
145
|
-
};
|
|
146
|
-
const encodeWorker = await this.orchestrator.workers.get("encode");
|
|
147
|
-
const muxWorker = await this.orchestrator.workers.get("mux");
|
|
148
|
-
await encodeWorker.send("configure", exportConfig);
|
|
149
|
-
await muxWorker.send("configure", { container: options.format });
|
|
150
|
-
const { readable, writable } = new TransformStream();
|
|
151
|
-
const exportPromise = this.runExportPipeline(writable, exportConfig);
|
|
152
|
-
exportPromise.then(() => {
|
|
153
|
-
this.setState("ready");
|
|
154
|
-
const totalFrames = Math.ceil(model.durationUs / 1e6 * fps);
|
|
155
|
-
this.eventBus.emit(MeframeEvent.ExportComplete, {
|
|
156
|
-
size: width * height * totalFrames * 4,
|
|
157
|
-
// Approximate size
|
|
158
|
-
durationMs: model.durationUs / 1e3,
|
|
159
|
-
format: options.format
|
|
160
|
-
});
|
|
161
|
-
}).catch((error) => {
|
|
162
|
-
this.setState("error");
|
|
163
|
-
this.eventBus.emit(MeframeEvent.Error, {
|
|
164
|
-
source: "export",
|
|
165
|
-
error,
|
|
166
|
-
context: { format: options.format }
|
|
167
|
-
});
|
|
189
|
+
durationUs: model.durationUs
|
|
168
190
|
});
|
|
169
|
-
|
|
191
|
+
const blob = await this.orchestrator.export(model, options);
|
|
192
|
+
this.setState("ready");
|
|
193
|
+
this.eventBus.emit(MeframeEvent.ExportComplete, {
|
|
194
|
+
size: blob.size,
|
|
195
|
+
durationMs: model.durationUs / 1e3,
|
|
196
|
+
format: options.format || "mp4"
|
|
197
|
+
});
|
|
198
|
+
return blob;
|
|
170
199
|
} catch (error) {
|
|
171
200
|
this.setState("error");
|
|
201
|
+
this.eventBus.emit(MeframeEvent.ExportError, {
|
|
202
|
+
error,
|
|
203
|
+
stage: "export"
|
|
204
|
+
});
|
|
172
205
|
throw error;
|
|
173
206
|
}
|
|
174
207
|
}
|
|
175
208
|
/**
|
|
176
|
-
*
|
|
209
|
+
* Check export readiness
|
|
210
|
+
* Returns coverage info for L2 cache
|
|
177
211
|
*/
|
|
178
|
-
async
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
await frameHandle.use(async (frame) => {
|
|
188
|
-
await encodeWorker2.send("encodeFrame", { frame, timeUs });
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
processedFrames++;
|
|
192
|
-
const progress = processedFrames / totalFrames;
|
|
193
|
-
this.eventBus.emit(MeframeEvent.ExportProgress, {
|
|
194
|
-
progress,
|
|
195
|
-
currentFrame: processedFrames,
|
|
196
|
-
totalFrames,
|
|
197
|
-
timeUs
|
|
198
|
-
});
|
|
212
|
+
async getExportReadiness() {
|
|
213
|
+
if (!this.model) {
|
|
214
|
+
return {
|
|
215
|
+
ready: false,
|
|
216
|
+
totalClips: 0,
|
|
217
|
+
cachedClips: 0,
|
|
218
|
+
missingClips: [],
|
|
219
|
+
coverage: 0
|
|
220
|
+
};
|
|
199
221
|
}
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
* Export to buffer (convenience method)
|
|
211
|
-
*/
|
|
212
|
-
async exportToBuffer(options) {
|
|
213
|
-
const stream = await this.export(options);
|
|
214
|
-
const chunks = [];
|
|
215
|
-
const reader = stream.getReader();
|
|
216
|
-
let done = false;
|
|
217
|
-
while (!done) {
|
|
218
|
-
const result2 = await reader.read();
|
|
219
|
-
done = result2.done;
|
|
220
|
-
if (result2.value) {
|
|
221
|
-
chunks.push(result2.value);
|
|
222
|
+
const videoTracks = this.model.tracks.filter((t) => t.kind === "video");
|
|
223
|
+
const allClips = videoTracks.flatMap((t) => t.clips);
|
|
224
|
+
const missingClips = [];
|
|
225
|
+
let cachedCount = 0;
|
|
226
|
+
for (const clip of allClips) {
|
|
227
|
+
const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, "video");
|
|
228
|
+
if (inL2) {
|
|
229
|
+
cachedCount++;
|
|
230
|
+
} else {
|
|
231
|
+
missingClips.push(clip.id);
|
|
222
232
|
}
|
|
223
233
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
return result.buffer;
|
|
234
|
+
return {
|
|
235
|
+
ready: missingClips.length === 0,
|
|
236
|
+
totalClips: allClips.length,
|
|
237
|
+
cachedClips: cachedCount,
|
|
238
|
+
missingClips,
|
|
239
|
+
coverage: allClips.length > 0 ? cachedCount / allClips.length : 0
|
|
240
|
+
};
|
|
232
241
|
}
|
|
233
242
|
/**
|
|
234
243
|
* Get current playback time
|
|
@@ -236,6 +245,17 @@ class Meframe {
|
|
|
236
245
|
getCurrentTime() {
|
|
237
246
|
return this.playbackController?.currentTimeUs || 0;
|
|
238
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Clear all L1/L2 cache data
|
|
250
|
+
* Useful for debugging or forcing re-render
|
|
251
|
+
*/
|
|
252
|
+
async clearCache() {
|
|
253
|
+
this.ensureReady();
|
|
254
|
+
this.playbackController?.stop();
|
|
255
|
+
this.preRenderService?.stop();
|
|
256
|
+
await this.orchestrator.cacheManager.clear();
|
|
257
|
+
console.log("[Meframe] Cache cleared successfully");
|
|
258
|
+
}
|
|
239
259
|
/**
|
|
240
260
|
* Clean up and destroy the instance
|
|
241
261
|
*/
|
package/dist/Meframe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Meframe.js","sources":["../src/Meframe.ts"],"sourcesContent":["/**\n * Meframe - Main entry point for the media processing framework\n */\n\nimport type { MeframeConfig, ResolvedConfig, MeframeState, ExportOptions } from './types';\nimport type { CompositionModelData, CompositionPatch, TimeUs } from './model/types';\nimport type { PreviewHandle } from './controllers/types';\nimport type { Plugin } from './plugins/types';\nimport { CompositionModel } from './model/CompositionModel';\nimport { loadConfig } from './config';\nimport { Orchestrator } from './orchestrator/Orchestrator';\nimport { PlaybackController } from './controllers/PlaybackController';\nimport { PreRenderService } from './controllers/PreRenderService';\nimport { PluginManager } from './plugins/PluginManager';\nimport { EventBus } from './event/EventBus';\nimport { MeframeEvent, type EventPayloadMap } from './event/events';\n// MuxWorker will be imported when needed for export\n\nexport class Meframe {\n /** Current state - managed internally via setState() */\n state: MeframeState = 'idle';\n /** Configuration - immutable after construction */\n readonly config: ResolvedConfig;\n /** Composition model - managed via setCompositionTree() */\n model: CompositionModel | null = null;\n /** Plugin manager for extensions */\n readonly pluginManager: PluginManager;\n /** Event bus for subscribing to Meframe events */\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n private orchestrator: Orchestrator;\n private eventBus: EventBus<EventPayloadMap>;\n private playbackController: PlaybackController | null = null;\n private preRenderService: PreRenderService | null = null;\n private canvas?: HTMLCanvasElement | OffscreenCanvas;\n\n private constructor(config: ResolvedConfig) {\n this.config = config;\n this.eventBus = new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n this.pluginManager = new PluginManager(this);\n\n // Initialize orchestrator with configuration and shared eventBus\n // Worker paths are passed via config\n this.orchestrator = new Orchestrator({\n maxWorkers: (config as any).maxWorkers,\n cacheConfig: config.cache as any,\n eventBus: this.eventBus,\n workerPath: config.global.workerPath,\n workerExtension: config.global.workerExtension,\n });\n }\n\n /**\n * Create a new Meframe instance\n */\n static async create(config: MeframeConfig = {}): Promise<Meframe> {\n // Load and resolve configuration using ConfigLoader\n const resolvedConfig = await loadConfig({ override: config });\n const instance = new Meframe(resolvedConfig);\n await instance.initialize();\n return instance;\n }\n\n /**\n * Initialize the core engine\n */\n private async initialize(): Promise<void> {\n this.setState('loading');\n\n try {\n // Initialize orchestrator (sets up workers and cache)\n await this.orchestrator.initialize();\n\n // Initialize plugins if provided\n const plugins = (this.config as any).plugins;\n if (plugins && Array.isArray(plugins)) {\n for (const plugin of plugins) {\n // Ensure plugin type matches the Plugin interface\n if (plugin && typeof plugin === 'object' && 'name' in plugin && 'install' in plugin) {\n this.pluginManager.register(plugin as Plugin);\n }\n }\n }\n\n this.setState('idle');\n } catch (error) {\n this.setState('error');\n throw error;\n }\n }\n\n /**\n * Set the composition model\n */\n async setCompositionModel(model: CompositionModel | CompositionModelData): Promise<void> {\n this.ensureNotDestroyed();\n\n // Convert plain object to CompositionModel instance if needed\n const compositionModel =\n model instanceof CompositionModel ? model : new CompositionModel(model);\n\n // Set the model in orchestrator\n await this.orchestrator.setCompositionModel(compositionModel);\n this.model = compositionModel;\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.Ready, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce(\n (acc: number, track: any) => acc + track.clips?.length || 0,\n 0\n ),\n durationUs: model.durationUs,\n });\n this.playbackController?.seek(0);\n }\n\n /**\n * Apply a patch to the composition model\n */\n async applyPatch(patch: CompositionPatch): Promise<void> {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n // Apply patch through orchestrator\n await this.orchestrator.applyPatch(patch);\n\n // Patch is applied to the model by orchestrator\n this.model = this.orchestrator.compositionModel!;\n }\n\n /**\n * Start preview and return a handle for control\n */\n startPreview(options?: {\n canvas?: HTMLCanvasElement | OffscreenCanvas;\n startUs?: TimeUs;\n }): PreviewHandle {\n this.ensureReady();\n\n // Use provided canvas or the one from config\n const canvas = options?.canvas || this.canvas;\n if (!canvas) {\n throw new Error('Canvas is required for preview');\n }\n\n // Store canvas for later use\n this.canvas = canvas;\n\n // Create playback controller\n if (!this.playbackController) {\n this.playbackController = new PlaybackController(this.orchestrator as any, this.eventBus, {\n canvas,\n startUs: options?.startUs,\n });\n this.playbackController.setAudioSession(this.orchestrator.audioSession);\n }\n\n // Start pre-render service for background caching\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n // this.preRenderService.start();\n }\n\n // Return preview handle\n return this.playbackController;\n }\n\n /**\n * Export the composition\n */\n async export(options: ExportOptions): Promise<ReadableStream<Uint8Array>> {\n this.ensureReady();\n this.setState('exporting');\n\n try {\n // Get composition model\n const model = this.model;\n if (!model) {\n throw new Error('No composition model set');\n }\n\n // Determine export parameters\n const width = options.width || (model as any).renderConfig?.width || 1920;\n const height = options.height || (model as any).renderConfig?.height || 1080;\n const fps = options.fps || model.fps || 30;\n const videoBitrate =\n options.videoBitrate ||\n (options.quality === 'highest'\n ? 10000000\n : options.quality === 'high'\n ? 5000000\n : options.quality === 'medium'\n ? 2500000\n : 1000000);\n const audioBitrate = options.audioBitrate || 128000;\n\n // Create export configuration\n const exportConfig = {\n width,\n height,\n fps,\n videoBitrate,\n audioBitrate,\n videoCodec: options.videoCodec || 'h264',\n audioCodec: options.audioCodec || 'aac',\n container: options.format,\n };\n\n // Get encode worker from orchestrator\n const encodeWorker = await this.orchestrator.workers.get('encode');\n const muxWorker = await this.orchestrator.workers.get('mux');\n\n // Configure workers for export\n await encodeWorker.send('configure', exportConfig);\n await muxWorker.send('configure', { container: options.format });\n\n // Create export stream\n const { readable, writable } = new TransformStream<Uint8Array>();\n\n // Start export process\n const exportPromise = this.runExportPipeline(writable, exportConfig);\n\n // Handle export completion\n exportPromise\n .then(() => {\n this.setState('ready');\n const totalFrames = Math.ceil((model.durationUs / 1000000) * fps);\n this.eventBus.emit(MeframeEvent.ExportComplete, {\n size: width * height * totalFrames * 4, // Approximate size\n durationMs: model.durationUs / 1000,\n format: options.format,\n });\n })\n .catch((error) => {\n this.setState('error');\n this.eventBus.emit(MeframeEvent.Error, {\n source: 'export',\n error: error as Error,\n context: { format: options.format },\n });\n });\n\n return readable;\n } catch (error) {\n this.setState('error');\n throw error;\n }\n }\n\n /**\n * Run the export pipeline\n */\n private async runExportPipeline(output: WritableStream<Uint8Array>, config: any): Promise<void> {\n const model = this.model!;\n const totalFrames = Math.ceil((model.durationUs / 1000000) * config.fps);\n let processedFrames = 0;\n\n // Process frames\n const frameTimeUs = 1000000 / config.fps;\n for (let timeUs = 0; timeUs < model.durationUs; timeUs += frameTimeUs) {\n // Render frame through orchestrator\n const frameHandle = await this.orchestrator.renderFrame(timeUs);\n\n // Skip if no frame (e.g., gap in timeline)\n if (frameHandle) {\n const encodeWorker = await this.orchestrator.workers.get('encode');\n await frameHandle.use(async (frame) => {\n await encodeWorker.send('encodeFrame', { frame, timeUs });\n });\n }\n\n // Update progress\n processedFrames++;\n const progress = processedFrames / totalFrames;\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress,\n currentFrame: processedFrames,\n totalFrames,\n timeUs,\n });\n }\n\n // Flush encoder and muxer\n const encodeWorker = await this.orchestrator.workers.get('encode');\n const muxWorker = await this.orchestrator.workers.get('mux');\n\n await encodeWorker.send('flush');\n await muxWorker.send('flush');\n\n // Get final output from muxer\n const result = await muxWorker.send<void, Uint8Array>('getOutput');\n\n // Write to output stream\n const writer = output.getWriter();\n await writer.write(result);\n await writer.close();\n }\n\n /**\n * Export to buffer (convenience method)\n */\n async exportToBuffer(options: ExportOptions): Promise<ArrayBuffer> {\n const stream = await this.export(options);\n const chunks: Uint8Array[] = [];\n const reader = stream.getReader();\n\n let done = false;\n while (!done) {\n const result = await reader.read();\n done = result.done;\n if (result.value) {\n chunks.push(result.value);\n }\n }\n\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result.buffer;\n }\n\n /**\n * Get current playback time\n */\n getCurrentTime(): number {\n return this.playbackController?.currentTimeUs || 0;\n }\n\n /**\n * Clean up and destroy the instance\n */\n async dispose(): Promise<void> {\n if (this.state === 'destroyed') return;\n\n // Stop playback\n this.playbackController?.stop();\n this.playbackController?.dispose();\n this.playbackController = null;\n\n // Stop pre-render service\n this.preRenderService?.stop();\n this.preRenderService = null;\n\n // Cleanup plugins\n await this.pluginManager.disposeAll();\n\n // Dispose orchestrator (stops workers and clears cache)\n await this.orchestrator.dispose();\n\n // Clear event bus\n this.eventBus.dispose();\n\n this.setState('destroyed');\n }\n\n /**\n * Set internal state\n */\n private setState(state: MeframeState): void {\n const oldState = this.state;\n this.state = state;\n\n // Emit state change event\n // Use a generic event for now, can be added to MeframeEvent enum later\n this.eventBus.emit('state:changed' as any, {\n oldState,\n newState: state,\n });\n }\n\n /**\n * Ensure the instance is not destroyed\n */\n private ensureNotDestroyed(): void {\n if (this.state === 'destroyed') {\n throw new Error('Core instance is destroyed');\n }\n }\n\n /**\n * Ensure the instance is ready\n */\n private ensureReady(): void {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n if (this.state === 'loading' || this.state === 'idle') {\n throw new Error('Core is not ready');\n }\n }\n}\n"],"names":["encodeWorker","result"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Meframe.js","sources":["../src/Meframe.ts"],"sourcesContent":["/**\n * Meframe - Main entry point for the media processing framework\n */\n\nimport type { MeframeConfig, ResolvedConfig, MeframeState, ExportOptions } from './types';\nimport type { CompositionModelData, CompositionPatch, TimeUs } from './model/types';\nimport type { PreviewHandle } from './controllers/types';\nimport type { Plugin } from './plugins/types';\nimport { CompositionModel } from './model/CompositionModel';\nimport { loadConfig } from './config';\nimport { Orchestrator } from './orchestrator/Orchestrator';\nimport { PlaybackController } from './controllers/PlaybackController';\nimport { PreRenderService } from './controllers/PreRenderService';\nimport { PluginManager } from './plugins/PluginManager';\nimport { EventBus } from './event/EventBus';\nimport { MeframeEvent, type EventPayloadMap } from './event/events';\n\nexport class Meframe {\n /** Current state - managed internally via setState() */\n state: MeframeState = 'idle';\n /** Configuration - immutable after construction */\n readonly config: ResolvedConfig;\n /** Composition model - managed via setCompositionTree() */\n model: CompositionModel | null = null;\n /** Plugin manager for extensions */\n readonly pluginManager: PluginManager;\n /** Event bus for subscribing to Meframe events */\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n private orchestrator: Orchestrator;\n private eventBus: EventBus<EventPayloadMap>;\n private playbackController: PlaybackController | null = null;\n private preRenderService: PreRenderService | null = null;\n private canvas?: HTMLCanvasElement | OffscreenCanvas;\n\n private constructor(config: ResolvedConfig) {\n this.config = config;\n this.eventBus = new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n this.pluginManager = new PluginManager(this);\n\n // Initialize orchestrator with configuration and shared eventBus\n // Worker paths are passed via config\n this.orchestrator = new Orchestrator({\n maxWorkers: (config as any).maxWorkers,\n cacheConfig: config.cache as any,\n eventBus: this.eventBus,\n workerPath: config.global.workerPath,\n workerExtension: config.global.workerExtension,\n });\n }\n\n /**\n * Create a new Meframe instance\n */\n static async create(config: MeframeConfig = {}): Promise<Meframe> {\n // Load and resolve configuration using ConfigLoader\n const resolvedConfig = await loadConfig({ override: config });\n const instance = new Meframe(resolvedConfig);\n await instance.initialize();\n return instance;\n }\n\n /**\n * Initialize the core engine\n */\n private async initialize(): Promise<void> {\n this.setState('loading');\n\n try {\n // Initialize orchestrator (sets up workers and cache)\n await this.orchestrator.initialize();\n\n // Initialize plugins if provided\n const plugins = (this.config as any).plugins;\n if (plugins && Array.isArray(plugins)) {\n for (const plugin of plugins) {\n // Ensure plugin type matches the Plugin interface\n if (plugin && typeof plugin === 'object' && 'name' in plugin && 'install' in plugin) {\n this.pluginManager.register(plugin as Plugin);\n }\n }\n }\n\n this.setState('idle');\n } catch (error) {\n this.setState('error');\n throw error;\n }\n }\n\n /**\n * Set the composition model\n */\n async setCompositionModel(model: CompositionModel | CompositionModelData): Promise<void> {\n this.ensureNotDestroyed();\n\n // Convert plain object to CompositionModel instance if needed\n const compositionModel =\n model instanceof CompositionModel ? model : new CompositionModel(model);\n\n // Set the model in orchestrator\n await this.orchestrator.setCompositionModel(compositionModel);\n this.model = compositionModel;\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.Ready, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce(\n (acc: number, track: any) => acc + track.clips?.length || 0,\n 0\n ),\n durationUs: model.durationUs,\n });\n this.playbackController?.seek(0);\n }\n\n /**\n * Apply a patch to the composition model\n */\n async applyPatch(patch: CompositionPatch): Promise<void> {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n // Apply patch through orchestrator\n await this.orchestrator.applyPatch(patch);\n\n // Patch is applied to the model by orchestrator\n this.model = this.orchestrator.compositionModel!;\n }\n\n /**\n * Start preview and return a handle for control\n */\n startPreview(options?: {\n canvas?: HTMLCanvasElement | OffscreenCanvas;\n startUs?: TimeUs;\n }): PreviewHandle {\n this.ensureReady();\n\n // Use provided canvas or the one from config\n const canvas = options?.canvas || this.canvas;\n if (!canvas) {\n throw new Error('Canvas is required for preview');\n }\n\n // Store canvas for later use\n this.canvas = canvas;\n\n // Create playback controller\n if (!this.playbackController) {\n this.playbackController = new PlaybackController(this.orchestrator as any, this.eventBus, {\n canvas,\n startUs: options?.startUs,\n });\n this.playbackController.setAudioSession(this.orchestrator.audioSession);\n }\n\n // Start pre-render service for background caching\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n this.preRenderService.start();\n }\n\n // Connect playback controller to pre-render service\n // Update pre-render position when playback position changes\n this.playbackController.on(MeframeEvent.PlaybackTimeUpdate, (payload: any) => {\n this.preRenderService?.updatePlaybackTime(payload.timeUs);\n });\n\n // Update playback active state\n this.playbackController.on(MeframeEvent.PlaybackPlay, () => {\n this.preRenderService?.setPlaybackActive(true);\n });\n\n this.playbackController.on(MeframeEvent.PlaybackPause, () => {\n this.preRenderService?.setPlaybackActive(false);\n });\n\n this.playbackController.on(MeframeEvent.PlaybackStop, () => {\n this.preRenderService?.setPlaybackActive(false);\n });\n\n // Return preview handle\n return this.playbackController;\n }\n\n /**\n * Export the composition\n * Uses L2 cache for fast export, muxes in main thread\n */\n async export(options: ExportOptions): Promise<Blob> {\n this.ensureReady();\n this.setState('exporting');\n\n try {\n const model = this.model;\n if (!model) {\n throw new Error('No composition model set');\n }\n\n // 1. Check L2 cache readiness\n const readiness = await this.getExportReadiness();\n\n if (!readiness.ready) {\n // Emit preparing event\n this.eventBus.emit(MeframeEvent.ExportPreparing, {\n totalClips: readiness.totalClips,\n cachedClips: readiness.cachedClips,\n missingClips: readiness.missingClips,\n });\n\n // Ensure PreRenderService is started\n if (!this.preRenderService) {\n this.preRenderService = new PreRenderService(this.orchestrator as any, this.events);\n this.preRenderService.start();\n }\n\n // Create AbortController for timeout control\n const abortController = new AbortController();\n const timeoutMs = 5 * 60 * 1000; // 5 minutes\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, timeoutMs);\n\n try {\n // Actively trigger missing clips L2 rendering\n await this.preRenderService.ensureClipsInL2(readiness.missingClips, {\n onProgress: (completed, total) => {\n this.eventBus.emit(MeframeEvent.ExportProgress, {\n progress: completed / total,\n stage: 'preparing',\n message: `Preparing cache: ${completed}/${total} clips`,\n });\n },\n signal: abortController.signal,\n });\n } catch (error) {\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new Error(`Export preparation timeout after ${timeoutMs / 1000}s`);\n }\n throw error;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // 2. Continue normal export flow\n const width = options.width || (model as any).renderConfig?.width || 720;\n const height = options.height || (model as any).renderConfig?.height || 1280;\n const fps = options.fps || model.fps || 30;\n\n this.eventBus.emit(MeframeEvent.ExportStart, {\n format: options.format || 'mp4',\n width,\n height,\n fps,\n durationUs: model.durationUs,\n });\n\n // Delegate to orchestrator\n const blob = await this.orchestrator.export(model, options);\n\n this.setState('ready');\n this.eventBus.emit(MeframeEvent.ExportComplete, {\n size: blob.size,\n durationMs: model.durationUs / 1000,\n format: options.format || 'mp4',\n });\n\n return blob;\n } catch (error) {\n this.setState('error');\n this.eventBus.emit(MeframeEvent.ExportError, {\n error: error as Error,\n stage: 'export',\n });\n throw error;\n }\n }\n\n /**\n * Check export readiness\n * Returns coverage info for L2 cache\n */\n async getExportReadiness(): Promise<{\n ready: boolean;\n totalClips: number;\n cachedClips: number;\n missingClips: string[];\n coverage: number;\n }> {\n if (!this.model) {\n return {\n ready: false,\n totalClips: 0,\n cachedClips: 0,\n missingClips: [],\n coverage: 0,\n };\n }\n\n const videoTracks = this.model.tracks.filter((t) => t.kind === 'video');\n const allClips = videoTracks.flatMap((t) => t.clips);\n\n const missingClips: string[] = [];\n let cachedCount = 0;\n\n for (const clip of allClips) {\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n cachedCount++;\n } else {\n missingClips.push(clip.id);\n }\n }\n\n return {\n ready: missingClips.length === 0,\n totalClips: allClips.length,\n cachedClips: cachedCount,\n missingClips,\n coverage: allClips.length > 0 ? cachedCount / allClips.length : 0,\n };\n }\n\n /**\n * Get current playback time\n */\n getCurrentTime(): number {\n return this.playbackController?.currentTimeUs || 0;\n }\n\n /**\n * Clear all L1/L2 cache data\n * Useful for debugging or forcing re-render\n */\n async clearCache(): Promise<void> {\n this.ensureReady();\n\n // Stop playback first\n this.playbackController?.stop();\n\n // Stop pre-render service\n this.preRenderService?.stop();\n\n // Clear cache through CacheManager\n await this.orchestrator.cacheManager.clear();\n\n console.log('[Meframe] Cache cleared successfully');\n }\n\n /**\n * Clean up and destroy the instance\n */\n async dispose(): Promise<void> {\n if (this.state === 'destroyed') return;\n\n // Stop playback\n this.playbackController?.stop();\n this.playbackController?.dispose();\n this.playbackController = null;\n\n // Stop pre-render service\n this.preRenderService?.stop();\n this.preRenderService = null;\n\n // Cleanup plugins\n await this.pluginManager.disposeAll();\n\n // Dispose orchestrator (stops workers and clears cache)\n await this.orchestrator.dispose();\n\n // Clear event bus\n this.eventBus.dispose();\n\n this.setState('destroyed');\n }\n\n /**\n * Set internal state\n */\n private setState(state: MeframeState): void {\n const oldState = this.state;\n this.state = state;\n\n // Emit state change event\n // Use a generic event for now, can be added to MeframeEvent enum later\n this.eventBus.emit('state:changed' as any, {\n oldState,\n newState: state,\n });\n }\n\n /**\n * Ensure the instance is not destroyed\n */\n private ensureNotDestroyed(): void {\n if (this.state === 'destroyed') {\n throw new Error('Core instance is destroyed');\n }\n }\n\n /**\n * Ensure the instance is ready\n */\n private ensureReady(): void {\n this.ensureNotDestroyed();\n\n if (!this.model) {\n throw new Error('No composition model set');\n }\n\n if (this.state === 'loading' || this.state === 'idle') {\n throw new Error('Core is not ready');\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { TimeUs } from '../model/types';
|
|
2
|
-
import { RcFrame
|
|
3
|
-
import { GOP } from './types';
|
|
2
|
+
import { RcFrame } from '../model';
|
|
4
3
|
import { L2Cache } from './L2Cache';
|
|
5
4
|
import { EventBus } from '../event/EventBus';
|
|
6
5
|
import { EventPayloadMap } from '../event/events';
|
|
@@ -28,7 +27,7 @@ export interface WaitForFrameResult {
|
|
|
28
27
|
clipId: string;
|
|
29
28
|
}
|
|
30
29
|
/**
|
|
31
|
-
* Simplified CacheManager for
|
|
30
|
+
* Simplified CacheManager for 2-Clip strategy
|
|
32
31
|
*
|
|
33
32
|
* Core features:
|
|
34
33
|
* - L1 (VRAM) for composed VideoFrames
|
|
@@ -46,7 +45,6 @@ export declare class CacheManager {
|
|
|
46
45
|
private eventBus?;
|
|
47
46
|
constructor(config: CacheManagerConfig, eventBus?: EventBus<EventPayloadMap>);
|
|
48
47
|
init(): Promise<void>;
|
|
49
|
-
configure(_model: CompositionModel | null): void;
|
|
50
48
|
receiveComposedFrames(stream: ReadableStream<VideoFrame | {
|
|
51
49
|
frame: VideoFrame;
|
|
52
50
|
metadata?: any;
|
|
@@ -58,8 +56,14 @@ export declare class CacheManager {
|
|
|
58
56
|
clipId: string;
|
|
59
57
|
timeUs: TimeUs;
|
|
60
58
|
}) => void;
|
|
61
|
-
}): void
|
|
62
|
-
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
receiveEncodedChunks(stream: ReadableStream<{
|
|
61
|
+
chunk: EncodedVideoChunk;
|
|
62
|
+
metadata: EncodedVideoChunkMetadata;
|
|
63
|
+
}>, clipId: string, track: 'video' | 'audio', options?: {
|
|
64
|
+
onComplete?: (metadata: EncodedVideoChunkMetadata) => void;
|
|
65
|
+
onError?: (error: Error) => void;
|
|
66
|
+
}): Promise<void>;
|
|
63
67
|
acceptMixedAudio(stream: ReadableStream<AudioData>, metadata: {
|
|
64
68
|
sampleRate: number;
|
|
65
69
|
numberOfChannels: number;
|
|
@@ -72,9 +76,6 @@ export declare class CacheManager {
|
|
|
72
76
|
resetAudioCache(): void;
|
|
73
77
|
getFrame(timeUs: TimeUs, clipId: string): Promise<RcFrame | null>;
|
|
74
78
|
waitForFrame(timeUs: TimeUs, clipId: string, options?: WaitForFrameOptions): Promise<WaitForFrameResult>;
|
|
75
|
-
putGOP(gop: GOP, trackId: string): Promise<void>;
|
|
76
|
-
putEncodedChunks(clipHash: string, chunks: Array<EncodedVideoChunk | EncodedAudioChunk>, track: 'video' | 'audio'): Promise<void>;
|
|
77
|
-
invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): void;
|
|
78
79
|
invalidateClip(clipId: string): Promise<void>;
|
|
79
80
|
/**
|
|
80
81
|
* Evict a clip from L1 cache
|
|
@@ -102,8 +103,24 @@ export declare class CacheManager {
|
|
|
102
103
|
hitRate: number;
|
|
103
104
|
};
|
|
104
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Create read stream from L2 cache for export
|
|
108
|
+
*/
|
|
109
|
+
createL2ReadStream(clipId: string, track: 'video' | 'audio'): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null>;
|
|
110
|
+
/**
|
|
111
|
+
* Check if clip is fully cached in L2
|
|
112
|
+
* Returns true only if the clip is marked as complete
|
|
113
|
+
*/
|
|
114
|
+
hasClipInL2(clipId: string, track: 'video' | 'audio'): Promise<boolean>;
|
|
115
|
+
/**
|
|
116
|
+
* Mark clip as complete in L2 cache
|
|
117
|
+
*/
|
|
118
|
+
markClipComplete(clipId: string, track: 'video' | 'audio'): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Get chunk metadata (decoderConfig) from L2 cache
|
|
121
|
+
*/
|
|
122
|
+
getL2Metadata(clipId: string, track: 'video' | 'audio'): Promise<any | null>;
|
|
105
123
|
private decodeFromL2;
|
|
106
|
-
private encodeToL2;
|
|
107
124
|
private notifyFrameWaiters;
|
|
108
125
|
private notifyClipReadyWaiters;
|
|
109
126
|
private cleanupWaiter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,9 +23,7 @@ class CacheManager {
|
|
|
23
23
|
async init() {
|
|
24
24
|
await this.l2Cache.init();
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
receiveComposedFrames(stream, params) {
|
|
26
|
+
async receiveComposedFrames(stream, params) {
|
|
29
27
|
const reader = stream.getReader();
|
|
30
28
|
const process = async () => {
|
|
31
29
|
const { done, value } = await reader.read();
|
|
@@ -74,47 +72,100 @@ class CacheManager {
|
|
|
74
72
|
}
|
|
75
73
|
await process();
|
|
76
74
|
};
|
|
77
|
-
|
|
75
|
+
try {
|
|
76
|
+
await process();
|
|
77
|
+
} catch (error) {
|
|
78
78
|
this.eventBus?.emit(MeframeEvent.ComposeFrameDropped, {
|
|
79
79
|
timeUs: 0,
|
|
80
80
|
reason: "compose_slow"
|
|
81
81
|
});
|
|
82
82
|
reader.releaseLock();
|
|
83
83
|
throw error;
|
|
84
|
-
}
|
|
84
|
+
}
|
|
85
85
|
}
|
|
86
|
-
|
|
86
|
+
async receiveEncodedChunks(stream, clipId, track, options) {
|
|
87
87
|
const reader = stream.getReader();
|
|
88
88
|
const chunks = [];
|
|
89
|
+
const batchSize = track === "video" ? 30 : 50;
|
|
90
|
+
let decoderConfig = null;
|
|
91
|
+
let metadata;
|
|
89
92
|
const process = async () => {
|
|
90
93
|
const { done, value } = await reader.read();
|
|
94
|
+
const { chunk, metadata: chunkMetadata } = value ?? {};
|
|
95
|
+
if (chunkMetadata) {
|
|
96
|
+
metadata = chunkMetadata;
|
|
97
|
+
if (!decoderConfig && chunkMetadata.decoderConfig) {
|
|
98
|
+
decoderConfig = chunkMetadata.decoderConfig;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
91
101
|
if (done) {
|
|
92
102
|
if (chunks.length > 0) {
|
|
93
|
-
await this.l2Cache.put(clipId, chunks, track
|
|
103
|
+
await this.l2Cache.put(clipId, chunks, track, {
|
|
104
|
+
isComplete: true,
|
|
105
|
+
metadata: decoderConfig
|
|
106
|
+
});
|
|
107
|
+
const firstChunk = chunks[0];
|
|
108
|
+
if (firstChunk) {
|
|
109
|
+
this.eventBus?.emit(MeframeEvent.CacheWrite, {
|
|
110
|
+
clipId,
|
|
111
|
+
timeUs: firstChunk.timestamp,
|
|
112
|
+
level: "L2",
|
|
113
|
+
size: chunks.reduce((sum, c) => sum + c.byteLength, 0)
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
await this.l2Cache.markComplete(clipId, track);
|
|
94
118
|
}
|
|
95
119
|
reader.releaseLock();
|
|
120
|
+
if (options?.onComplete) {
|
|
121
|
+
options.onComplete(metadata);
|
|
122
|
+
}
|
|
96
123
|
return;
|
|
97
124
|
}
|
|
98
|
-
if (
|
|
99
|
-
chunks.push(
|
|
125
|
+
if (chunk) {
|
|
126
|
+
chunks.push(chunk);
|
|
100
127
|
this.eventBus?.emit(MeframeEvent.EncodeChunkReady, {
|
|
101
|
-
timeUs:
|
|
102
|
-
durationUs:
|
|
128
|
+
timeUs: chunk.timestamp,
|
|
129
|
+
durationUs: chunk.duration ?? 0,
|
|
103
130
|
track,
|
|
104
|
-
size:
|
|
131
|
+
size: chunk.byteLength
|
|
105
132
|
});
|
|
133
|
+
if (chunks.length >= batchSize) {
|
|
134
|
+
const batchToWrite = chunks.splice(0);
|
|
135
|
+
if (batchToWrite.length > 0) {
|
|
136
|
+
await this.l2Cache.put(clipId, batchToWrite, track, {
|
|
137
|
+
metadata: decoderConfig
|
|
138
|
+
});
|
|
139
|
+
this.eventBus?.emit(MeframeEvent.CacheWrite, {
|
|
140
|
+
clipId,
|
|
141
|
+
timeUs: chunk.timestamp,
|
|
142
|
+
level: "L2",
|
|
143
|
+
size: batchToWrite.reduce((sum, c) => sum + c.byteLength, 0)
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
106
147
|
}
|
|
107
148
|
await process();
|
|
108
149
|
};
|
|
109
|
-
|
|
150
|
+
try {
|
|
151
|
+
await process();
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (chunks.length > 0) {
|
|
154
|
+
await this.l2Cache.put(clipId, chunks, track, {
|
|
155
|
+
metadata: decoderConfig
|
|
156
|
+
});
|
|
157
|
+
}
|
|
110
158
|
this.eventBus?.emit(MeframeEvent.EncodeChunkError, {
|
|
111
159
|
timeUs: 0,
|
|
112
160
|
track,
|
|
113
161
|
error
|
|
114
162
|
});
|
|
115
163
|
reader.releaseLock();
|
|
164
|
+
if (options?.onError) {
|
|
165
|
+
options.onError(error);
|
|
166
|
+
}
|
|
116
167
|
throw error;
|
|
117
|
-
}
|
|
168
|
+
}
|
|
118
169
|
}
|
|
119
170
|
acceptMixedAudio(stream, metadata) {
|
|
120
171
|
this.audioL1Cache.attachStream(stream, metadata);
|
|
@@ -218,21 +269,8 @@ class CacheManager {
|
|
|
218
269
|
this.pendingFramePromises.set(requestKey, trackedPromise);
|
|
219
270
|
return trackedPromise;
|
|
220
271
|
}
|
|
221
|
-
async putGOP(gop, trackId) {
|
|
222
|
-
if (!gop.clipId) {
|
|
223
|
-
throw new Error("GOP clipId is required for clip-aware caching");
|
|
224
|
-
}
|
|
225
|
-
this.videoL1Cache.putGOP(gop, gop.clipId, trackId);
|
|
226
|
-
this.encodeToL2(gop);
|
|
227
|
-
}
|
|
228
|
-
async putEncodedChunks(clipHash, chunks, track) {
|
|
229
|
-
await this.l2Cache.put(clipHash, chunks, track);
|
|
230
|
-
}
|
|
231
|
-
invalidateRange(startUs, endUs, clipId) {
|
|
232
|
-
this.videoL1Cache.invalidateRange(startUs, endUs, clipId);
|
|
233
|
-
this.l2Cache.invalidateRange(startUs, endUs, clipId);
|
|
234
|
-
}
|
|
235
272
|
async invalidateClip(clipId) {
|
|
273
|
+
console.log(`[CacheManager] invalidateClip(${clipId}) - clearing L1 and L2`);
|
|
236
274
|
this.videoL1Cache.invalidateRange(0, Infinity, clipId);
|
|
237
275
|
await this.l2Cache.invalidateClip(clipId);
|
|
238
276
|
}
|
|
@@ -294,6 +332,32 @@ class CacheManager {
|
|
|
294
332
|
l2: this.l2Cache.getMetadata()
|
|
295
333
|
};
|
|
296
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Create read stream from L2 cache for export
|
|
337
|
+
*/
|
|
338
|
+
async createL2ReadStream(clipId, track) {
|
|
339
|
+
return this.l2Cache.createReadStream(clipId, track);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Check if clip is fully cached in L2
|
|
343
|
+
* Returns true only if the clip is marked as complete
|
|
344
|
+
*/
|
|
345
|
+
async hasClipInL2(clipId, track) {
|
|
346
|
+
const result = await this.l2Cache.hasCompleteClip(clipId, track);
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Mark clip as complete in L2 cache
|
|
351
|
+
*/
|
|
352
|
+
async markClipComplete(clipId, track) {
|
|
353
|
+
await this.l2Cache.markComplete(clipId, track);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Get chunk metadata (decoderConfig) from L2 cache
|
|
357
|
+
*/
|
|
358
|
+
async getL2Metadata(clipId, track) {
|
|
359
|
+
return this.l2Cache.getClipMetadata(clipId, track);
|
|
360
|
+
}
|
|
297
361
|
async decodeFromL2(timeUs, clipId) {
|
|
298
362
|
const encodedChunk = await this.l2Cache.get(timeUs, clipId);
|
|
299
363
|
if (!encodedChunk) {
|
|
@@ -301,8 +365,6 @@ class CacheManager {
|
|
|
301
365
|
}
|
|
302
366
|
return null;
|
|
303
367
|
}
|
|
304
|
-
async encodeToL2(_gop) {
|
|
305
|
-
}
|
|
306
368
|
notifyFrameWaiters(clipId, timestampUs, frameDurationUs, frame) {
|
|
307
369
|
const waiters = this.frameWaiters.get(clipId);
|
|
308
370
|
if (!waiters || waiters.size === 0) {
|