@meframe/core 0.0.29 → 0.0.30
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 -30
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +7 -118
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +42 -68
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +224 -188
- 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/L2OPFSStore.d.ts +37 -0
- package/dist/cache/l2/L2OPFSStore.d.ts.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/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 +115 -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/model/types.d.ts +0 -1
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.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 +24 -9
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +149 -152
- 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 +244 -312
- 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 +50 -15
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +297 -80
- 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/index.d.ts +0 -1
- package/dist/stages/load/index.d.ts.map +1 -1
- package/dist/stages/load/types.d.ts +9 -9
- 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 +0 -180
- package/dist/cache/l2/IndexedDBStore.js.map +0 -1
- package/dist/cache/l2/L2Cache.js +0 -329
- package/dist/cache/l2/L2Cache.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/load/EventHandlers.d.ts +0 -26
- package/dist/stages/load/EventHandlers.d.ts.map +0 -1
- package/dist/stages/load/EventHandlers.js +0 -42
- package/dist/stages/load/EventHandlers.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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { binarySearchRange, binarySearchFirst } from "../../utils/binary-search.js";
|
|
2
|
+
class MP4IndexCache {
|
|
3
|
+
indexes = /* @__PURE__ */ new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Set index for a resource
|
|
6
|
+
*/
|
|
7
|
+
set(resourceId, index) {
|
|
8
|
+
this.indexes.set(resourceId, index);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get index for a resource
|
|
12
|
+
*/
|
|
13
|
+
get(resourceId) {
|
|
14
|
+
return this.indexes.get(resourceId) || null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if index exists for a resource
|
|
18
|
+
*/
|
|
19
|
+
has(resourceId) {
|
|
20
|
+
return this.indexes.has(resourceId);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get GOP range (byte range) for a specific time
|
|
24
|
+
* Returns the GOP that contains the target time
|
|
25
|
+
*/
|
|
26
|
+
getGOPRangeForTime(resourceId, timeUs) {
|
|
27
|
+
const index = this.indexes.get(resourceId);
|
|
28
|
+
if (!index?.tracks.video) return null;
|
|
29
|
+
const { samples, gopIndex } = index.tracks.video;
|
|
30
|
+
const targetGOP = binarySearchRange(gopIndex, timeUs, (gop, idx) => {
|
|
31
|
+
const nextGOP = gopIndex[idx + 1];
|
|
32
|
+
return {
|
|
33
|
+
start: gop.startTimeUs,
|
|
34
|
+
end: nextGOP ? nextGOP.startTimeUs : Infinity
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
if (!targetGOP) {
|
|
38
|
+
console.warn("[MP4IndexCache] No GOP found for timeUs:", timeUs);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const startSample = samples[targetGOP.keyframeSampleIndex];
|
|
42
|
+
const endSampleIndex = targetGOP.keyframeSampleIndex + targetGOP.sampleCount - 1;
|
|
43
|
+
const endSample = samples[endSampleIndex];
|
|
44
|
+
if (!startSample || !endSample) {
|
|
45
|
+
console.error("[MP4IndexCache] Missing samples for GOP:", {
|
|
46
|
+
keyframeSampleIndex: targetGOP.keyframeSampleIndex,
|
|
47
|
+
sampleCount: targetGOP.sampleCount,
|
|
48
|
+
totalSamples: samples.length
|
|
49
|
+
});
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
byteStart: startSample.byteOffset,
|
|
54
|
+
byteEnd: endSample.byteOffset + endSample.byteLength,
|
|
55
|
+
startTimeUs: startSample.timestamp,
|
|
56
|
+
endTimeUs: endSample.timestamp + endSample.duration
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get sample at specific timestamp
|
|
61
|
+
*/
|
|
62
|
+
getSampleAtTime(resourceId, timeUs) {
|
|
63
|
+
const index = this.indexes.get(resourceId);
|
|
64
|
+
if (!index?.tracks.video) return null;
|
|
65
|
+
const { samples } = index.tracks.video;
|
|
66
|
+
const result = binarySearchRange(samples, timeUs, (sample, _index) => ({
|
|
67
|
+
start: sample.timestamp,
|
|
68
|
+
end: sample.timestamp + sample.duration
|
|
69
|
+
}));
|
|
70
|
+
return result || null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get nearest keyframe before or at the target time
|
|
74
|
+
*/
|
|
75
|
+
getNearestKeyframe(resourceId, timeUs) {
|
|
76
|
+
const index = this.indexes.get(resourceId);
|
|
77
|
+
if (!index?.tracks.video) return null;
|
|
78
|
+
const { samples, gopIndex } = index.tracks.video;
|
|
79
|
+
const firstGOPAfterIndex = binarySearchFirst(gopIndex, (gop) => gop.startTimeUs > timeUs);
|
|
80
|
+
const targetGOPIndex = firstGOPAfterIndex - 1;
|
|
81
|
+
if (targetGOPIndex < 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const targetGOP = gopIndex[targetGOPIndex];
|
|
85
|
+
if (!targetGOP) return null;
|
|
86
|
+
return samples[targetGOP.keyframeSampleIndex] ?? null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Clear all cached indexes
|
|
90
|
+
*/
|
|
91
|
+
clear() {
|
|
92
|
+
this.indexes.clear();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Remove index for a specific resource
|
|
96
|
+
*/
|
|
97
|
+
remove(resourceId) {
|
|
98
|
+
this.indexes.delete(resourceId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
MP4IndexCache
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=MP4IndexCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MP4IndexCache.js","sources":["../../../src/cache/resource/MP4IndexCache.ts"],"sourcesContent":["import type { MP4Index, Sample, GOPRange } from '../../stages/demux/types';\nimport type { TimeUs } from '../../model/types';\nimport { binarySearchRange, binarySearchFirst } from '../../utils/binary-search';\n\n/**\n * MP4IndexCache - In-memory cache for MP4 indexes\n *\n * Provides fast lookup for:\n * - Sample at specific timestamp\n * - GOP range for time range\n * - Nearest keyframe\n */\nexport class MP4IndexCache {\n private indexes = new Map<string, MP4Index>();\n\n /**\n * Set index for a resource\n */\n set(resourceId: string, index: MP4Index): void {\n this.indexes.set(resourceId, index);\n }\n\n /**\n * Get index for a resource\n */\n get(resourceId: string): MP4Index | null {\n return this.indexes.get(resourceId) || null;\n }\n\n /**\n * Check if index exists for a resource\n */\n has(resourceId: string): boolean {\n return this.indexes.has(resourceId);\n }\n\n /**\n * Get GOP range (byte range) for a specific time\n * Returns the GOP that contains the target time\n */\n getGOPRangeForTime(resourceId: string, timeUs: TimeUs): GOPRange | null {\n const index = this.indexes.get(resourceId);\n if (!index?.tracks.video) return null;\n\n const { samples, gopIndex } = index.tracks.video;\n\n // Find GOP that contains this timestamp using binary search\n const targetGOP = binarySearchRange(gopIndex, timeUs, (gop, idx) => {\n const nextGOP = gopIndex[idx + 1];\n return {\n start: gop.startTimeUs,\n end: nextGOP ? nextGOP.startTimeUs : Infinity,\n };\n });\n\n if (!targetGOP) {\n console.warn('[MP4IndexCache] No GOP found for timeUs:', timeUs);\n return null;\n }\n\n // Calculate byte range for this GOP\n const startSample = samples[targetGOP.keyframeSampleIndex];\n const endSampleIndex = targetGOP.keyframeSampleIndex + targetGOP.sampleCount - 1;\n const endSample = samples[endSampleIndex];\n\n if (!startSample || !endSample) {\n console.error('[MP4IndexCache] Missing samples for GOP:', {\n keyframeSampleIndex: targetGOP.keyframeSampleIndex,\n sampleCount: targetGOP.sampleCount,\n totalSamples: samples.length,\n });\n return null;\n }\n\n return {\n byteStart: startSample.byteOffset,\n byteEnd: endSample.byteOffset + endSample.byteLength,\n startTimeUs: startSample.timestamp,\n endTimeUs: endSample.timestamp + endSample.duration,\n };\n }\n\n /**\n * Get sample at specific timestamp\n */\n getSampleAtTime(resourceId: string, timeUs: TimeUs): Sample | null {\n const index = this.indexes.get(resourceId);\n if (!index?.tracks.video) return null;\n\n const { samples } = index.tracks.video;\n\n const result = binarySearchRange(samples, timeUs, (sample, _index) => ({\n start: sample.timestamp,\n end: sample.timestamp + sample.duration,\n }));\n\n return result || null;\n }\n\n /**\n * Get nearest keyframe before or at the target time\n */\n getNearestKeyframe(resourceId: string, timeUs: TimeUs): Sample | null {\n const index = this.indexes.get(resourceId);\n if (!index?.tracks.video) return null;\n\n const { samples, gopIndex } = index.tracks.video;\n\n // Find the first GOP that starts after timeUs using binary search\n const firstGOPAfterIndex = binarySearchFirst(gopIndex, (gop) => gop.startTimeUs > timeUs);\n\n // The nearest keyframe is in the GOP before firstGOPAfterIndex\n const targetGOPIndex = firstGOPAfterIndex - 1;\n\n if (targetGOPIndex < 0) {\n // timeUs is before the first GOP\n return null;\n }\n\n const targetGOP = gopIndex[targetGOPIndex];\n if (!targetGOP) return null;\n\n // Return the keyframe sample of this GOP\n return samples[targetGOP.keyframeSampleIndex] ?? null;\n }\n\n /**\n * Clear all cached indexes\n */\n clear(): void {\n this.indexes.clear();\n }\n\n /**\n * Remove index for a specific resource\n */\n remove(resourceId: string): void {\n this.indexes.delete(resourceId);\n }\n}\n"],"names":[],"mappings":";AAYO,MAAM,cAAc;AAAA,EACjB,8BAAc,IAAA;AAAA;AAAA;AAAA;AAAA,EAKtB,IAAI,YAAoB,OAAuB;AAC7C,SAAK,QAAQ,IAAI,YAAY,KAAK;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAqC;AACvC,WAAO,KAAK,QAAQ,IAAI,UAAU,KAAK;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAA6B;AAC/B,WAAO,KAAK,QAAQ,IAAI,UAAU;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmB,YAAoB,QAAiC;AACtE,UAAM,QAAQ,KAAK,QAAQ,IAAI,UAAU;AACzC,QAAI,CAAC,OAAO,OAAO,MAAO,QAAO;AAEjC,UAAM,EAAE,SAAS,SAAA,IAAa,MAAM,OAAO;AAG3C,UAAM,YAAY,kBAAkB,UAAU,QAAQ,CAAC,KAAK,QAAQ;AAClE,YAAM,UAAU,SAAS,MAAM,CAAC;AAChC,aAAO;AAAA,QACL,OAAO,IAAI;AAAA,QACX,KAAK,UAAU,QAAQ,cAAc;AAAA,MAAA;AAAA,IAEzC,CAAC;AAED,QAAI,CAAC,WAAW;AACd,cAAQ,KAAK,4CAA4C,MAAM;AAC/D,aAAO;AAAA,IACT;AAGA,UAAM,cAAc,QAAQ,UAAU,mBAAmB;AACzD,UAAM,iBAAiB,UAAU,sBAAsB,UAAU,cAAc;AAC/E,UAAM,YAAY,QAAQ,cAAc;AAExC,QAAI,CAAC,eAAe,CAAC,WAAW;AAC9B,cAAQ,MAAM,4CAA4C;AAAA,QACxD,qBAAqB,UAAU;AAAA,QAC/B,aAAa,UAAU;AAAA,QACvB,cAAc,QAAQ;AAAA,MAAA,CACvB;AACD,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,WAAW,YAAY;AAAA,MACvB,SAAS,UAAU,aAAa,UAAU;AAAA,MAC1C,aAAa,YAAY;AAAA,MACzB,WAAW,UAAU,YAAY,UAAU;AAAA,IAAA;AAAA,EAE/C;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,YAAoB,QAA+B;AACjE,UAAM,QAAQ,KAAK,QAAQ,IAAI,UAAU;AACzC,QAAI,CAAC,OAAO,OAAO,MAAO,QAAO;AAEjC,UAAM,EAAE,QAAA,IAAY,MAAM,OAAO;AAEjC,UAAM,SAAS,kBAAkB,SAAS,QAAQ,CAAC,QAAQ,YAAY;AAAA,MACrE,OAAO,OAAO;AAAA,MACd,KAAK,OAAO,YAAY,OAAO;AAAA,IAAA,EAC/B;AAEF,WAAO,UAAU;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,YAAoB,QAA+B;AACpE,UAAM,QAAQ,KAAK,QAAQ,IAAI,UAAU;AACzC,QAAI,CAAC,OAAO,OAAO,MAAO,QAAO;AAEjC,UAAM,EAAE,SAAS,SAAA,IAAa,MAAM,OAAO;AAG3C,UAAM,qBAAqB,kBAAkB,UAAU,CAAC,QAAQ,IAAI,cAAc,MAAM;AAGxF,UAAM,iBAAiB,qBAAqB;AAE5C,QAAI,iBAAiB,GAAG;AAEtB,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,SAAS,cAAc;AACzC,QAAI,CAAC,UAAW,QAAO;AAGvB,WAAO,QAAQ,UAAU,mBAAmB,KAAK;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,QAAQ,MAAA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,YAA0B;AAC/B,SAAK,QAAQ,OAAO,UAAU;AAAA,EAChC;AACF;"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { OPFSManager } from '../storage/opfs/OPFSManager';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ResourceCache - Original video resource OPFS cache
|
|
5
|
+
*
|
|
6
|
+
* Stores original MP4/video files for on-demand decoding
|
|
7
|
+
* Supports range reads for GOP-level access
|
|
8
|
+
*/
|
|
9
|
+
export declare class ResourceCache {
|
|
10
|
+
readonly opfsManager: OPFSManager;
|
|
11
|
+
readonly projectId: string;
|
|
12
|
+
constructor(opfsManager: OPFSManager, projectId: string);
|
|
13
|
+
init(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Write resource to OPFS (streaming)
|
|
16
|
+
*/
|
|
17
|
+
writeResource(resourceId: string, stream: ReadableStream<Uint8Array>): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Read byte range from resource (for GOP-level access)
|
|
20
|
+
*/
|
|
21
|
+
readRange(resourceId: string, start: number, end: number): Promise<ArrayBuffer>;
|
|
22
|
+
/**
|
|
23
|
+
* Check if resource exists in OPFS
|
|
24
|
+
*/
|
|
25
|
+
hasResource(resourceId: string): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Get resource file size
|
|
28
|
+
*/
|
|
29
|
+
getResourceSize(resourceId: string): Promise<number>;
|
|
30
|
+
/**
|
|
31
|
+
* Delete a resource from OPFS
|
|
32
|
+
*/
|
|
33
|
+
deleteResource(resourceId: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Clear all resources for current project
|
|
36
|
+
*/
|
|
37
|
+
clear(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* LRU eviction to free up space
|
|
40
|
+
* Note: This is a simplified implementation
|
|
41
|
+
* Full implementation would track access times in IndexedDB
|
|
42
|
+
*/
|
|
43
|
+
evictLRU(_targetBytes: number): Promise<void>;
|
|
44
|
+
private getFileName;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=ResourceCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResourceCache.d.ts","sourceRoot":"","sources":["../../../src/cache/resource/ResourceCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAG1D;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM;IAKjD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;OAEG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1F;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAWrF;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWvD;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAW1D;;OAEG;IACG,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;;;OAIG;IACG,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD,OAAO,CAAC,WAAW;CAIpB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
class ResourceCache {
|
|
2
|
+
opfsManager;
|
|
3
|
+
projectId;
|
|
4
|
+
constructor(opfsManager, projectId) {
|
|
5
|
+
this.opfsManager = opfsManager;
|
|
6
|
+
this.projectId = projectId;
|
|
7
|
+
}
|
|
8
|
+
async init() {
|
|
9
|
+
await this.opfsManager.init();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Write resource to OPFS (streaming)
|
|
13
|
+
*/
|
|
14
|
+
async writeResource(resourceId, stream) {
|
|
15
|
+
const fileName = this.getFileName(resourceId);
|
|
16
|
+
const path = {
|
|
17
|
+
projectId: this.projectId,
|
|
18
|
+
prefix: "resource",
|
|
19
|
+
fileName
|
|
20
|
+
};
|
|
21
|
+
await this.opfsManager.writeFile(path, stream);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read byte range from resource (for GOP-level access)
|
|
25
|
+
*/
|
|
26
|
+
async readRange(resourceId, start, end) {
|
|
27
|
+
const fileName = this.getFileName(resourceId);
|
|
28
|
+
const path = {
|
|
29
|
+
projectId: this.projectId,
|
|
30
|
+
prefix: "resource",
|
|
31
|
+
fileName
|
|
32
|
+
};
|
|
33
|
+
return await this.opfsManager.readRange(path, start, end);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if resource exists in OPFS
|
|
37
|
+
*/
|
|
38
|
+
async hasResource(resourceId) {
|
|
39
|
+
const fileName = this.getFileName(resourceId);
|
|
40
|
+
const path = {
|
|
41
|
+
projectId: this.projectId,
|
|
42
|
+
prefix: "resource",
|
|
43
|
+
fileName
|
|
44
|
+
};
|
|
45
|
+
return await this.opfsManager.exists(path);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get resource file size
|
|
49
|
+
*/
|
|
50
|
+
async getResourceSize(resourceId) {
|
|
51
|
+
const fileName = this.getFileName(resourceId);
|
|
52
|
+
const path = {
|
|
53
|
+
projectId: this.projectId,
|
|
54
|
+
prefix: "resource",
|
|
55
|
+
fileName
|
|
56
|
+
};
|
|
57
|
+
return await this.opfsManager.getFileSize(path);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Delete a resource from OPFS
|
|
61
|
+
*/
|
|
62
|
+
async deleteResource(resourceId) {
|
|
63
|
+
const fileName = this.getFileName(resourceId);
|
|
64
|
+
const path = {
|
|
65
|
+
projectId: this.projectId,
|
|
66
|
+
prefix: "resource",
|
|
67
|
+
fileName
|
|
68
|
+
};
|
|
69
|
+
await this.opfsManager.deleteFile(path);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Clear all resources for current project
|
|
73
|
+
*/
|
|
74
|
+
async clear() {
|
|
75
|
+
await this.opfsManager.deleteProjectDirectory(this.projectId, "resource");
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* LRU eviction to free up space
|
|
79
|
+
* Note: This is a simplified implementation
|
|
80
|
+
* Full implementation would track access times in IndexedDB
|
|
81
|
+
*/
|
|
82
|
+
async evictLRU(_targetBytes) {
|
|
83
|
+
console.warn("[ResourceCache] LRU eviction not yet implemented");
|
|
84
|
+
}
|
|
85
|
+
getFileName(resourceId) {
|
|
86
|
+
return `${resourceId}.mp4`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
ResourceCache
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=ResourceCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResourceCache.js","sources":["../../../src/cache/resource/ResourceCache.ts"],"sourcesContent":["import { OPFSManager } from '../storage/opfs/OPFSManager';\nimport type { OPFSPath } from '../storage/opfs/types';\n\n/**\n * ResourceCache - Original video resource OPFS cache\n *\n * Stores original MP4/video files for on-demand decoding\n * Supports range reads for GOP-level access\n */\nexport class ResourceCache {\n readonly opfsManager: OPFSManager;\n readonly projectId: string;\n\n constructor(opfsManager: OPFSManager, projectId: string) {\n this.opfsManager = opfsManager;\n this.projectId = projectId;\n }\n\n async init(): Promise<void> {\n await this.opfsManager.init();\n }\n\n /**\n * Write resource to OPFS (streaming)\n */\n async writeResource(resourceId: string, stream: ReadableStream<Uint8Array>): Promise<void> {\n const fileName = this.getFileName(resourceId);\n const path: OPFSPath = {\n projectId: this.projectId,\n prefix: 'resource',\n fileName,\n };\n\n await this.opfsManager.writeFile(path, stream);\n }\n\n /**\n * Read byte range from resource (for GOP-level access)\n */\n async readRange(resourceId: string, start: number, end: number): Promise<ArrayBuffer> {\n const fileName = this.getFileName(resourceId);\n const path: OPFSPath = {\n projectId: this.projectId,\n prefix: 'resource',\n fileName,\n };\n\n return await this.opfsManager.readRange(path, start, end);\n }\n\n /**\n * Check if resource exists in OPFS\n */\n async hasResource(resourceId: string): Promise<boolean> {\n const fileName = this.getFileName(resourceId);\n const path: OPFSPath = {\n projectId: this.projectId,\n prefix: 'resource',\n fileName,\n };\n\n return await this.opfsManager.exists(path);\n }\n\n /**\n * Get resource file size\n */\n async getResourceSize(resourceId: string): Promise<number> {\n const fileName = this.getFileName(resourceId);\n const path: OPFSPath = {\n projectId: this.projectId,\n prefix: 'resource',\n fileName,\n };\n\n return await this.opfsManager.getFileSize(path);\n }\n\n /**\n * Delete a resource from OPFS\n */\n async deleteResource(resourceId: string): Promise<void> {\n const fileName = this.getFileName(resourceId);\n const path: OPFSPath = {\n projectId: this.projectId,\n prefix: 'resource',\n fileName,\n };\n\n await this.opfsManager.deleteFile(path);\n }\n\n /**\n * Clear all resources for current project\n */\n async clear(): Promise<void> {\n await this.opfsManager.deleteProjectDirectory(this.projectId, 'resource');\n }\n\n /**\n * LRU eviction to free up space\n * Note: This is a simplified implementation\n * Full implementation would track access times in IndexedDB\n */\n async evictLRU(_targetBytes: number): Promise<void> {\n // TODO: Implement LRU eviction when resource metadata tracking is added\n console.warn('[ResourceCache] LRU eviction not yet implemented');\n }\n\n private getFileName(resourceId: string): string {\n // Simple filename mapping - could be enhanced with extension detection\n return `${resourceId}.mp4`;\n }\n}\n"],"names":[],"mappings":"AASO,MAAM,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,EAET,YAAY,aAA0B,WAAmB;AACvD,SAAK,cAAc;AACnB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,KAAK,YAAY,KAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,YAAoB,QAAmD;AACzF,UAAM,WAAW,KAAK,YAAY,UAAU;AAC5C,UAAM,OAAiB;AAAA,MACrB,WAAW,KAAK;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IAAA;AAGF,UAAM,KAAK,YAAY,UAAU,MAAM,MAAM;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,YAAoB,OAAe,KAAmC;AACpF,UAAM,WAAW,KAAK,YAAY,UAAU;AAC5C,UAAM,OAAiB;AAAA,MACrB,WAAW,KAAK;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IAAA;AAGF,WAAO,MAAM,KAAK,YAAY,UAAU,MAAM,OAAO,GAAG;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,YAAsC;AACtD,UAAM,WAAW,KAAK,YAAY,UAAU;AAC5C,UAAM,OAAiB;AAAA,MACrB,WAAW,KAAK;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IAAA;AAGF,WAAO,MAAM,KAAK,YAAY,OAAO,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,YAAqC;AACzD,UAAM,WAAW,KAAK,YAAY,UAAU;AAC5C,UAAM,OAAiB;AAAA,MACrB,WAAW,KAAK;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IAAA;AAGF,WAAO,MAAM,KAAK,YAAY,YAAY,IAAI;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,YAAmC;AACtD,UAAM,WAAW,KAAK,YAAY,UAAU;AAC5C,UAAM,OAAiB;AAAA,MACrB,WAAW,KAAK;AAAA,MAChB,QAAQ;AAAA,MACR;AAAA,IAAA;AAGF,UAAM,KAAK,YAAY,WAAW,IAAI;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,UAAM,KAAK,YAAY,uBAAuB,KAAK,WAAW,UAAU;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAS,cAAqC;AAElD,YAAQ,KAAK,kDAAkD;AAAA,EACjE;AAAA,EAEQ,YAAY,YAA4B;AAE9C,WAAO,GAAG,UAAU;AAAA,EACtB;AACF;"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ChunkBatch } from '../opfs/types';
|
|
2
|
+
|
|
3
|
+
export type { ChunkBatch };
|
|
4
|
+
export interface ChunkRecord {
|
|
5
|
+
projectId: string;
|
|
6
|
+
clipId: string;
|
|
7
|
+
track: 'video' | 'audio';
|
|
8
|
+
fileName: string;
|
|
9
|
+
batches: ChunkBatch[];
|
|
10
|
+
lastAccess: number;
|
|
11
|
+
totalBytes: number;
|
|
12
|
+
isComplete: boolean;
|
|
13
|
+
expectedDurationUs?: number;
|
|
14
|
+
metadata?: {
|
|
15
|
+
codec?: string;
|
|
16
|
+
description?: Uint8Array;
|
|
17
|
+
codedWidth?: number;
|
|
18
|
+
codedHeight?: number;
|
|
19
|
+
displayAspectWidth?: number;
|
|
20
|
+
displayAspectHeight?: number;
|
|
21
|
+
colorSpace?: VideoColorSpaceInit;
|
|
22
|
+
hardwareAcceleration?: HardwareAcceleration;
|
|
23
|
+
optimizeForLatency?: boolean;
|
|
24
|
+
sampleRate?: number;
|
|
25
|
+
numberOfChannels?: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* IndexedDB storage for L2 cache metadata
|
|
30
|
+
* Handles all database operations for chunk records
|
|
31
|
+
*/
|
|
32
|
+
export declare class ChunkRecordStore {
|
|
33
|
+
private db;
|
|
34
|
+
private initPromise;
|
|
35
|
+
init(): Promise<void>;
|
|
36
|
+
private initStorage;
|
|
37
|
+
/**
|
|
38
|
+
* Get a chunk record
|
|
39
|
+
*/
|
|
40
|
+
getRecord(projectId: string, clipId: string, track: string): Promise<ChunkRecord | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Put/update a chunk record
|
|
43
|
+
*/
|
|
44
|
+
putRecord(record: ChunkRecord): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Delete a chunk record
|
|
47
|
+
*/
|
|
48
|
+
deleteRecord(projectId: string, clipId: string, track: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Update last access time for a record
|
|
51
|
+
*/
|
|
52
|
+
updateLastAccess(projectId: string, clipId: string, track: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Collect all records for a specific clip
|
|
55
|
+
*/
|
|
56
|
+
collectRecordsByClipId(projectId: string, clipId: string): Promise<ChunkRecord[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Get all records for a specific project
|
|
59
|
+
*/
|
|
60
|
+
getRecordsByProjectId(projectId: string): Promise<ChunkRecord[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Get all records sorted by lastAccess (for quota enforcement)
|
|
63
|
+
*/
|
|
64
|
+
getAllRecordsSortedByAccess(): Promise<ChunkRecord[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Get all records (for project statistics)
|
|
67
|
+
*/
|
|
68
|
+
getAllRecords(): Promise<ChunkRecord[]>;
|
|
69
|
+
/**
|
|
70
|
+
* Clear all data from chunks and meta stores
|
|
71
|
+
*/
|
|
72
|
+
clear(): Promise<void>;
|
|
73
|
+
private promisifyRequest;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=ChunkRecordStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChunkRecordStore.d.ts","sourceRoot":"","sources":["../../../../src/cache/storage/indexeddb/ChunkRecordStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,UAAU,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,UAAU,CAAC,EAAE,mBAAmB,CAAC;QACjC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;QAC5C,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,EAAE,CAA4B;IACtC,OAAO,CAAC,WAAW,CAA8B;IAE3C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAOb,WAAW;IAqCzB;;OAEG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAU9F;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAcnD;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQnF;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAavF;;OAEG;IACG,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA4BvF;;OAEG;IACG,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAatE;;OAEG;IACG,2BAA2B,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAyB3D;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAwB7C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,OAAO,CAAC,gBAAgB;CAMzB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { OPFSPrefix, OPFSPath } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OPFSManager - Unified OPFS management infrastructure
|
|
5
|
+
*
|
|
6
|
+
* Supports:
|
|
7
|
+
* - Directory isolation by prefix (l2/resource)
|
|
8
|
+
* - Range reads for on-demand decoding
|
|
9
|
+
* - Streaming writes
|
|
10
|
+
* - File operations (exists, delete, getFileSize)
|
|
11
|
+
*/
|
|
12
|
+
export declare class OPFSManager {
|
|
13
|
+
private opfsRoot;
|
|
14
|
+
private initPromise;
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
private initStorage;
|
|
17
|
+
/**
|
|
18
|
+
* Get project directory by prefix
|
|
19
|
+
*/
|
|
20
|
+
getProjectDir(projectId: string, prefix: OPFSPrefix): Promise<FileSystemDirectoryHandle>;
|
|
21
|
+
/**
|
|
22
|
+
* Write file (ArrayBuffer or ReadableStream)
|
|
23
|
+
*/
|
|
24
|
+
writeFile(path: OPFSPath, data: ArrayBuffer | ReadableStream<Uint8Array>): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Read entire file
|
|
27
|
+
*/
|
|
28
|
+
readFile(path: OPFSPath): Promise<ArrayBuffer>;
|
|
29
|
+
/**
|
|
30
|
+
* Read byte range from file (for on-demand decoding)
|
|
31
|
+
*/
|
|
32
|
+
readRange(path: OPFSPath, start: number, end: number): Promise<ArrayBuffer>;
|
|
33
|
+
/**
|
|
34
|
+
* Delete file
|
|
35
|
+
*/
|
|
36
|
+
deleteFile(path: OPFSPath): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Check if file exists
|
|
39
|
+
*/
|
|
40
|
+
exists(path: OPFSPath): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Get file size
|
|
43
|
+
*/
|
|
44
|
+
getFileSize(path: OPFSPath): Promise<number>;
|
|
45
|
+
/**
|
|
46
|
+
* Create writable stream for streaming writes
|
|
47
|
+
*/
|
|
48
|
+
createWritableStream(path: OPFSPath): Promise<FileSystemWritableFileStream>;
|
|
49
|
+
/**
|
|
50
|
+
* Delete entire project directory
|
|
51
|
+
*/
|
|
52
|
+
deleteProjectDirectory(projectId: string, prefix: OPFSPrefix): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=OPFSManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OPFSManager.d.ts","sourceRoot":"","sources":["../../../../src/cache/storage/opfs/OPFSManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEpD;;;;;;;;GAQG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,WAAW,CAA8B;IAE3C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAOb,WAAW;IAIzB;;OAEG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAS9F;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B9F;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC;IAOpD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAQjF;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/C;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAa9C;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAOlD;;OAEG;IACG,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAMjF;;OAEG;IACG,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;CAYnF"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
class OPFSManager {
|
|
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
|
+
* Get project directory by prefix
|
|
14
|
+
*/
|
|
15
|
+
async getProjectDir(projectId, prefix) {
|
|
16
|
+
if (!this.opfsRoot) {
|
|
17
|
+
throw new Error("[OPFSManager] Not initialized");
|
|
18
|
+
}
|
|
19
|
+
const dirName = `meframe-${prefix}-${projectId}`;
|
|
20
|
+
return this.opfsRoot.getDirectoryHandle(dirName, { create: true });
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Write file (ArrayBuffer or ReadableStream)
|
|
24
|
+
*/
|
|
25
|
+
async writeFile(path, data) {
|
|
26
|
+
const projectDir = await this.getProjectDir(path.projectId, path.prefix);
|
|
27
|
+
const fileHandle = await projectDir.getFileHandle(path.fileName, { create: true });
|
|
28
|
+
const writable = await fileHandle.createWritable();
|
|
29
|
+
if (data instanceof ArrayBuffer) {
|
|
30
|
+
await writable.write(data);
|
|
31
|
+
} else {
|
|
32
|
+
const reader = data.getReader();
|
|
33
|
+
try {
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = await reader.read();
|
|
36
|
+
if (done) break;
|
|
37
|
+
if (value) {
|
|
38
|
+
const buffer = value.buffer.slice(
|
|
39
|
+
value.byteOffset,
|
|
40
|
+
value.byteOffset + value.byteLength
|
|
41
|
+
);
|
|
42
|
+
await writable.write(buffer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} finally {
|
|
46
|
+
reader.releaseLock();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
await writable.close();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read entire file
|
|
53
|
+
*/
|
|
54
|
+
async readFile(path) {
|
|
55
|
+
const projectDir = await this.getProjectDir(path.projectId, path.prefix);
|
|
56
|
+
const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });
|
|
57
|
+
const file = await fileHandle.getFile();
|
|
58
|
+
return await file.arrayBuffer();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read byte range from file (for on-demand decoding)
|
|
62
|
+
*/
|
|
63
|
+
async readRange(path, start, end) {
|
|
64
|
+
const projectDir = await this.getProjectDir(path.projectId, path.prefix);
|
|
65
|
+
const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });
|
|
66
|
+
const file = await fileHandle.getFile();
|
|
67
|
+
const slice = file.slice(start, end);
|
|
68
|
+
return await slice.arrayBuffer();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Delete file
|
|
72
|
+
*/
|
|
73
|
+
async deleteFile(path) {
|
|
74
|
+
try {
|
|
75
|
+
const projectDir = await this.getProjectDir(path.projectId, path.prefix);
|
|
76
|
+
await projectDir.removeEntry(path.fileName);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error?.name !== "NotFoundError") {
|
|
79
|
+
console.warn(`[OPFSManager] Failed to delete file ${path.fileName}:`, error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if file exists
|
|
85
|
+
*/
|
|
86
|
+
async exists(path) {
|
|
87
|
+
try {
|
|
88
|
+
const projectDir = await this.getProjectDir(path.projectId, path.prefix);
|
|
89
|
+
await projectDir.getFileHandle(path.fileName, { create: false });
|
|
90
|
+
return true;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error?.name === "NotFoundError") {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get file size
|
|
100
|
+
*/
|
|
101
|
+
async getFileSize(path) {
|
|
102
|
+
const projectDir = await this.getProjectDir(path.projectId, path.prefix);
|
|
103
|
+
const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });
|
|
104
|
+
const file = await fileHandle.getFile();
|
|
105
|
+
return file.size;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Create writable stream for streaming writes
|
|
109
|
+
*/
|
|
110
|
+
async createWritableStream(path) {
|
|
111
|
+
const projectDir = await this.getProjectDir(path.projectId, path.prefix);
|
|
112
|
+
const fileHandle = await projectDir.getFileHandle(path.fileName, { create: true });
|
|
113
|
+
return await fileHandle.createWritable();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Delete entire project directory
|
|
117
|
+
*/
|
|
118
|
+
async deleteProjectDirectory(projectId, prefix) {
|
|
119
|
+
if (!this.opfsRoot) return;
|
|
120
|
+
try {
|
|
121
|
+
const dirName = `meframe-${prefix}-${projectId}`;
|
|
122
|
+
await this.opfsRoot.removeEntry(dirName, { recursive: true });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error?.name !== "NotFoundError") {
|
|
125
|
+
console.warn(`[OPFSManager] Failed to remove directory ${prefix}-${projectId}:`, error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export {
|
|
131
|
+
OPFSManager
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=OPFSManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OPFSManager.js","sources":["../../../../src/cache/storage/opfs/OPFSManager.ts"],"sourcesContent":["import type { OPFSPrefix, OPFSPath } from './types';\n\n/**\n * OPFSManager - Unified OPFS management infrastructure\n *\n * Supports:\n * - Directory isolation by prefix (l2/resource)\n * - Range reads for on-demand decoding\n * - Streaming writes\n * - File operations (exists, delete, getFileSize)\n */\nexport class OPFSManager {\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 * Get project directory by prefix\n */\n async getProjectDir(projectId: string, prefix: OPFSPrefix): Promise<FileSystemDirectoryHandle> {\n if (!this.opfsRoot) {\n throw new Error('[OPFSManager] Not initialized');\n }\n\n const dirName = `meframe-${prefix}-${projectId}`;\n return this.opfsRoot.getDirectoryHandle(dirName, { create: true });\n }\n\n /**\n * Write file (ArrayBuffer or ReadableStream)\n */\n async writeFile(path: OPFSPath, data: ArrayBuffer | ReadableStream<Uint8Array>): Promise<void> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: true });\n const writable = await fileHandle.createWritable();\n\n if (data instanceof ArrayBuffer) {\n await writable.write(data);\n } else {\n // Stream mode\n const reader = data.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n // Convert Uint8Array to ArrayBuffer for FileSystemWritableFileStream\n const buffer = value.buffer.slice(\n value.byteOffset,\n value.byteOffset + value.byteLength\n );\n await writable.write(buffer as ArrayBuffer);\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n await writable.close();\n }\n\n /**\n * Read entire file\n */\n async readFile(path: OPFSPath): Promise<ArrayBuffer> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n return await file.arrayBuffer();\n }\n\n /**\n * Read byte range from file (for on-demand decoding)\n */\n async readRange(path: OPFSPath, start: number, end: number): Promise<ArrayBuffer> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n const slice = file.slice(start, end);\n return await slice.arrayBuffer();\n }\n\n /**\n * Delete file\n */\n async deleteFile(path: OPFSPath): Promise<void> {\n try {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n await projectDir.removeEntry(path.fileName);\n } catch (error) {\n if ((error as any)?.name !== 'NotFoundError') {\n console.warn(`[OPFSManager] Failed to delete file ${path.fileName}:`, error);\n }\n }\n }\n\n /**\n * Check if file exists\n */\n async exists(path: OPFSPath): Promise<boolean> {\n try {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n await projectDir.getFileHandle(path.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 * Get file size\n */\n async getFileSize(path: OPFSPath): Promise<number> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n return file.size;\n }\n\n /**\n * Create writable stream for streaming writes\n */\n async createWritableStream(path: OPFSPath): Promise<FileSystemWritableFileStream> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: true });\n return await fileHandle.createWritable();\n }\n\n /**\n * Delete entire project directory\n */\n async deleteProjectDirectory(projectId: string, prefix: OPFSPrefix): Promise<void> {\n if (!this.opfsRoot) return;\n\n try {\n const dirName = `meframe-${prefix}-${projectId}`;\n await this.opfsRoot.removeEntry(dirName, { recursive: true });\n } catch (error) {\n if ((error as any)?.name !== 'NotFoundError') {\n console.warn(`[OPFSManager] Failed to remove directory ${prefix}-${projectId}:`, error);\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TimeUs } from '../../../model/types';
|
|
2
|
+
|
|
3
|
+
export type OPFSPrefix = 'l2' | 'resource';
|
|
4
|
+
export interface OPFSPath {
|
|
5
|
+
projectId: string;
|
|
6
|
+
prefix: OPFSPrefix;
|
|
7
|
+
fileName: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ChunkBatch {
|
|
10
|
+
startUs: TimeUs;
|
|
11
|
+
durationUs: TimeUs;
|
|
12
|
+
byteOffset: number;
|
|
13
|
+
byteLength: number;
|
|
14
|
+
type: 'key' | 'delta';
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cache/storage/opfs/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,UAAU,CAAC;AAE3C,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;CACvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAU9C;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAU9C;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,cAoF5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc;IACzB;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8BH;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;qCAyBmC,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE;;;;;;;;;;;;;;IAezD;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BK,CAAC"}
|
package/dist/config/defaults.js
CHANGED
|
@@ -64,8 +64,27 @@ const DEFAULT_CONFIG = {
|
|
|
64
64
|
audio: {}
|
|
65
65
|
},
|
|
66
66
|
cache: {
|
|
67
|
-
l1: {
|
|
68
|
-
|
|
67
|
+
l1: {
|
|
68
|
+
windowSizeUs: 6e6,
|
|
69
|
+
// ±3 seconds window
|
|
70
|
+
maxMemoryMB: 200
|
|
71
|
+
},
|
|
72
|
+
l2: {
|
|
73
|
+
autoFill: true,
|
|
74
|
+
fillPriority: "low"
|
|
75
|
+
},
|
|
76
|
+
opfs: {
|
|
77
|
+
enabled: true,
|
|
78
|
+
resource: {
|
|
79
|
+
maxSizeMB: 5120,
|
|
80
|
+
// 5GB for original resources
|
|
81
|
+
evictionPolicy: "lru"
|
|
82
|
+
},
|
|
83
|
+
l2: {
|
|
84
|
+
maxSizeMB: 2048
|
|
85
|
+
// 2GB for encoded chunks
|
|
86
|
+
}
|
|
87
|
+
}
|
|
69
88
|
},
|
|
70
89
|
mux: {}
|
|
71
90
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sources":["../../src/config/defaults.ts"],"sourcesContent":["import type { ResolvedConfig } from './types';\nimport { CANVAS_PRESETS } from './presets';\n\n/**\n * Detect if running in development mode\n * In dev mode, workers are loaded from source (Vite dev server)\n * In prod mode, workers are loaded from dist\n */\nconst isDev = import.meta.env?.DEV ?? false;\n\n/**\n * Default configuration values based on mobile 1080p30 memory budget\n *\n * Note: Canvas dimensions are fixed for the entire project.\n * All video clips will be scaled/fitted to this resolution.\n * Choose based on your primary content type:\n * - 720×1280: Mobile vertical video\n * - 1080×1920: HD vertical video (TikTok, Instagram Reels) ← default\n * - 1920×1080: HD horizontal video (YouTube)\n */\nexport const DEFAULT_CONFIG: ResolvedConfig = {\n global: {\n projectId: `project-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,\n logLevel: 'info',\n enablePerfMonitor: false,\n defaultCanvasWidth: CANVAS_PRESETS.MOBILE_PORTRAIT.width,\n defaultCanvasHeight: CANVAS_PRESETS.MOBILE_PORTRAIT.height,\n defaultFps: 30,\n workerPath: isDev ? '/src' : '/meframe-workers',\n workerExtension: isDev ? '.ts' : '.js',\n },\n\n load: {\n maxConcurrent: 2,\n backpressure: {\n highWaterMark: 64 * 1024, // 64 KB\n stallTimeoutMs: 500,\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n window: {\n maxInflightPerClip: 1,\n maxInflight: 4,\n chunkSize: 1 * 1024 * 1024, // 1 MB\n },\n },\n\n demux: {\n backpressure: {\n highWaterMark: 10, // 10 EncodedChunks\n },\n },\n\n decode: {\n video: {\n backpressure: {\n highWaterMark: 4, // 4 EncodedVideoChunks\n decodeQueueThreshold: 16, // Pause when decoder has 16+ chunks\n },\n maxGOPs: 4, // Cache 4 GOPs for seeking\n },\n audio: {\n backpressure: {\n highWaterMark: 20, // 20 EncodedAudioChunks\n },\n },\n },\n\n compose: {\n visual: {},\n audio: {\n enableDucking: false, // Default: no ducking\n },\n },\n\n encode: {\n video: {},\n audio: {},\n },\n\n cache: {\n l1: {},\n l2: {},\n },\n\n mux: {},\n};\n\n/**\n * Tuning presets for common scenarios\n */\nexport const TUNING_PRESETS = {\n /**\n * Low-latency live streaming\n * Minimize buffering for real-time playback\n */\n lowLatency: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 16 * 1024, // 16 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 3,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 2,\n decodeQueueThreshold: 4,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 10,\n },\n },\n },\n },\n\n /**\n * 4K60 playback/export\n * Larger buffers for high bitrate content\n */\n highQuality: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 256 * 1024, // 256 KB\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 20,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 8,\n decodeQueueThreshold: 24,\n },\n codecHints: ['h264', 'hevc'] as ('h264' | 'hevc')[],\n },\n audio: {\n backpressure: {\n highWaterMark: 30,\n },\n },\n },\n cache: {\n l2: {\n quotaGb: 1,\n },\n },\n },\n\n /**\n * Batch offline transcode\n * Maximum throughput, memory not a concern\n */\n offline: {\n global: {\n logLevel: 'warn' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 512 * 1024, // 512 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 25,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 12,\n decodeQueueThreshold: 32,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 40,\n },\n },\n },\n },\n} as const;\n"],"names":[],"mappings":";AAoBO,MAAM,iBAAiC;AAAA,EAC5C,QAAQ;AAAA,IACN,WAAW,WAAW,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,IAC1E,UAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,oBAAoB,eAAe,gBAAgB;AAAA,IACnD,qBAAqB,eAAe,gBAAgB;AAAA,IACpD,YAAY;AAAA,IACZ,YAA6B;AAAA,IAC7B,iBAAiC;AAAA,EAAA;AAAA,EAGnC,MAAM;AAAA,IACJ,eAAe;AAAA,IACf,cAAc;AAAA,MACZ,eAAe,KAAK;AAAA;AAAA,MACpB,gBAAgB;AAAA,IAAA;AAAA,IAElB,OAAO;AAAA,MACL,aAAa;AAAA,MACb,aAAa;AAAA,IAAA;AAAA,IAEf,QAAQ;AAAA,MACN,oBAAoB;AAAA,MACpB,aAAa;AAAA,MACb,WAAW,IAAI,OAAO;AAAA;AAAA,IAAA;AAAA,EACxB;AAAA,EAGF,OAAO;AAAA,IACL,cAAc;AAAA,MACZ,eAAe;AAAA;AAAA,IAAA;AAAA,EACjB;AAAA,EAGF,QAAQ;AAAA,IACN,OAAO;AAAA,MACL,cAAc;AAAA,QACZ,eAAe;AAAA;AAAA,QACf,sBAAsB;AAAA;AAAA,MAAA;AAAA,MAExB,SAAS;AAAA;AAAA,IAAA;AAAA,IAEX,OAAO;AAAA,MACL,cAAc;AAAA,QACZ,eAAe;AAAA;AAAA,MAAA;AAAA,IACjB;AAAA,EACF;AAAA,EAGF,SAAS;AAAA,IACP,QAAQ,CAAA;AAAA,IACR,OAAO;AAAA,MACL,eAAe;AAAA;AAAA,IAAA;AAAA,EACjB;AAAA,EAGF,QAAQ;AAAA,IACN,OAAO,CAAA;AAAA,IACP,OAAO,CAAA;AAAA,EAAC;AAAA,EAGV,OAAO;AAAA,IACL,IAAI,
|
|
1
|
+
{"version":3,"file":"defaults.js","sources":["../../src/config/defaults.ts"],"sourcesContent":["import type { ResolvedConfig } from './types';\nimport { CANVAS_PRESETS } from './presets';\n\n/**\n * Detect if running in development mode\n * In dev mode, workers are loaded from source (Vite dev server)\n * In prod mode, workers are loaded from dist\n */\nconst isDev = import.meta.env?.DEV ?? false;\n\n/**\n * Default configuration values based on mobile 1080p30 memory budget\n *\n * Note: Canvas dimensions are fixed for the entire project.\n * All video clips will be scaled/fitted to this resolution.\n * Choose based on your primary content type:\n * - 720×1280: Mobile vertical video\n * - 1080×1920: HD vertical video (TikTok, Instagram Reels) ← default\n * - 1920×1080: HD horizontal video (YouTube)\n */\nexport const DEFAULT_CONFIG: ResolvedConfig = {\n global: {\n projectId: `project-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,\n logLevel: 'info',\n enablePerfMonitor: false,\n defaultCanvasWidth: CANVAS_PRESETS.MOBILE_PORTRAIT.width,\n defaultCanvasHeight: CANVAS_PRESETS.MOBILE_PORTRAIT.height,\n defaultFps: 30,\n workerPath: isDev ? '/src' : '/meframe-workers',\n workerExtension: isDev ? '.ts' : '.js',\n },\n\n load: {\n maxConcurrent: 2,\n backpressure: {\n highWaterMark: 64 * 1024, // 64 KB\n stallTimeoutMs: 500,\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n window: {\n maxInflightPerClip: 1,\n maxInflight: 4,\n chunkSize: 1 * 1024 * 1024, // 1 MB\n },\n },\n\n demux: {\n backpressure: {\n highWaterMark: 10, // 10 EncodedChunks\n },\n },\n\n decode: {\n video: {\n backpressure: {\n highWaterMark: 4, // 4 EncodedVideoChunks\n decodeQueueThreshold: 16, // Pause when decoder has 16+ chunks\n },\n maxGOPs: 4, // Cache 4 GOPs for seeking\n },\n audio: {\n backpressure: {\n highWaterMark: 20, // 20 EncodedAudioChunks\n },\n },\n },\n\n compose: {\n visual: {},\n audio: {\n enableDucking: false, // Default: no ducking\n },\n },\n\n encode: {\n video: {},\n audio: {},\n },\n\n cache: {\n l1: {\n windowSizeUs: 6_000_000, // ±3 seconds window\n maxMemoryMB: 200,\n },\n l2: {\n autoFill: true,\n fillPriority: 'low' as const,\n },\n opfs: {\n enabled: true,\n resource: {\n maxSizeMB: 5120, // 5GB for original resources\n evictionPolicy: 'lru' as const,\n },\n l2: {\n maxSizeMB: 2048, // 2GB for encoded chunks\n },\n },\n },\n\n mux: {},\n};\n\n/**\n * Tuning presets for common scenarios\n */\nexport const TUNING_PRESETS = {\n /**\n * Low-latency live streaming\n * Minimize buffering for real-time playback\n */\n lowLatency: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 16 * 1024, // 16 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 3,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 2,\n decodeQueueThreshold: 4,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 10,\n },\n },\n },\n },\n\n /**\n * 4K60 playback/export\n * Larger buffers for high bitrate content\n */\n highQuality: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 256 * 1024, // 256 KB\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 20,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 8,\n decodeQueueThreshold: 24,\n },\n codecHints: ['h264', 'hevc'] as ('h264' | 'hevc')[],\n },\n audio: {\n backpressure: {\n highWaterMark: 30,\n },\n },\n },\n cache: {\n l2: {\n quotaGb: 1,\n },\n },\n },\n\n /**\n * Batch offline transcode\n * Maximum throughput, memory not a concern\n */\n offline: {\n global: {\n logLevel: 'warn' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 512 * 1024, // 512 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 25,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 12,\n decodeQueueThreshold: 32,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 40,\n },\n },\n },\n },\n} as const;\n"],"names":[],"mappings":"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"}
|