@meframe/core 0.0.29 → 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 +116 -51
- 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 +231 -297
- 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/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 +4 -2
- 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/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.lMOUMWFh.js → MP4Demuxer.BEa6PLJm.js} +9 -2
- package/dist/workers/{MP4Demuxer.lMOUMWFh.js.map → MP4Demuxer.BEa6PLJm.js.map} +1 -1
- package/dist/workers/stages/compose/{video-compose.worker.CIeEIJO7.js → video-compose.worker.DHQ8B105.js} +59 -31
- 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.DcurGC8i.js → audio-demux.worker._VRQdLdv.js} +2 -2
- package/dist/workers/stages/demux/{audio-demux.worker.DcurGC8i.js.map → audio-demux.worker._VRQdLdv.js.map} +1 -1
- package/dist/workers/stages/demux/{video-demux.worker.B1_wntU4.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/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/workers/BaseDecoder.BWYu1W0B.js.map +0 -1
- package/dist/workers/stages/compose/video-compose.worker.CIeEIJO7.js.map +0 -1
- package/dist/workers/stages/demux/video-demux.worker.B1_wntU4.js.map +0 -1
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
class OPFSStore {
|
|
2
|
-
opfsRoot = null;
|
|
3
|
-
initPromise = null;
|
|
4
|
-
async init() {
|
|
5
|
-
if (this.initPromise) return this.initPromise;
|
|
6
|
-
this.initPromise = this.initStorage();
|
|
7
|
-
return this.initPromise;
|
|
8
|
-
}
|
|
9
|
-
async initStorage() {
|
|
10
|
-
this.opfsRoot = await navigator.storage.getDirectory();
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Read chunk data from OPFS file
|
|
14
|
-
*/
|
|
15
|
-
async read(fileName, batch, projectId) {
|
|
16
|
-
if (!this.opfsRoot) return null;
|
|
17
|
-
try {
|
|
18
|
-
const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
|
|
19
|
-
create: false
|
|
20
|
-
});
|
|
21
|
-
const fileHandle = await projectDir.getFileHandle(fileName);
|
|
22
|
-
const file = await fileHandle.getFile();
|
|
23
|
-
const slice = file.slice(batch.byteOffset, batch.byteOffset + batch.byteLength);
|
|
24
|
-
return await slice.arrayBuffer();
|
|
25
|
-
} catch (error) {
|
|
26
|
-
if (error?.name === "NotFoundError") {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Append chunks to OPFS file (or create new file)
|
|
34
|
-
* Supports incremental writing for streaming scenarios
|
|
35
|
-
*/
|
|
36
|
-
async append(fileName, chunks, existingBatches, projectId) {
|
|
37
|
-
if (!this.opfsRoot) return [];
|
|
38
|
-
const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
|
|
39
|
-
create: true
|
|
40
|
-
});
|
|
41
|
-
const fileHandle = await projectDir.getFileHandle(fileName, { create: true });
|
|
42
|
-
let offset = 0;
|
|
43
|
-
if (existingBatches && existingBatches.length > 0) {
|
|
44
|
-
const lastBatch = existingBatches[existingBatches.length - 1];
|
|
45
|
-
if (lastBatch) {
|
|
46
|
-
offset = lastBatch.byteOffset + lastBatch.byteLength;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const writable = await fileHandle.createWritable({ keepExistingData: true });
|
|
50
|
-
const batches = [];
|
|
51
|
-
for (const chunk of chunks) {
|
|
52
|
-
const data = await this.chunkToArrayBuffer(chunk);
|
|
53
|
-
await writable.write({ type: "write", position: offset, data });
|
|
54
|
-
batches.push({
|
|
55
|
-
startUs: chunk.timestamp,
|
|
56
|
-
durationUs: chunk.duration || 0,
|
|
57
|
-
byteOffset: offset,
|
|
58
|
-
byteLength: data.byteLength,
|
|
59
|
-
type: chunk.type
|
|
60
|
-
});
|
|
61
|
-
offset += data.byteLength;
|
|
62
|
-
}
|
|
63
|
-
await writable.close();
|
|
64
|
-
return batches;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Delete a file from OPFS
|
|
68
|
-
*/
|
|
69
|
-
async deleteFile(fileName, projectId) {
|
|
70
|
-
if (!this.opfsRoot) return;
|
|
71
|
-
try {
|
|
72
|
-
const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
|
|
73
|
-
create: false
|
|
74
|
-
});
|
|
75
|
-
await projectDir.removeEntry(fileName);
|
|
76
|
-
} catch (error) {
|
|
77
|
-
if (error?.name !== "NotFoundError") {
|
|
78
|
-
console.warn(`[OPFSStore] Failed to delete file ${fileName}:`, error);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Delete entire project directory
|
|
84
|
-
*/
|
|
85
|
-
async deleteProjectDirectory(projectId) {
|
|
86
|
-
if (!this.opfsRoot) return;
|
|
87
|
-
try {
|
|
88
|
-
const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
|
|
89
|
-
create: false
|
|
90
|
-
});
|
|
91
|
-
await this.opfsRoot.removeEntry(projectDir.name, { recursive: true });
|
|
92
|
-
} catch (error) {
|
|
93
|
-
if (error?.name !== "NotFoundError") {
|
|
94
|
-
console.warn(`[OPFSStore] Failed to remove project directory ${projectId}:`, error);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Check if a file exists in OPFS
|
|
100
|
-
*/
|
|
101
|
-
async fileExists(fileName, projectId) {
|
|
102
|
-
if (!this.opfsRoot) return false;
|
|
103
|
-
try {
|
|
104
|
-
const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {
|
|
105
|
-
create: false
|
|
106
|
-
});
|
|
107
|
-
await projectDir.getFileHandle(fileName, { create: false });
|
|
108
|
-
return true;
|
|
109
|
-
} catch (error) {
|
|
110
|
-
if (error?.name === "NotFoundError") {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Clear all OPFS data for current project
|
|
118
|
-
*/
|
|
119
|
-
async clear(projectId) {
|
|
120
|
-
await this.deleteProjectDirectory(projectId);
|
|
121
|
-
}
|
|
122
|
-
async chunkToArrayBuffer(chunk) {
|
|
123
|
-
const buffer = new ArrayBuffer(chunk.byteLength);
|
|
124
|
-
chunk.copyTo(buffer);
|
|
125
|
-
return buffer;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
export {
|
|
129
|
-
OPFSStore
|
|
130
|
-
};
|
|
131
|
-
//# sourceMappingURL=OPFSStore.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"OPFSStore.js","sources":["../../../src/cache/l2/OPFSStore.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\n\nexport interface ChunkBatch {\n startUs: TimeUs;\n durationUs: TimeUs;\n byteOffset: number;\n byteLength: number;\n type: 'key' | 'delta';\n}\n\n/**\n * OPFS file storage for L2 cache\n * Handles all file operations in Origin Private File System\n */\nexport class OPFSStore {\n private opfsRoot: FileSystemDirectoryHandle | null = null;\n private initPromise: Promise<void> | null = null;\n\n async init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = this.initStorage();\n return this.initPromise;\n }\n\n private async initStorage(): Promise<void> {\n this.opfsRoot = await navigator.storage.getDirectory();\n }\n\n /**\n * Read chunk data from OPFS file\n */\n async read(fileName: string, batch: ChunkBatch, projectId: string): Promise<ArrayBuffer | null> {\n if (!this.opfsRoot) return null;\n\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: false,\n });\n const fileHandle = await projectDir.getFileHandle(fileName);\n const file = await fileHandle.getFile();\n const slice = file.slice(batch.byteOffset, batch.byteOffset + batch.byteLength);\n return await slice.arrayBuffer();\n } catch (error) {\n if ((error as any)?.name === 'NotFoundError') {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Append chunks to OPFS file (or create new file)\n * Supports incremental writing for streaming scenarios\n */\n async append(\n fileName: string,\n chunks: Array<EncodedVideoChunk | EncodedAudioChunk>,\n existingBatches: ChunkBatch[] | undefined,\n projectId: string\n ): Promise<ChunkBatch[]> {\n if (!this.opfsRoot) return [];\n\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: true,\n });\n const fileHandle = await projectDir.getFileHandle(fileName, { create: true });\n\n // Calculate starting offset from existing batches\n let offset = 0;\n if (existingBatches && existingBatches.length > 0) {\n const lastBatch = existingBatches[existingBatches.length - 1];\n if (lastBatch) {\n offset = lastBatch.byteOffset + lastBatch.byteLength;\n }\n }\n\n const writable = await fileHandle.createWritable({ keepExistingData: true });\n\n const batches: ChunkBatch[] = [];\n\n for (const chunk of chunks) {\n const data = await this.chunkToArrayBuffer(chunk);\n await writable.write({ type: 'write', position: offset, data });\n\n batches.push({\n startUs: chunk.timestamp,\n durationUs: chunk.duration || 0,\n byteOffset: offset,\n byteLength: data.byteLength,\n type: chunk.type,\n });\n\n offset += data.byteLength;\n }\n\n await writable.close();\n return batches;\n }\n\n /**\n * Delete a file from OPFS\n */\n async deleteFile(fileName: string, projectId: string): Promise<void> {\n if (!this.opfsRoot) return;\n\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: false,\n });\n await projectDir.removeEntry(fileName);\n } catch (error) {\n if ((error as any)?.name !== 'NotFoundError') {\n console.warn(`[OPFSStore] Failed to delete file ${fileName}:`, error);\n }\n }\n }\n\n /**\n * Delete entire project directory\n */\n async deleteProjectDirectory(projectId: string): Promise<void> {\n if (!this.opfsRoot) return;\n\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: false,\n });\n await this.opfsRoot.removeEntry(projectDir.name, { recursive: true });\n } catch (error) {\n if ((error as any)?.name !== 'NotFoundError') {\n console.warn(`[OPFSStore] Failed to remove project directory ${projectId}:`, error);\n }\n }\n }\n\n /**\n * Check if a file exists in OPFS\n */\n async fileExists(fileName: string, projectId: string): Promise<boolean> {\n if (!this.opfsRoot) return false;\n\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${projectId}`, {\n create: false,\n });\n await projectDir.getFileHandle(fileName, { create: false });\n return true;\n } catch (error) {\n if ((error as any)?.name === 'NotFoundError') {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Clear all OPFS data for current project\n */\n async clear(projectId: string): Promise<void> {\n await this.deleteProjectDirectory(projectId);\n }\n\n private async chunkToArrayBuffer(\n chunk: EncodedVideoChunk | EncodedAudioChunk\n ): Promise<ArrayBuffer> {\n const buffer = new ArrayBuffer(chunk.byteLength);\n chunk.copyTo(buffer);\n return buffer;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { IPreRenderService, RenderStrategy, PreRenderStatus } from './types';
|
|
2
|
-
import { TimeUs } from '../model/types';
|
|
3
|
-
import { Orchestrator } from '../orchestrator';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* PreRenderService: Background pre-render for filling L2 cache
|
|
7
|
-
*
|
|
8
|
-
* Dual-mode strategy:
|
|
9
|
-
* 1. When playback active: Ensure 2-Clip window (Prev/Current/Next) for preview
|
|
10
|
-
* 2. When idle: Continue rendering subsequent clips for L2 export cache
|
|
11
|
-
*
|
|
12
|
-
* Goals:
|
|
13
|
-
* - Maintain smooth preview (2-Clip L1 cache)
|
|
14
|
-
* - Build complete L2 cache for fast export
|
|
15
|
-
* - Operate in background without blocking
|
|
16
|
-
*/
|
|
17
|
-
export declare class PreRenderService implements IPreRenderService {
|
|
18
|
-
private orchestrator;
|
|
19
|
-
private _isRunning;
|
|
20
|
-
private _isPaused;
|
|
21
|
-
private renderLoopId;
|
|
22
|
-
private _framesRendered;
|
|
23
|
-
private _currentTimeUs;
|
|
24
|
-
private isPlaybackActive;
|
|
25
|
-
private strategy;
|
|
26
|
-
private isRendering;
|
|
27
|
-
private highPriorityMode;
|
|
28
|
-
private exportWaiters;
|
|
29
|
-
private progressCallback;
|
|
30
|
-
constructor(orchestrator: Orchestrator, _eventBus: any);
|
|
31
|
-
start(): void;
|
|
32
|
-
stop(): Promise<void>;
|
|
33
|
-
pause(): void;
|
|
34
|
-
resume(): void;
|
|
35
|
-
setWindow(size: TimeUs): void;
|
|
36
|
-
setStrategy(strategy: Partial<RenderStrategy>): void;
|
|
37
|
-
setPriority(_priority: 'low' | 'normal' | 'high'): void;
|
|
38
|
-
updatePlaybackTime(timeUs: TimeUs): void;
|
|
39
|
-
setPlaybackActive(active: boolean): void;
|
|
40
|
-
get isRunning(): boolean;
|
|
41
|
-
get queueSize(): number;
|
|
42
|
-
get status(): PreRenderStatus;
|
|
43
|
-
private scheduleNextRender;
|
|
44
|
-
private renderTick;
|
|
45
|
-
/**
|
|
46
|
-
* Render a complete clip to L2 cache
|
|
47
|
-
* Creates dedicated pipeline that bypasses L1 window management
|
|
48
|
-
*/
|
|
49
|
-
private renderClipToL2;
|
|
50
|
-
/**
|
|
51
|
-
* Ensure all clips are in L2 cache
|
|
52
|
-
* Switches to high priority mode and waits for completion
|
|
53
|
-
*/
|
|
54
|
-
ensureClipsInL2(options?: {
|
|
55
|
-
onProgress?: (completed: number, total: number) => void;
|
|
56
|
-
signal?: AbortSignal;
|
|
57
|
-
}): Promise<void>;
|
|
58
|
-
}
|
|
59
|
-
//# sourceMappingURL=PreRenderService.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PreRenderService.d.ts","sourceRoot":"","sources":["../../src/controllers/PreRenderService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAClF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,QAAQ,CAKd;IAEF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,aAAa,CAAoE;IACzF,OAAO,CAAC,gBAAgB,CAA6D;gBAEzE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG;IAItD,KAAK,IAAI,IAAI;IAMP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAS3B,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,IAAI;IAOd,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI7B,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI;IAIpD,WAAW,CAAC,SAAS,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI;IAIvD,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIxC,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAIxC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,MAAM,IAAI,eAAe,CAM5B;IAED,OAAO,CAAC,kBAAkB;YAqBZ,UAAU;IAoGxB;;;OAGG;YACW,cAAc;IAkB5B;;;OAGG;IACG,eAAe,CAAC,OAAO,CAAC,EAAE;QAC9B,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;QACxD,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;CAYlB"}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { hasResourceId } from "../model/types.js";
|
|
2
|
-
class PreRenderService {
|
|
3
|
-
orchestrator;
|
|
4
|
-
_isRunning = false;
|
|
5
|
-
_isPaused = false;
|
|
6
|
-
renderLoopId = null;
|
|
7
|
-
_framesRendered = 0;
|
|
8
|
-
_currentTimeUs = 0;
|
|
9
|
-
isPlaybackActive = false;
|
|
10
|
-
strategy = {
|
|
11
|
-
direction: "forward",
|
|
12
|
-
lookahead: 3e6,
|
|
13
|
-
lookbehind: 2e6,
|
|
14
|
-
keyframesOnly: false
|
|
15
|
-
};
|
|
16
|
-
isRendering = false;
|
|
17
|
-
highPriorityMode = false;
|
|
18
|
-
exportWaiters = [];
|
|
19
|
-
progressCallback = null;
|
|
20
|
-
constructor(orchestrator, _eventBus) {
|
|
21
|
-
this.orchestrator = orchestrator;
|
|
22
|
-
}
|
|
23
|
-
start() {
|
|
24
|
-
if (this._isRunning) return;
|
|
25
|
-
this._isRunning = true;
|
|
26
|
-
this.scheduleNextRender();
|
|
27
|
-
}
|
|
28
|
-
async stop() {
|
|
29
|
-
this._isRunning = false;
|
|
30
|
-
if (this.renderLoopId !== null) {
|
|
31
|
-
cancelIdleCallback(this.renderLoopId);
|
|
32
|
-
this.renderLoopId = null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
pause() {
|
|
36
|
-
this._isPaused = true;
|
|
37
|
-
}
|
|
38
|
-
resume() {
|
|
39
|
-
this._isPaused = false;
|
|
40
|
-
if (this._isRunning && !this.renderLoopId) {
|
|
41
|
-
this.scheduleNextRender();
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
setWindow(size) {
|
|
45
|
-
this.strategy.lookahead = size;
|
|
46
|
-
}
|
|
47
|
-
setStrategy(strategy) {
|
|
48
|
-
this.strategy = { ...this.strategy, ...strategy };
|
|
49
|
-
}
|
|
50
|
-
setPriority(_priority) {
|
|
51
|
-
}
|
|
52
|
-
updatePlaybackTime(timeUs) {
|
|
53
|
-
this._currentTimeUs = timeUs;
|
|
54
|
-
}
|
|
55
|
-
setPlaybackActive(active) {
|
|
56
|
-
this.isPlaybackActive = active;
|
|
57
|
-
}
|
|
58
|
-
get isRunning() {
|
|
59
|
-
return this._isRunning;
|
|
60
|
-
}
|
|
61
|
-
get queueSize() {
|
|
62
|
-
return 0;
|
|
63
|
-
}
|
|
64
|
-
get status() {
|
|
65
|
-
return {
|
|
66
|
-
isRunning: this._isRunning,
|
|
67
|
-
framesRendered: this._framesRendered,
|
|
68
|
-
currentTimeUs: this._currentTimeUs
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
scheduleNextRender() {
|
|
72
|
-
if (!this._isRunning) return;
|
|
73
|
-
if (this.highPriorityMode) {
|
|
74
|
-
this.renderLoopId = requestAnimationFrame(async () => {
|
|
75
|
-
await this.renderTick();
|
|
76
|
-
this.scheduleNextRender();
|
|
77
|
-
});
|
|
78
|
-
} else {
|
|
79
|
-
this.renderLoopId = requestIdleCallback(
|
|
80
|
-
async () => {
|
|
81
|
-
await this.renderTick();
|
|
82
|
-
this.scheduleNextRender();
|
|
83
|
-
},
|
|
84
|
-
{ timeout: 100 }
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
async renderTick() {
|
|
89
|
-
if (!this._isRunning || this._isPaused || !this.orchestrator.compositionModel) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
const model = this.orchestrator.compositionModel;
|
|
93
|
-
if (this.isPlaybackActive) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (this.isRendering) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const mainTrack = model.findTrack(model.mainTrackId);
|
|
100
|
-
const allClips = mainTrack?.clips ?? [];
|
|
101
|
-
if (allClips.length === 0) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
let clipToRender = null;
|
|
105
|
-
let completedCount = 0;
|
|
106
|
-
for (const clip of allClips) {
|
|
107
|
-
const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, "video");
|
|
108
|
-
if (inL2) {
|
|
109
|
-
completedCount++;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (hasResourceId(clip) && this.orchestrator.resourceLoader.isResourceLoading(clip.resourceId)) {
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
clipToRender = clip;
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
if (!clipToRender) {
|
|
119
|
-
if (completedCount < allClips.length) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (this.highPriorityMode) {
|
|
123
|
-
this.exportWaiters.forEach((w) => w.resolve());
|
|
124
|
-
this.exportWaiters = [];
|
|
125
|
-
this.highPriorityMode = false;
|
|
126
|
-
this.progressCallback = null;
|
|
127
|
-
}
|
|
128
|
-
void this.stop();
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
this.isRendering = true;
|
|
132
|
-
try {
|
|
133
|
-
const rendered = await this.renderClipToL2(clipToRender.id);
|
|
134
|
-
if (!rendered) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
if (this.highPriorityMode && this.progressCallback) {
|
|
138
|
-
const totalClips = allClips.length;
|
|
139
|
-
this.progressCallback(completedCount + 1, totalClips);
|
|
140
|
-
}
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.error(`[PreRenderService] Failed to render clip ${clipToRender.id}:`, error);
|
|
143
|
-
if (this.highPriorityMode) {
|
|
144
|
-
this.exportWaiters.forEach((w) => w.reject(error));
|
|
145
|
-
this.exportWaiters = [];
|
|
146
|
-
this.highPriorityMode = false;
|
|
147
|
-
this.progressCallback = null;
|
|
148
|
-
}
|
|
149
|
-
} finally {
|
|
150
|
-
this.isRendering = false;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Render a complete clip to L2 cache
|
|
155
|
-
* Creates dedicated pipeline that bypasses L1 window management
|
|
156
|
-
*/
|
|
157
|
-
async renderClipToL2(clipId) {
|
|
158
|
-
const model = this.orchestrator.compositionModel;
|
|
159
|
-
if (!model) return false;
|
|
160
|
-
const clip = model.findClip(clipId);
|
|
161
|
-
if (!clip) return false;
|
|
162
|
-
const rendered = await this.orchestrator.renderClipForL2(clipId);
|
|
163
|
-
const fps = model.fps || 30;
|
|
164
|
-
const expectedFrames = Math.ceil(clip.durationUs / 1e6 * fps);
|
|
165
|
-
this._framesRendered += expectedFrames;
|
|
166
|
-
return rendered;
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Ensure all clips are in L2 cache
|
|
170
|
-
* Switches to high priority mode and waits for completion
|
|
171
|
-
*/
|
|
172
|
-
async ensureClipsInL2(options) {
|
|
173
|
-
return new Promise((resolve, reject) => {
|
|
174
|
-
this.highPriorityMode = true;
|
|
175
|
-
this.progressCallback = options?.onProgress || null;
|
|
176
|
-
this.exportWaiters.push({ resolve, reject });
|
|
177
|
-
this.stop();
|
|
178
|
-
this.start();
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
export {
|
|
183
|
-
PreRenderService
|
|
184
|
-
};
|
|
185
|
-
//# sourceMappingURL=PreRenderService.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PreRenderService.js","sources":["../../src/controllers/PreRenderService.ts"],"sourcesContent":["import type { IPreRenderService, RenderStrategy, PreRenderStatus } from './types';\nimport type { TimeUs } from '../model/types';\nimport type { Orchestrator } from '../orchestrator';\nimport { hasResourceId } from '../model/types';\n\n/**\n * PreRenderService: Background pre-render for filling L2 cache\n *\n * Dual-mode strategy:\n * 1. When playback active: Ensure 2-Clip window (Prev/Current/Next) for preview\n * 2. When idle: Continue rendering subsequent clips for L2 export cache\n *\n * Goals:\n * - Maintain smooth preview (2-Clip L1 cache)\n * - Build complete L2 cache for fast export\n * - Operate in background without blocking\n */\nexport class PreRenderService implements IPreRenderService {\n private orchestrator: Orchestrator;\n private _isRunning = false;\n private _isPaused = false;\n private renderLoopId: number | null = null;\n private _framesRendered = 0;\n private _currentTimeUs: TimeUs = 0;\n private isPlaybackActive = false;\n private strategy: RenderStrategy = {\n direction: 'forward',\n lookahead: 3_000_000,\n lookbehind: 2_000_000,\n keyframesOnly: false,\n };\n\n private isRendering = false;\n private highPriorityMode = false;\n private exportWaiters: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];\n private progressCallback: ((completed: number, total: number) => void) | null = null;\n\n constructor(orchestrator: Orchestrator, _eventBus: any) {\n this.orchestrator = orchestrator;\n }\n\n start(): void {\n if (this._isRunning) return;\n this._isRunning = true;\n this.scheduleNextRender();\n }\n\n async stop(): Promise<void> {\n this._isRunning = false;\n\n if (this.renderLoopId !== null) {\n cancelIdleCallback(this.renderLoopId);\n this.renderLoopId = null;\n }\n }\n\n pause(): void {\n this._isPaused = true;\n }\n\n resume(): void {\n this._isPaused = false;\n if (this._isRunning && !this.renderLoopId) {\n this.scheduleNextRender();\n }\n }\n\n setWindow(size: TimeUs): void {\n this.strategy.lookahead = size;\n }\n\n setStrategy(strategy: Partial<RenderStrategy>): void {\n this.strategy = { ...this.strategy, ...strategy };\n }\n\n setPriority(_priority: 'low' | 'normal' | 'high'): void {\n // Simplified - no-op for 2-Clip strategy\n }\n\n updatePlaybackTime(timeUs: TimeUs): void {\n this._currentTimeUs = timeUs;\n }\n\n setPlaybackActive(active: boolean): void {\n this.isPlaybackActive = active;\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n get queueSize(): number {\n return 0;\n }\n\n get status(): PreRenderStatus {\n return {\n isRunning: this._isRunning,\n framesRendered: this._framesRendered,\n currentTimeUs: this._currentTimeUs,\n };\n }\n\n private scheduleNextRender(): void {\n if (!this._isRunning) return;\n\n if (this.highPriorityMode) {\n // High priority: use requestAnimationFrame for faster rendering\n this.renderLoopId = requestAnimationFrame(async () => {\n await this.renderTick();\n this.scheduleNextRender();\n }) as any;\n } else {\n // Normal priority: use requestIdleCallback\n this.renderLoopId = requestIdleCallback(\n async () => {\n await this.renderTick();\n this.scheduleNextRender();\n },\n { timeout: 100 }\n );\n }\n }\n\n private async renderTick(): Promise<void> {\n if (!this._isRunning || this._isPaused || !this.orchestrator.compositionModel) {\n return;\n }\n\n const model = this.orchestrator.compositionModel;\n\n if (this.isPlaybackActive) {\n // Playback mode: Do nothing, PlaybackController manages 2-clip window\n return;\n }\n\n // If already rendering, skip this tick\n if (this.isRendering) {\n return;\n }\n\n // Idle mode: Continue rendering clips beyond 2-Clip window for L2 cache\n // Only process main track clips (attachments are already included)\n const mainTrack = model.findTrack(model.mainTrackId);\n const allClips = mainTrack?.clips ?? [];\n\n if (allClips.length === 0) {\n return;\n }\n // Find next clip to render, skip if resource is loading\n let clipToRender = null;\n let completedCount = 0;\n\n for (const clip of allClips) {\n // Check L2 cache\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n completedCount++;\n continue;\n }\n\n // Check if resource is being loaded by preview channel\n if (\n hasResourceId(clip) &&\n this.orchestrator.resourceLoader.isResourceLoading(clip.resourceId)\n ) {\n continue; // Preview priority: skip this clip, will retry in next tick\n }\n\n // Found available clip\n clipToRender = clip;\n break;\n }\n\n // Handle completion\n if (!clipToRender) {\n if (completedCount < allClips.length) {\n // All unprocessed clips are temporarily skipped due to resource conflicts\n // Continue loop, will retry in next tick\n return;\n }\n\n // Really done: all clips are in L2\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.resolve());\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n\n void this.stop();\n return;\n }\n\n // Render this clip completely with concurrency protection\n this.isRendering = true;\n try {\n const rendered = await this.renderClipToL2(clipToRender.id);\n if (!rendered) {\n // Resource conflict: don't mark as processed, will retry in next tick\n return;\n }\n\n // Report progress in high priority mode\n if (this.highPriorityMode && this.progressCallback) {\n const totalClips = allClips.length;\n // completedCount from loop + 1 for the clip just rendered\n this.progressCallback(completedCount + 1, totalClips);\n }\n } catch (error) {\n console.error(`[PreRenderService] Failed to render clip ${clipToRender.id}:`, error);\n\n // Notify waiters of error in high priority mode\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.reject(error as Error));\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Render a complete clip to L2 cache\n * Creates dedicated pipeline that bypasses L1 window management\n */\n private async renderClipToL2(clipId: string): Promise<boolean> {\n const model = this.orchestrator.compositionModel;\n if (!model) return false;\n\n const clip = model.findClip(clipId);\n if (!clip) return false;\n\n // Start L2 rendering\n const rendered = await this.orchestrator.renderClipForL2(clipId);\n\n // Update counter\n const fps = model.fps || 30;\n const expectedFrames = Math.ceil((clip.durationUs / 1_000_000) * fps);\n this._framesRendered += expectedFrames;\n\n return rendered;\n }\n\n /**\n * Ensure all clips are in L2 cache\n * Switches to high priority mode and waits for completion\n */\n async ensureClipsInL2(options?: {\n onProgress?: (completed: number, total: number) => void;\n signal?: AbortSignal;\n }): Promise<void> {\n // Switch to high priority mode and wait for all clips to complete\n return new Promise<void>((resolve, reject) => {\n this.highPriorityMode = true;\n this.progressCallback = options?.onProgress || null;\n this.exportWaiters.push({ resolve, reject });\n\n // Cancel current idle callback and restart with RAF\n this.stop();\n this.start();\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { RenderTask } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Priority queue for pre-render tasks
|
|
5
|
-
*/
|
|
6
|
-
export declare class PreRenderTaskQueue {
|
|
7
|
-
private queue;
|
|
8
|
-
private taskMap;
|
|
9
|
-
enqueue(task: RenderTask): void;
|
|
10
|
-
dequeue(): RenderTask | null;
|
|
11
|
-
peek(): RenderTask | null;
|
|
12
|
-
isEmpty(): boolean;
|
|
13
|
-
size(): number;
|
|
14
|
-
clear(): void;
|
|
15
|
-
remove(timeUs: number, clipId: string): boolean;
|
|
16
|
-
removeRange(startUs: number, endUs: number, clipId?: string): number;
|
|
17
|
-
getAll(): RenderTask[];
|
|
18
|
-
private sortQueue;
|
|
19
|
-
private getTaskKey;
|
|
20
|
-
}
|
|
21
|
-
//# sourceMappingURL=PreRenderTaskQueue.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PreRenderTaskQueue.d.ts","sourceRoot":"","sources":["../../src/controllers/PreRenderTaskQueue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,OAAO,CAAiC;IAEhD,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAiB/B,OAAO,IAAI,UAAU,GAAG,IAAI;IAS5B,IAAI,IAAI,UAAU,GAAG,IAAI;IAIzB,OAAO,IAAI,OAAO;IAIlB,IAAI,IAAI,MAAM;IAId,KAAK,IAAI,IAAI;IAKb,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAiB/C,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAoBpE,MAAM,IAAI,UAAU,EAAE;IAItB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,UAAU;CAGnB"}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { VideoClipSession } from './VideoClipSession';
|
|
2
|
-
import { ClipUpdateResult } from './CompositionPlanner';
|
|
3
|
-
import { CacheManager } from '../cache/CacheManager';
|
|
4
|
-
|
|
5
|
-
export interface ClipSessionFactory {
|
|
6
|
-
createSession(clipId: string): Promise<VideoClipSession>;
|
|
7
|
-
}
|
|
8
|
-
export type ClipSessionState = 'idle' | 'activating' | 'active' | 'failed';
|
|
9
|
-
/**
|
|
10
|
-
* ClipSessionManager manages VideoClipSession instances using 2-Clip strategy
|
|
11
|
-
*
|
|
12
|
-
* At any time, maintains:
|
|
13
|
-
* - Current clip (being played)
|
|
14
|
-
* - Next clip (for forward playback)
|
|
15
|
-
*/
|
|
16
|
-
export declare class ClipSessionManager {
|
|
17
|
-
private entries;
|
|
18
|
-
private activeSet;
|
|
19
|
-
private inflightCount;
|
|
20
|
-
private readonly maxConcurrent;
|
|
21
|
-
private readonly factory;
|
|
22
|
-
private readonly cacheManager;
|
|
23
|
-
constructor(config: {
|
|
24
|
-
maxConcurrent?: number;
|
|
25
|
-
factory: ClipSessionFactory;
|
|
26
|
-
cacheManager: CacheManager;
|
|
27
|
-
});
|
|
28
|
-
/**
|
|
29
|
-
* Ensure clips are active (Current, Next)
|
|
30
|
-
* Deactivates clips outside the set
|
|
31
|
-
*/
|
|
32
|
-
ensureClips(clipIds: Set<string>): Promise<void>;
|
|
33
|
-
handlePlannerUpdate(clipId: string, update: ClipUpdateResult): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* Get session for a clip
|
|
36
|
-
*/
|
|
37
|
-
getSession(clipId: string): VideoClipSession | undefined;
|
|
38
|
-
/**
|
|
39
|
-
* Check if a clip is active
|
|
40
|
-
*/
|
|
41
|
-
isClipActive(clipId: string): boolean;
|
|
42
|
-
/**
|
|
43
|
-
* Get clip state
|
|
44
|
-
*/
|
|
45
|
-
getClipState(clipId: string): ClipSessionState;
|
|
46
|
-
/**
|
|
47
|
-
* Get metrics for monitoring
|
|
48
|
-
*/
|
|
49
|
-
getMetrics(): {
|
|
50
|
-
idle: number;
|
|
51
|
-
activating: number;
|
|
52
|
-
active: number;
|
|
53
|
-
failed: number;
|
|
54
|
-
total: number;
|
|
55
|
-
inflight: number;
|
|
56
|
-
activeSessions: number;
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* Clear all sessions
|
|
60
|
-
*/
|
|
61
|
-
clear(): Promise<void>;
|
|
62
|
-
/**
|
|
63
|
-
* Dispose and cleanup
|
|
64
|
-
*/
|
|
65
|
-
dispose(): Promise<void>;
|
|
66
|
-
private enqueueActivation;
|
|
67
|
-
private activateClip;
|
|
68
|
-
private deactivateClip;
|
|
69
|
-
}
|
|
70
|
-
//# sourceMappingURL=ClipSessionManager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ClipSessionManager.d.ts","sourceRoot":"","sources":["../../src/orchestrator/ClipSessionManager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC1D;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAW3E;;;;;;GAMG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;gBAEhC,MAAM,EAAE;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,kBAAkB,CAAC;QAC5B,YAAY,EAAE,YAAY,CAAC;KAC5B;IAMD;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BhD,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlF;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAIxD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKrC;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB;IAI9C;;OAEG;IACH,UAAU;;;;;;;;;IAcV;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAIhB,iBAAiB;YAiCjB,YAAY;YA4BZ,cAAc;CAY7B"}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
class ClipSessionManager {
|
|
2
|
-
entries = /* @__PURE__ */ new Map();
|
|
3
|
-
activeSet = /* @__PURE__ */ new Set();
|
|
4
|
-
inflightCount = 0;
|
|
5
|
-
maxConcurrent;
|
|
6
|
-
factory;
|
|
7
|
-
cacheManager;
|
|
8
|
-
constructor(config) {
|
|
9
|
-
this.maxConcurrent = config.maxConcurrent ?? 4;
|
|
10
|
-
this.factory = config.factory;
|
|
11
|
-
this.cacheManager = config.cacheManager;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Ensure clips are active (Current, Next)
|
|
15
|
-
* Deactivates clips outside the set
|
|
16
|
-
*/
|
|
17
|
-
async ensureClips(clipIds) {
|
|
18
|
-
for (const [clipId, entry] of this.entries) {
|
|
19
|
-
if (!clipIds.has(clipId) && entry.state === "active") {
|
|
20
|
-
await this.deactivateClip(clipId);
|
|
21
|
-
this.cacheManager.invalidateClipInL1(clipId);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
this.activeSet = clipIds;
|
|
25
|
-
const toActivate = [];
|
|
26
|
-
for (const clipId of clipIds) {
|
|
27
|
-
const hasL1Cache = this.cacheManager.hasClipInL1(clipId);
|
|
28
|
-
const hasL2Cache = await this.cacheManager.hasClipInL2(clipId, "video");
|
|
29
|
-
const noCache = !hasL1Cache && !hasL2Cache;
|
|
30
|
-
const entry = this.entries.get(clipId);
|
|
31
|
-
const needActivate = !entry || entry.state === "idle" || entry.state === "failed";
|
|
32
|
-
if (needActivate && noCache) {
|
|
33
|
-
toActivate.push(clipId);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
for (const clipId of toActivate) {
|
|
37
|
-
await this.enqueueActivation(clipId);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async handlePlannerUpdate(clipId, update) {
|
|
41
|
-
const session = this.getSession(clipId);
|
|
42
|
-
if (!session) return;
|
|
43
|
-
await session.handlePlannerUpdate(update);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Get session for a clip
|
|
47
|
-
*/
|
|
48
|
-
getSession(clipId) {
|
|
49
|
-
return this.entries.get(clipId)?.session;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Check if a clip is active
|
|
53
|
-
*/
|
|
54
|
-
isClipActive(clipId) {
|
|
55
|
-
const entry = this.entries.get(clipId);
|
|
56
|
-
return entry?.state === "active" || false;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Get clip state
|
|
60
|
-
*/
|
|
61
|
-
getClipState(clipId) {
|
|
62
|
-
return this.entries.get(clipId)?.state ?? "idle";
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Get metrics for monitoring
|
|
66
|
-
*/
|
|
67
|
-
getMetrics() {
|
|
68
|
-
const states = { idle: 0, activating: 0, active: 0, failed: 0 };
|
|
69
|
-
for (const entry of this.entries.values()) {
|
|
70
|
-
states[entry.state]++;
|
|
71
|
-
}
|
|
72
|
-
return {
|
|
73
|
-
total: this.entries.size,
|
|
74
|
-
inflight: this.inflightCount,
|
|
75
|
-
activeSessions: this.activeSet.size,
|
|
76
|
-
...states
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Clear all sessions
|
|
81
|
-
*/
|
|
82
|
-
async clear() {
|
|
83
|
-
for (const clipId of this.entries.keys()) {
|
|
84
|
-
await this.deactivateClip(clipId);
|
|
85
|
-
}
|
|
86
|
-
this.entries.clear();
|
|
87
|
-
this.activeSet.clear();
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Dispose and cleanup
|
|
91
|
-
*/
|
|
92
|
-
async dispose() {
|
|
93
|
-
await this.clear();
|
|
94
|
-
}
|
|
95
|
-
async enqueueActivation(clipId) {
|
|
96
|
-
let entry = this.entries.get(clipId);
|
|
97
|
-
if (!entry) {
|
|
98
|
-
entry = {
|
|
99
|
-
clipId,
|
|
100
|
-
state: "idle",
|
|
101
|
-
lastAccess: Date.now(),
|
|
102
|
-
retryCount: 0
|
|
103
|
-
};
|
|
104
|
-
this.entries.set(clipId, entry);
|
|
105
|
-
}
|
|
106
|
-
if (entry.state === "activating" || entry.state === "active") {
|
|
107
|
-
return entry.activationPromise;
|
|
108
|
-
}
|
|
109
|
-
while (this.inflightCount >= this.maxConcurrent) {
|
|
110
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
111
|
-
}
|
|
112
|
-
entry.state = "activating";
|
|
113
|
-
entry.lastAccess = Date.now();
|
|
114
|
-
this.inflightCount++;
|
|
115
|
-
const activationPromise = this.activateClip(entry);
|
|
116
|
-
entry.activationPromise = activationPromise;
|
|
117
|
-
return activationPromise;
|
|
118
|
-
}
|
|
119
|
-
async activateClip(entry) {
|
|
120
|
-
try {
|
|
121
|
-
const session = await this.factory.createSession(entry.clipId);
|
|
122
|
-
await session.activate();
|
|
123
|
-
entry.session = session;
|
|
124
|
-
entry.state = "active";
|
|
125
|
-
entry.retryCount = 0;
|
|
126
|
-
} catch (error) {
|
|
127
|
-
console.error(`[ClipSessionManager] Failed to activate clip ${entry.clipId}:`, error);
|
|
128
|
-
entry.state = "failed";
|
|
129
|
-
entry.retryCount++;
|
|
130
|
-
if (entry.retryCount < 3) {
|
|
131
|
-
const delay = Math.min(1e3 * Math.pow(2, entry.retryCount), 5e3);
|
|
132
|
-
setTimeout(() => {
|
|
133
|
-
if (this.activeSet.has(entry.clipId)) {
|
|
134
|
-
entry.state = "idle";
|
|
135
|
-
void this.enqueueActivation(entry.clipId);
|
|
136
|
-
}
|
|
137
|
-
}, delay);
|
|
138
|
-
}
|
|
139
|
-
} finally {
|
|
140
|
-
this.inflightCount--;
|
|
141
|
-
entry.activationPromise = void 0;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
async deactivateClip(clipId) {
|
|
145
|
-
const entry = this.entries.get(clipId);
|
|
146
|
-
if (!entry) return;
|
|
147
|
-
if (entry.session) {
|
|
148
|
-
await entry.session.dispose();
|
|
149
|
-
entry.session = void 0;
|
|
150
|
-
}
|
|
151
|
-
entry.state = "idle";
|
|
152
|
-
this.entries.delete(clipId);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
export {
|
|
156
|
-
ClipSessionManager
|
|
157
|
-
};
|
|
158
|
-
//# sourceMappingURL=ClipSessionManager.js.map
|