@meframe/core 0.0.3 → 0.0.5

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