@meframe/core 0.0.28 → 0.0.30-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/Meframe.d.ts +2 -13
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +6 -100
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +35 -19
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +223 -134
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/l1/VideoL1Cache.d.ts +15 -2
  10. package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
  11. package/dist/cache/l1/VideoL1Cache.js +58 -38
  12. package/dist/cache/l1/VideoL1Cache.js.map +1 -1
  13. package/dist/cache/l2/L2Cache.d.ts.map +1 -1
  14. package/dist/cache/l2/L2Cache.js +5 -5
  15. package/dist/cache/l2/L2Cache.js.map +1 -1
  16. package/dist/cache/l2/L2OPFSStore.d.ts +37 -0
  17. package/dist/cache/l2/L2OPFSStore.d.ts.map +1 -0
  18. package/dist/cache/l2/L2OPFSStore.js +89 -0
  19. package/dist/cache/l2/L2OPFSStore.js.map +1 -0
  20. package/dist/cache/resource/AudioSampleCache.d.ts +52 -0
  21. package/dist/cache/resource/AudioSampleCache.d.ts.map +1 -0
  22. package/dist/cache/resource/AudioSampleCache.js +69 -0
  23. package/dist/cache/resource/AudioSampleCache.js.map +1 -0
  24. package/dist/cache/resource/ImageBitmapCache.d.ts +65 -0
  25. package/dist/cache/resource/ImageBitmapCache.d.ts.map +1 -0
  26. package/dist/cache/resource/ImageBitmapCache.js +101 -0
  27. package/dist/cache/resource/ImageBitmapCache.js.map +1 -0
  28. package/dist/cache/resource/MP4IndexCache.d.ts +48 -0
  29. package/dist/cache/resource/MP4IndexCache.d.ts.map +1 -0
  30. package/dist/cache/resource/MP4IndexCache.js +104 -0
  31. package/dist/cache/resource/MP4IndexCache.js.map +1 -0
  32. package/dist/cache/resource/ResourceCache.d.ts +46 -0
  33. package/dist/cache/resource/ResourceCache.d.ts.map +1 -0
  34. package/dist/cache/resource/ResourceCache.js +92 -0
  35. package/dist/cache/resource/ResourceCache.js.map +1 -0
  36. package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts +75 -0
  37. package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts.map +1 -0
  38. package/dist/cache/{l2/IndexedDBStore.js → storage/indexeddb/ChunkRecordStore.js} +3 -3
  39. package/dist/cache/storage/indexeddb/ChunkRecordStore.js.map +1 -0
  40. package/dist/cache/storage/opfs/OPFSManager.d.ts +54 -0
  41. package/dist/cache/storage/opfs/OPFSManager.d.ts.map +1 -0
  42. package/dist/cache/storage/opfs/OPFSManager.js +133 -0
  43. package/dist/cache/storage/opfs/OPFSManager.js.map +1 -0
  44. package/dist/cache/storage/opfs/types.d.ts +16 -0
  45. package/dist/cache/storage/opfs/types.d.ts.map +1 -0
  46. package/dist/config/defaults.d.ts.map +1 -1
  47. package/dist/config/defaults.js +21 -2
  48. package/dist/config/defaults.js.map +1 -1
  49. package/dist/config/types.d.ts +28 -0
  50. package/dist/config/types.d.ts.map +1 -1
  51. package/dist/controllers/ExportController.d.ts +16 -0
  52. package/dist/controllers/ExportController.d.ts.map +1 -0
  53. package/dist/controllers/ExportController.js +44 -0
  54. package/dist/controllers/ExportController.js.map +1 -0
  55. package/dist/controllers/PlaybackController.d.ts +28 -4
  56. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  57. package/dist/controllers/PlaybackController.js +117 -52
  58. package/dist/controllers/PlaybackController.js.map +1 -1
  59. package/dist/controllers/index.d.ts +2 -3
  60. package/dist/controllers/index.d.ts.map +1 -1
  61. package/dist/controllers/types.d.ts +0 -28
  62. package/dist/controllers/types.d.ts.map +1 -1
  63. package/dist/event/events.d.ts +8 -0
  64. package/dist/event/events.d.ts.map +1 -1
  65. package/dist/event/events.js +1 -0
  66. package/dist/event/events.js.map +1 -1
  67. package/dist/model/CompositionModel.d.ts.map +1 -1
  68. package/dist/model/CompositionModel.js +11 -6
  69. package/dist/model/CompositionModel.js.map +1 -1
  70. package/dist/model/RcFrame.d.ts +2 -0
  71. package/dist/model/RcFrame.d.ts.map +1 -1
  72. package/dist/model/RcFrame.js +3 -0
  73. package/dist/model/RcFrame.js.map +1 -1
  74. package/dist/orchestrator/ExportScheduler.d.ts +35 -0
  75. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -0
  76. package/dist/orchestrator/ExportScheduler.js +241 -0
  77. package/dist/orchestrator/ExportScheduler.js.map +1 -0
  78. package/dist/orchestrator/GlobalAudioSession.d.ts +21 -7
  79. package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
  80. package/dist/orchestrator/GlobalAudioSession.js +132 -140
  81. package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
  82. package/dist/orchestrator/OnDemandVideoSession.d.ts +73 -0
  83. package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -0
  84. package/dist/orchestrator/OnDemandVideoSession.js +281 -0
  85. package/dist/orchestrator/OnDemandVideoSession.js.map +1 -0
  86. package/dist/orchestrator/Orchestrator.d.ts +22 -17
  87. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  88. package/dist/orchestrator/Orchestrator.js +234 -301
  89. package/dist/orchestrator/Orchestrator.js.map +1 -1
  90. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  91. package/dist/orchestrator/VideoClipSession.js +3 -15
  92. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  93. package/dist/orchestrator/index.d.ts +0 -1
  94. package/dist/orchestrator/index.d.ts.map +1 -1
  95. package/dist/orchestrator/types.d.ts +4 -4
  96. package/dist/orchestrator/types.d.ts.map +1 -1
  97. package/dist/stages/compose/FilterProcessor.d.ts +1 -1
  98. package/dist/stages/compose/FilterProcessor.d.ts.map +1 -1
  99. package/dist/stages/compose/FilterProcessor.js +226 -0
  100. package/dist/stages/compose/FilterProcessor.js.map +1 -0
  101. package/dist/stages/compose/FrameRateConverter.d.ts +68 -0
  102. package/dist/stages/compose/FrameRateConverter.d.ts.map +1 -0
  103. package/dist/stages/compose/LayerRenderer.d.ts +1 -1
  104. package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
  105. package/dist/stages/compose/LayerRenderer.js +270 -0
  106. package/dist/stages/compose/LayerRenderer.js.map +1 -0
  107. package/dist/stages/compose/TransitionProcessor.d.ts +1 -1
  108. package/dist/stages/compose/TransitionProcessor.d.ts.map +1 -1
  109. package/dist/stages/compose/TransitionProcessor.js +189 -0
  110. package/dist/stages/compose/TransitionProcessor.js.map +1 -0
  111. package/dist/stages/compose/VideoComposer.d.ts +6 -4
  112. package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
  113. package/dist/stages/compose/VideoComposer.js +229 -0
  114. package/dist/stages/compose/VideoComposer.js.map +1 -0
  115. package/dist/stages/compose/text-renderers/animation-utils.js +76 -0
  116. package/dist/stages/compose/text-renderers/animation-utils.js.map +1 -0
  117. package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts +2 -2
  118. package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts.map +1 -1
  119. package/dist/stages/compose/text-renderers/basic-text-renderer.js +93 -0
  120. package/dist/stages/compose/text-renderers/basic-text-renderer.js.map +1 -0
  121. package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts +1 -1
  122. package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts.map +1 -1
  123. package/dist/stages/compose/text-renderers/character-ktv-renderer.js +132 -0
  124. package/dist/stages/compose/text-renderers/character-ktv-renderer.js.map +1 -0
  125. package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts +1 -1
  126. package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts.map +1 -1
  127. package/dist/stages/compose/text-renderers/word-by-word-renderer.js +128 -0
  128. package/dist/stages/compose/text-renderers/word-by-word-renderer.js.map +1 -0
  129. package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts +1 -1
  130. package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts.map +1 -1
  131. package/dist/stages/compose/text-renderers/word-fancy-renderer.js +135 -0
  132. package/dist/stages/compose/text-renderers/word-fancy-renderer.js.map +1 -0
  133. package/dist/stages/compose/text-utils/locale-detector.js +16 -0
  134. package/dist/stages/compose/text-utils/locale-detector.js.map +1 -0
  135. package/dist/stages/compose/text-utils/text-metrics.js +21 -0
  136. package/dist/stages/compose/text-utils/text-metrics.js.map +1 -0
  137. package/dist/stages/compose/text-utils/text-wrapper.js +225 -0
  138. package/dist/stages/compose/text-utils/text-wrapper.js.map +1 -0
  139. package/dist/stages/compose/types.d.ts +2 -1
  140. package/dist/stages/compose/types.d.ts.map +1 -1
  141. package/dist/stages/decode/BaseDecoder.js +0 -3
  142. package/dist/stages/decode/BaseDecoder.js.map +1 -1
  143. package/dist/stages/demux/MP4Demuxer.d.ts +5 -0
  144. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  145. package/dist/stages/demux/MP4Demuxer.js +281 -0
  146. package/dist/stages/demux/MP4Demuxer.js.map +1 -0
  147. package/dist/stages/demux/MP4IndexParser.d.ts +71 -0
  148. package/dist/stages/demux/MP4IndexParser.d.ts.map +1 -0
  149. package/dist/stages/demux/MP4IndexParser.js +416 -0
  150. package/dist/stages/demux/MP4IndexParser.js.map +1 -0
  151. package/dist/stages/demux/types.d.ts +48 -0
  152. package/dist/stages/demux/types.d.ts.map +1 -1
  153. package/dist/stages/encode/index.d.ts +0 -1
  154. package/dist/stages/encode/index.d.ts.map +1 -1
  155. package/dist/stages/load/ResourceLoader.d.ts +44 -2
  156. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  157. package/dist/stages/load/ResourceLoader.js +281 -37
  158. package/dist/stages/load/ResourceLoader.js.map +1 -1
  159. package/dist/stages/load/TaskManager.d.ts +6 -2
  160. package/dist/stages/load/TaskManager.d.ts.map +1 -1
  161. package/dist/stages/load/TaskManager.js +27 -4
  162. package/dist/stages/load/TaskManager.js.map +1 -1
  163. package/dist/stages/load/types.d.ts +7 -0
  164. package/dist/stages/load/types.d.ts.map +1 -1
  165. package/dist/stages/mux/MP4Muxer.d.ts +2 -2
  166. package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
  167. package/dist/stages/mux/MP4Muxer.js +24 -13
  168. package/dist/stages/mux/MP4Muxer.js.map +1 -1
  169. package/dist/stages/mux/MuxManager.d.ts +10 -21
  170. package/dist/stages/mux/MuxManager.d.ts.map +1 -1
  171. package/dist/stages/mux/MuxManager.js +21 -162
  172. package/dist/stages/mux/MuxManager.js.map +1 -1
  173. package/dist/stages/mux/index.d.ts +0 -1
  174. package/dist/stages/mux/index.d.ts.map +1 -1
  175. package/dist/utils/binary-search.d.ts +12 -4
  176. package/dist/utils/binary-search.d.ts.map +1 -1
  177. package/dist/utils/binary-search.js +52 -6
  178. package/dist/utils/binary-search.js.map +1 -1
  179. package/dist/workers/{BaseDecoder.BWYu1W0B.js → BaseDecoder.CTW-vr29.js} +1 -4
  180. package/dist/workers/BaseDecoder.CTW-vr29.js.map +1 -0
  181. package/dist/workers/{MP4Demuxer.CFHDkPYc.js → MP4Demuxer.BEa6PLJm.js} +10 -3
  182. package/dist/workers/{MP4Demuxer.CFHDkPYc.js.map → MP4Demuxer.BEa6PLJm.js.map} +1 -1
  183. package/dist/workers/stages/compose/{video-compose.worker.M5uomNVr.js → video-compose.worker.DHQ8B105.js} +260 -83
  184. package/dist/workers/stages/compose/video-compose.worker.DHQ8B105.js.map +1 -0
  185. package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js → audio-decode.worker.CP8bXXa4.js} +2 -2
  186. package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js.map → audio-decode.worker.CP8bXXa4.js.map} +1 -1
  187. package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js → video-decode.worker.BIspTxgV.js} +2 -2
  188. package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js.map → video-decode.worker.BIspTxgV.js.map} +1 -1
  189. package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js → audio-demux.worker._VRQdLdv.js} +2 -2
  190. package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js.map → audio-demux.worker._VRQdLdv.js.map} +1 -1
  191. package/dist/workers/stages/demux/{video-demux.worker.D_WeHPkt.js → video-demux.worker.CSkxGtmx.js} +3 -19
  192. package/dist/workers/stages/demux/video-demux.worker.CSkxGtmx.js.map +1 -0
  193. package/dist/workers/worker-manifest.json +5 -5
  194. package/package.json +1 -1
  195. package/dist/cache/l2/IndexedDBStore.js.map +0 -1
  196. package/dist/cache/l2/OPFSStore.js +0 -131
  197. package/dist/cache/l2/OPFSStore.js.map +0 -1
  198. package/dist/controllers/PreRenderService.d.ts +0 -59
  199. package/dist/controllers/PreRenderService.d.ts.map +0 -1
  200. package/dist/controllers/PreRenderService.js +0 -185
  201. package/dist/controllers/PreRenderService.js.map +0 -1
  202. package/dist/controllers/PreRenderTaskQueue.d.ts +0 -21
  203. package/dist/controllers/PreRenderTaskQueue.d.ts.map +0 -1
  204. package/dist/orchestrator/ClipSessionManager.d.ts +0 -70
  205. package/dist/orchestrator/ClipSessionManager.d.ts.map +0 -1
  206. package/dist/orchestrator/ClipSessionManager.js +0 -158
  207. package/dist/orchestrator/ClipSessionManager.js.map +0 -1
  208. package/dist/stages/decode/AudioChunkDecoder.js +0 -169
  209. package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
  210. package/dist/stages/encode/ClipEncoderManager.d.ts +0 -64
  211. package/dist/stages/encode/ClipEncoderManager.d.ts.map +0 -1
  212. package/dist/stages/mux/OPFSWriter.d.ts +0 -46
  213. package/dist/stages/mux/OPFSWriter.d.ts.map +0 -1
  214. package/dist/utils/BackpressureAdapter.d.ts +0 -26
  215. package/dist/utils/BackpressureAdapter.d.ts.map +0 -1
  216. package/dist/utils/time-utils.js +0 -45
  217. package/dist/utils/time-utils.js.map +0 -1
  218. package/dist/workers/BaseDecoder.BWYu1W0B.js.map +0 -1
  219. package/dist/workers/stages/compose/video-compose.worker.M5uomNVr.js.map +0 -1
  220. package/dist/workers/stages/demux/video-demux.worker.D_WeHPkt.js.map +0 -1
@@ -1,17 +1,4 @@
1
1
  import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.CE5euh3R.js";
2
- const MICROSECONDS_PER_SECOND = 1e6;
3
- const DEFAULT_FPS = 30;
4
- function normalizeFps(value) {
5
- if (!Number.isFinite(value) || value <= 0) {
6
- return DEFAULT_FPS;
7
- }
8
- return value;
9
- }
10
- function frameDurationFromFps(fps) {
11
- const normalized = normalizeFps(fps);
12
- const duration = MICROSECONDS_PER_SECOND / normalized;
13
- return Math.max(Math.round(duration), 1);
14
- }
15
2
  function measureTextWidth(ctx, text, fontSize, fontFamily, fontWeight = 400) {
16
3
  ctx.save();
17
4
  ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
@@ -1456,7 +1443,9 @@ class FilterProcessor {
1456
1443
  const closeLayerFrame = (layer) => {
1457
1444
  if (layer.type === "video") {
1458
1445
  const vf = layer.videoFrame;
1459
- vf?.close?.();
1446
+ if (vf?.close) {
1447
+ vf.close();
1448
+ }
1460
1449
  }
1461
1450
  };
1462
1451
  class VideoComposer {
@@ -1466,32 +1455,42 @@ class VideoComposer {
1466
1455
  layerRenderer;
1467
1456
  transitionProcessor;
1468
1457
  filterProcessor;
1469
- timelineContext;
1458
+ frameDurationUs;
1459
+ // Cached frame duration
1470
1460
  constructor(config) {
1471
1461
  this.config = this.applyDefaults(config);
1472
- this.canvas = new OffscreenCanvas(this.config.width, this.config.height);
1473
- const ctx = this.canvas.getContext("2d", {
1474
- alpha: true,
1475
- desynchronized: true,
1476
- willReadFrequently: false,
1477
- colorSpace: "srgb"
1478
- });
1479
- if (!ctx) {
1462
+ this.frameDurationUs = Math.round(1e6 / this.config.fps);
1463
+ if (config.externalCanvas) {
1464
+ this.canvas = config.externalCanvas;
1465
+ this.ctx = this.canvas.getContext("2d", {
1466
+ alpha: true,
1467
+ desynchronized: true,
1468
+ willReadFrequently: false,
1469
+ colorSpace: "srgb"
1470
+ });
1471
+ } else {
1472
+ this.canvas = new OffscreenCanvas(this.config.width, this.config.height);
1473
+ this.ctx = this.canvas.getContext("2d", {
1474
+ alpha: true,
1475
+ desynchronized: true,
1476
+ willReadFrequently: false,
1477
+ colorSpace: "srgb"
1478
+ });
1479
+ }
1480
+ if (!this.ctx) {
1480
1481
  throw new Error("Failed to create 2D rendering context");
1481
1482
  }
1482
- this.ctx = ctx;
1483
1483
  this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
1484
1484
  this.loadFonts();
1485
1485
  this.ctx.imageSmoothingQuality = "high";
1486
1486
  this.layerRenderer = new LayerRenderer(
1487
- ctx,
1487
+ this.ctx,
1488
1488
  this.config.width,
1489
1489
  this.config.height,
1490
1490
  this.config.fps
1491
1491
  );
1492
1492
  this.transitionProcessor = new TransitionProcessor(this.config.width, this.config.height);
1493
1493
  this.filterProcessor = new FilterProcessor();
1494
- this.timelineContext = this.config.timeline;
1495
1494
  }
1496
1495
  applyDefaults(config) {
1497
1496
  return {
@@ -1513,21 +1512,18 @@ class VideoComposer {
1513
1512
  clipDurationUs: Infinity,
1514
1513
  compositionFps: 30
1515
1514
  },
1516
- fonts: config.fonts ?? []
1515
+ fonts: config.fonts ?? [],
1516
+ externalCanvas: config.externalCanvas
1517
1517
  };
1518
1518
  }
1519
- createStreams(instruction) {
1520
- if (instruction?.baseConfig.timeline) {
1521
- this.timelineContext = instruction.baseConfig.timeline;
1522
- }
1519
+ createStreams(_instruction) {
1523
1520
  const stream = new TransformStream(
1524
1521
  {
1525
1522
  transform: async (request, controller) => {
1526
1523
  const result = await this.composeFrame(request);
1527
- controller.enqueue(result.frame);
1528
- setTimeout(() => {
1529
- result.frame.close();
1530
- }, 1e3);
1524
+ if (result.frame) {
1525
+ controller.enqueue(result.frame);
1526
+ }
1531
1527
  },
1532
1528
  flush: async () => {
1533
1529
  this.filterProcessor.clearCache();
@@ -1560,13 +1556,27 @@ class VideoComposer {
1560
1556
  continue;
1561
1557
  }
1562
1558
  try {
1563
- if (layer.filters && layer.filters.length > 0) {
1564
- this.ctx.save();
1565
- this.filterProcessor.applyFilters(this.ctx, layer.filters);
1566
- }
1567
- await this.layerRenderer.renderLayer(layer);
1568
- if (layer.filters && layer.filters.length > 0) {
1569
- this.ctx.restore();
1559
+ if (layer.rcFrame) {
1560
+ await layer.rcFrame.use(async (frame2) => {
1561
+ layer.videoFrame = frame2;
1562
+ if (layer.filters && layer.filters.length > 0) {
1563
+ this.ctx.save();
1564
+ this.filterProcessor.applyFilters(this.ctx, layer.filters);
1565
+ }
1566
+ await this.layerRenderer.renderLayer(layer);
1567
+ if (layer.filters && layer.filters.length > 0) {
1568
+ this.ctx.restore();
1569
+ }
1570
+ });
1571
+ } else {
1572
+ if (layer.filters && layer.filters.length > 0) {
1573
+ this.ctx.save();
1574
+ this.filterProcessor.applyFilters(this.ctx, layer.filters);
1575
+ }
1576
+ await this.layerRenderer.renderLayer(layer);
1577
+ if (layer.filters && layer.filters.length > 0) {
1578
+ this.ctx.restore();
1579
+ }
1570
1580
  }
1571
1581
  } catch (error) {
1572
1582
  console.error("[VideoComposer] composeFrame error: ", error);
@@ -1576,7 +1586,10 @@ class VideoComposer {
1576
1586
  if (request.transition) {
1577
1587
  this.ctx.restore();
1578
1588
  }
1579
- const frame = await this.createOutputFrame(request.timeUs);
1589
+ let frame = null;
1590
+ if (!this.config.externalCanvas) {
1591
+ frame = await this.createOutputFrame(request.timeUs);
1592
+ }
1580
1593
  return {
1581
1594
  frame,
1582
1595
  timeUs: request.timeUs
@@ -1602,10 +1615,10 @@ class VideoComposer {
1602
1615
  // return [...layers].sort((a, b) => a.zIndex - b.zIndex);
1603
1616
  // }
1604
1617
  async createOutputFrame(timeUs) {
1605
- const duration = frameDurationFromFps(this.timelineContext.compositionFps);
1606
1618
  const frame = new VideoFrame(this.canvas, {
1607
1619
  timestamp: timeUs,
1608
- duration,
1620
+ duration: this.frameDurationUs,
1621
+ // Use cached duration
1609
1622
  alpha: "discard",
1610
1623
  visibleRect: { x: 0, y: 0, width: this.canvas.width, height: this.canvas.height }
1611
1624
  });
@@ -1629,6 +1642,9 @@ class VideoComposer {
1629
1642
  }
1630
1643
  updateConfig(config) {
1631
1644
  Object.assign(this.config, this.applyDefaults({ ...this.config, ...config }));
1645
+ if (config.fps !== void 0) {
1646
+ this.frameDurationUs = Math.round(1e6 / this.config.fps);
1647
+ }
1632
1648
  if (config.width || config.height) {
1633
1649
  this.canvas.width = this.config.width;
1634
1650
  this.canvas.height = this.config.height;
@@ -1638,9 +1654,6 @@ class VideoComposer {
1638
1654
  if (config.enableSmoothing !== void 0) {
1639
1655
  this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
1640
1656
  }
1641
- if (config.timeline) {
1642
- this.timelineContext = config.timeline;
1643
- }
1644
1657
  if (config.fonts) {
1645
1658
  this.loadFonts();
1646
1659
  }
@@ -1649,6 +1662,19 @@ class VideoComposer {
1649
1662
  this.filterProcessor.clearCache();
1650
1663
  }
1651
1664
  }
1665
+ const MICROSECONDS_PER_SECOND = 1e6;
1666
+ const DEFAULT_FPS = 30;
1667
+ function normalizeFps(value) {
1668
+ if (!Number.isFinite(value) || value <= 0) {
1669
+ return DEFAULT_FPS;
1670
+ }
1671
+ return value;
1672
+ }
1673
+ function frameDurationFromFps(fps) {
1674
+ const normalized = normalizeFps(fps);
1675
+ const duration = MICROSECONDS_PER_SECOND / normalized;
1676
+ return Math.max(Math.round(duration), 1);
1677
+ }
1652
1678
  function interpolateKeyframes(keyframes, timeUs) {
1653
1679
  const defaultTransform = {
1654
1680
  x: 0,
@@ -1727,6 +1753,183 @@ function applyEasing(t, easing) {
1727
1753
  return t;
1728
1754
  }
1729
1755
  }
1756
+ class FrameRateConverter {
1757
+ clipDurationUs;
1758
+ frameDurationUs;
1759
+ // State for frame processing
1760
+ targetFrameIndex = 0;
1761
+ targetFrameTimeUs = 0;
1762
+ sourceFrameBuffer = [];
1763
+ constructor(targetFps, clipDurationUs) {
1764
+ if (targetFps <= 0) {
1765
+ throw new Error(`Invalid target fps: ${targetFps}`);
1766
+ }
1767
+ if (clipDurationUs <= 0) {
1768
+ throw new Error(`Invalid clip duration: ${clipDurationUs}`);
1769
+ }
1770
+ this.clipDurationUs = clipDurationUs;
1771
+ this.frameDurationUs = Math.round(1e6 / targetFps);
1772
+ }
1773
+ /**
1774
+ * Create a TransformStream that converts VFR frames to CFR frames
1775
+ */
1776
+ createStream() {
1777
+ return new TransformStream({
1778
+ start: () => {
1779
+ this.targetFrameIndex = 0;
1780
+ this.targetFrameTimeUs = 0;
1781
+ this.sourceFrameBuffer = [];
1782
+ this.sourceFrameCount = 0;
1783
+ this.outputFrameCount = 0;
1784
+ },
1785
+ transform: (sourceFrame, controller) => {
1786
+ this.processSourceFrame(sourceFrame, controller);
1787
+ },
1788
+ flush: (controller) => {
1789
+ this.flushRemainingFrames(controller);
1790
+ }
1791
+ });
1792
+ }
1793
+ sourceFrameCount = 0;
1794
+ outputFrameCount = 0;
1795
+ /**
1796
+ * Process incoming source frame and output target frames
1797
+ */
1798
+ processSourceFrame(sourceFrame, controller) {
1799
+ this.sourceFrameBuffer.push(sourceFrame);
1800
+ this.sourceFrameCount++;
1801
+ while (this.targetFrameTimeUs < this.clipDurationUs) {
1802
+ const closestFrame = this.findClosestFrame(this.targetFrameTimeUs);
1803
+ if (!closestFrame) {
1804
+ break;
1805
+ }
1806
+ if (this.shouldWaitForNextFrame(closestFrame)) {
1807
+ break;
1808
+ }
1809
+ try {
1810
+ const targetFrame = new VideoFrame(closestFrame, {
1811
+ timestamp: this.targetFrameTimeUs,
1812
+ duration: this.frameDurationUs
1813
+ });
1814
+ controller.enqueue(targetFrame);
1815
+ this.outputFrameCount++;
1816
+ this.cleanupOldFrames(this.targetFrameTimeUs);
1817
+ this.targetFrameIndex++;
1818
+ this.targetFrameTimeUs = this.targetFrameIndex * this.frameDurationUs;
1819
+ } catch (_error) {
1820
+ console.error("[FrameRateConverter] Failed to create target frame:", _error);
1821
+ this.targetFrameIndex++;
1822
+ this.targetFrameTimeUs = this.targetFrameIndex * this.frameDurationUs;
1823
+ }
1824
+ }
1825
+ }
1826
+ /**
1827
+ * Flush remaining target frames at end of stream
1828
+ */
1829
+ flushRemainingFrames(controller) {
1830
+ while (this.sourceFrameBuffer.length > 0) {
1831
+ const closestFrame = this.findClosestFrame(this.targetFrameTimeUs);
1832
+ if (!closestFrame) {
1833
+ break;
1834
+ }
1835
+ if (this.targetFrameTimeUs >= this.clipDurationUs) {
1836
+ break;
1837
+ }
1838
+ try {
1839
+ const targetFrame = new VideoFrame(closestFrame, {
1840
+ timestamp: this.targetFrameTimeUs,
1841
+ duration: this.frameDurationUs
1842
+ });
1843
+ controller.enqueue(targetFrame);
1844
+ this.cleanupOldFrames(this.targetFrameTimeUs);
1845
+ this.targetFrameIndex++;
1846
+ this.targetFrameTimeUs = this.targetFrameIndex * this.frameDurationUs;
1847
+ } catch (_error) {
1848
+ console.error("[FrameRateConverter] Failed to create target frame in flush:", _error);
1849
+ break;
1850
+ }
1851
+ }
1852
+ for (const frame of this.sourceFrameBuffer) {
1853
+ try {
1854
+ frame.close();
1855
+ } catch {
1856
+ }
1857
+ }
1858
+ this.sourceFrameBuffer = [];
1859
+ }
1860
+ /**
1861
+ * Find the source frame closest to target time
1862
+ */
1863
+ findClosestFrame(targetTimeUs) {
1864
+ if (this.sourceFrameBuffer.length === 0) {
1865
+ return null;
1866
+ }
1867
+ let closestFrame = this.sourceFrameBuffer[0];
1868
+ let minError = Math.abs((closestFrame?.timestamp ?? 0) - targetTimeUs);
1869
+ for (const frame of this.sourceFrameBuffer) {
1870
+ const error = Math.abs((frame.timestamp ?? 0) - targetTimeUs);
1871
+ if (error < minError) {
1872
+ minError = error;
1873
+ closestFrame = frame;
1874
+ }
1875
+ }
1876
+ return closestFrame ?? null;
1877
+ }
1878
+ /**
1879
+ * Check if we should wait for next source frame before outputting
1880
+ * Returns true if:
1881
+ * - We only have 1 frame in buffer
1882
+ * - The closest frame is before target time
1883
+ * - We might get a better match from next frame
1884
+ */
1885
+ shouldWaitForNextFrame(closestFrame) {
1886
+ if (this.sourceFrameBuffer.length <= 1) {
1887
+ const frameTimestamp = closestFrame.timestamp ?? 0;
1888
+ if (frameTimestamp < this.targetFrameTimeUs) {
1889
+ return true;
1890
+ }
1891
+ }
1892
+ return false;
1893
+ }
1894
+ /**
1895
+ * Clean up source frames that are no longer needed
1896
+ * Keep frames that might be needed for future target frames
1897
+ */
1898
+ cleanupOldFrames(currentTargetTimeUs) {
1899
+ const nextTargetTimeUs = currentTargetTimeUs + this.frameDurationUs;
1900
+ const previousTargetTimeUs = currentTargetTimeUs - this.frameDurationUs;
1901
+ let removeCount = 0;
1902
+ for (let i = 0; i < this.sourceFrameBuffer.length; i++) {
1903
+ const frame = this.sourceFrameBuffer[i];
1904
+ if (!frame) continue;
1905
+ const frameTimestamp = frame.timestamp ?? 0;
1906
+ const isNeededForNext = Math.abs(frameTimestamp - nextTargetTimeUs) < Math.abs(frameTimestamp - previousTargetTimeUs);
1907
+ if (frameTimestamp < previousTargetTimeUs && !isNeededForNext) {
1908
+ try {
1909
+ frame.close();
1910
+ } catch {
1911
+ }
1912
+ removeCount++;
1913
+ } else {
1914
+ break;
1915
+ }
1916
+ }
1917
+ if (removeCount > 0) {
1918
+ this.sourceFrameBuffer.splice(0, removeCount);
1919
+ }
1920
+ }
1921
+ /**
1922
+ * Get current conversion state (for debugging)
1923
+ */
1924
+ getState() {
1925
+ return {
1926
+ targetFrameIndex: this.targetFrameIndex,
1927
+ targetFrameTimeUs: this.targetFrameTimeUs,
1928
+ bufferSize: this.sourceFrameBuffer.length,
1929
+ frameDurationUs: this.frameDurationUs
1930
+ };
1931
+ }
1932
+ }
1730
1933
  function resolveActiveLayers(layers, timestamp) {
1731
1934
  return layers.filter((layer) => {
1732
1935
  if (!layer.payload.attachmentId) {
@@ -1842,7 +2045,6 @@ class VideoComposeWorker {
1842
2045
  downstreamPort = null;
1843
2046
  upstreamPort = null;
1844
2047
  instructions = null;
1845
- streamState = null;
1846
2048
  imageMap = /* @__PURE__ */ new Map();
1847
2049
  constructor() {
1848
2050
  this.channel = new WorkerChannel(self, {
@@ -1925,7 +2127,13 @@ class VideoComposeWorker {
1925
2127
  console.warn("[VideoComposeWorker] No instructions installed");
1926
2128
  return;
1927
2129
  }
1928
- const filteredStream = stream.pipeThrough(
2130
+ const timeline = this.instructions.baseConfig.timeline;
2131
+ const fpsConverter = new FrameRateConverter(
2132
+ timeline?.compositionFps ?? 30,
2133
+ timeline?.clipDurationUs ?? Infinity
2134
+ );
2135
+ const cfrStream = stream.pipeThrough(fpsConverter.createStream());
2136
+ const filteredStream = cfrStream.pipeThrough(
1929
2137
  new TransformStream({
1930
2138
  transform: (frame, controller) => {
1931
2139
  try {
@@ -2004,7 +2212,6 @@ class VideoComposeWorker {
2004
2212
  this.imageMap.forEach((bitmap) => bitmap.close());
2005
2213
  this.imageMap.clear();
2006
2214
  this.instructions = null;
2007
- this.streamState = null;
2008
2215
  this.channel.state = WorkerState.Disposed;
2009
2216
  return { success: true };
2010
2217
  }
@@ -2045,7 +2252,6 @@ class VideoComposeWorker {
2045
2252
  }
2046
2253
  async handleDisposeClip() {
2047
2254
  this.instructions = null;
2048
- this.streamState = null;
2049
2255
  this.downstreamPort?.close();
2050
2256
  this.upstreamPort?.close();
2051
2257
  this.downstreamPort = null;
@@ -2115,7 +2321,7 @@ class VideoComposeWorker {
2115
2321
  }
2116
2322
  }
2117
2323
  buildComposeRequest(instruction, frame) {
2118
- const clipRelativeTime = this.computeTimelineTimestamp(frame, instruction.baseConfig);
2324
+ const clipRelativeTime = frame.timestamp ?? 0;
2119
2325
  const clipDurationUs = instruction.baseConfig.timeline?.clipDurationUs ?? Infinity;
2120
2326
  if (clipRelativeTime < 0 || clipRelativeTime >= clipDurationUs) {
2121
2327
  return null;
@@ -2159,35 +2365,6 @@ class VideoComposeWorker {
2159
2365
  direction: entry.params.payload?.direction
2160
2366
  };
2161
2367
  }
2162
- computeTimelineTimestamp(frame, config) {
2163
- if (!this.streamState) {
2164
- this.streamState = {
2165
- lastSourceTimestamp: null,
2166
- nextFrameIndex: 0
2167
- };
2168
- }
2169
- const timeline = config.timeline;
2170
- if (!timeline) {
2171
- const ts = frame.timestamp ?? 0;
2172
- this.streamState.lastSourceTimestamp = frame.timestamp ?? null;
2173
- return ts;
2174
- }
2175
- const { compositionFps } = timeline;
2176
- const sourceTimestamp = frame.timestamp ?? null;
2177
- if (sourceTimestamp !== null && this.streamState.lastSourceTimestamp !== null && sourceTimestamp < this.streamState.lastSourceTimestamp) {
2178
- this.streamState.nextFrameIndex = 0;
2179
- }
2180
- const frameDuration = frameDurationFromFps(compositionFps);
2181
- let frameIndex = this.streamState.nextFrameIndex;
2182
- if (sourceTimestamp !== null) {
2183
- const approxIndex = Math.round(sourceTimestamp / frameDuration);
2184
- frameIndex = Math.max(frameIndex, approxIndex);
2185
- }
2186
- const relativeTimeUs = frameIndex * frameDuration;
2187
- this.streamState.nextFrameIndex = frameIndex + 1;
2188
- this.streamState.lastSourceTimestamp = sourceTimestamp;
2189
- return relativeTimeUs;
2190
- }
2191
2368
  }
2192
2369
  const worker = new VideoComposeWorker();
2193
2370
  self.addEventListener("beforeunload", () => {
@@ -2198,4 +2375,4 @@ export {
2198
2375
  VideoComposeWorker,
2199
2376
  videoCompose_worker as default
2200
2377
  };
2201
- //# sourceMappingURL=video-compose.worker.M5uomNVr.js.map
2378
+ //# sourceMappingURL=video-compose.worker.DHQ8B105.js.map