@meframe/core 0.0.27 → 0.0.29

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 (43) hide show
  1. package/dist/Meframe.d.ts +2 -2
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +8 -2
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +2 -0
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +9 -2
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/l1/AudioL1Cache.d.ts +1 -1
  10. package/dist/cache/l1/AudioL1Cache.js +2 -2
  11. package/dist/cache/l1/AudioL1Cache.js.map +1 -1
  12. package/dist/controllers/PlaybackController.d.ts +1 -1
  13. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  14. package/dist/controllers/PlaybackController.js +9 -9
  15. package/dist/controllers/PlaybackController.js.map +1 -1
  16. package/dist/model/types.d.ts +5 -0
  17. package/dist/model/types.d.ts.map +1 -1
  18. package/dist/model/types.js.map +1 -1
  19. package/dist/orchestrator/Orchestrator.d.ts +2 -1
  20. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  21. package/dist/orchestrator/Orchestrator.js +11 -5
  22. package/dist/orchestrator/Orchestrator.js.map +1 -1
  23. package/dist/stages/compose/FrameRateConverter.d.ts +68 -0
  24. package/dist/stages/compose/FrameRateConverter.d.ts.map +1 -0
  25. package/dist/stages/compose/VideoComposer.d.ts +2 -2
  26. package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
  27. package/dist/stages/encode/index.d.ts +0 -1
  28. package/dist/stages/encode/index.d.ts.map +1 -1
  29. package/dist/workers/{MP4Demuxer.CFHDkPYc.js → MP4Demuxer.lMOUMWFh.js} +2 -2
  30. package/dist/workers/{MP4Demuxer.CFHDkPYc.js.map → MP4Demuxer.lMOUMWFh.js.map} +1 -1
  31. package/dist/workers/stages/compose/{video-compose.worker.M5uomNVr.js → video-compose.worker.CIeEIJO7.js} +208 -59
  32. package/dist/workers/stages/compose/video-compose.worker.CIeEIJO7.js.map +1 -0
  33. package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js → audio-demux.worker.DcurGC8i.js} +2 -2
  34. package/dist/workers/stages/demux/{audio-demux.worker.BTFPcY7P.js.map → audio-demux.worker.DcurGC8i.js.map} +1 -1
  35. package/dist/workers/stages/demux/{video-demux.worker.D_WeHPkt.js → video-demux.worker.B1_wntU4.js} +2 -2
  36. package/dist/workers/stages/demux/{video-demux.worker.D_WeHPkt.js.map → video-demux.worker.B1_wntU4.js.map} +1 -1
  37. package/dist/workers/worker-manifest.json +3 -3
  38. package/package.json +1 -1
  39. package/dist/stages/encode/ClipEncoderManager.d.ts +0 -64
  40. package/dist/stages/encode/ClipEncoderManager.d.ts.map +0 -1
  41. package/dist/utils/time-utils.js +0 -45
  42. package/dist/utils/time-utils.js.map +0 -1
  43. package/dist/workers/stages/compose/video-compose.worker.M5uomNVr.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}`;
@@ -1466,9 +1453,11 @@ class VideoComposer {
1466
1453
  layerRenderer;
1467
1454
  transitionProcessor;
1468
1455
  filterProcessor;
1469
- timelineContext;
1456
+ frameDurationUs;
1457
+ // Cached frame duration
1470
1458
  constructor(config) {
1471
1459
  this.config = this.applyDefaults(config);
1460
+ this.frameDurationUs = Math.round(1e6 / this.config.fps);
1472
1461
  this.canvas = new OffscreenCanvas(this.config.width, this.config.height);
1473
1462
  const ctx = this.canvas.getContext("2d", {
1474
1463
  alpha: true,
@@ -1491,7 +1480,6 @@ class VideoComposer {
1491
1480
  );
1492
1481
  this.transitionProcessor = new TransitionProcessor(this.config.width, this.config.height);
1493
1482
  this.filterProcessor = new FilterProcessor();
1494
- this.timelineContext = this.config.timeline;
1495
1483
  }
1496
1484
  applyDefaults(config) {
1497
1485
  return {
@@ -1516,10 +1504,7 @@ class VideoComposer {
1516
1504
  fonts: config.fonts ?? []
1517
1505
  };
1518
1506
  }
1519
- createStreams(instruction) {
1520
- if (instruction?.baseConfig.timeline) {
1521
- this.timelineContext = instruction.baseConfig.timeline;
1522
- }
1507
+ createStreams(_instruction) {
1523
1508
  const stream = new TransformStream(
1524
1509
  {
1525
1510
  transform: async (request, controller) => {
@@ -1602,10 +1587,10 @@ class VideoComposer {
1602
1587
  // return [...layers].sort((a, b) => a.zIndex - b.zIndex);
1603
1588
  // }
1604
1589
  async createOutputFrame(timeUs) {
1605
- const duration = frameDurationFromFps(this.timelineContext.compositionFps);
1606
1590
  const frame = new VideoFrame(this.canvas, {
1607
1591
  timestamp: timeUs,
1608
- duration,
1592
+ duration: this.frameDurationUs,
1593
+ // Use cached duration
1609
1594
  alpha: "discard",
1610
1595
  visibleRect: { x: 0, y: 0, width: this.canvas.width, height: this.canvas.height }
1611
1596
  });
@@ -1629,6 +1614,9 @@ class VideoComposer {
1629
1614
  }
1630
1615
  updateConfig(config) {
1631
1616
  Object.assign(this.config, this.applyDefaults({ ...this.config, ...config }));
1617
+ if (config.fps !== void 0) {
1618
+ this.frameDurationUs = Math.round(1e6 / this.config.fps);
1619
+ }
1632
1620
  if (config.width || config.height) {
1633
1621
  this.canvas.width = this.config.width;
1634
1622
  this.canvas.height = this.config.height;
@@ -1638,9 +1626,6 @@ class VideoComposer {
1638
1626
  if (config.enableSmoothing !== void 0) {
1639
1627
  this.ctx.imageSmoothingEnabled = this.config.enableSmoothing;
1640
1628
  }
1641
- if (config.timeline) {
1642
- this.timelineContext = config.timeline;
1643
- }
1644
1629
  if (config.fonts) {
1645
1630
  this.loadFonts();
1646
1631
  }
@@ -1649,6 +1634,19 @@ class VideoComposer {
1649
1634
  this.filterProcessor.clearCache();
1650
1635
  }
1651
1636
  }
1637
+ const MICROSECONDS_PER_SECOND = 1e6;
1638
+ const DEFAULT_FPS = 30;
1639
+ function normalizeFps(value) {
1640
+ if (!Number.isFinite(value) || value <= 0) {
1641
+ return DEFAULT_FPS;
1642
+ }
1643
+ return value;
1644
+ }
1645
+ function frameDurationFromFps(fps) {
1646
+ const normalized = normalizeFps(fps);
1647
+ const duration = MICROSECONDS_PER_SECOND / normalized;
1648
+ return Math.max(Math.round(duration), 1);
1649
+ }
1652
1650
  function interpolateKeyframes(keyframes, timeUs) {
1653
1651
  const defaultTransform = {
1654
1652
  x: 0,
@@ -1727,6 +1725,183 @@ function applyEasing(t, easing) {
1727
1725
  return t;
1728
1726
  }
1729
1727
  }
1728
+ class FrameRateConverter {
1729
+ clipDurationUs;
1730
+ frameDurationUs;
1731
+ // State for frame processing
1732
+ targetFrameIndex = 0;
1733
+ targetFrameTimeUs = 0;
1734
+ sourceFrameBuffer = [];
1735
+ constructor(targetFps, clipDurationUs) {
1736
+ if (targetFps <= 0) {
1737
+ throw new Error(`Invalid target fps: ${targetFps}`);
1738
+ }
1739
+ if (clipDurationUs <= 0) {
1740
+ throw new Error(`Invalid clip duration: ${clipDurationUs}`);
1741
+ }
1742
+ this.clipDurationUs = clipDurationUs;
1743
+ this.frameDurationUs = Math.round(1e6 / targetFps);
1744
+ }
1745
+ /**
1746
+ * Create a TransformStream that converts VFR frames to CFR frames
1747
+ */
1748
+ createStream() {
1749
+ return new TransformStream({
1750
+ start: () => {
1751
+ this.targetFrameIndex = 0;
1752
+ this.targetFrameTimeUs = 0;
1753
+ this.sourceFrameBuffer = [];
1754
+ this.sourceFrameCount = 0;
1755
+ this.outputFrameCount = 0;
1756
+ },
1757
+ transform: (sourceFrame, controller) => {
1758
+ this.processSourceFrame(sourceFrame, controller);
1759
+ },
1760
+ flush: (controller) => {
1761
+ this.flushRemainingFrames(controller);
1762
+ }
1763
+ });
1764
+ }
1765
+ sourceFrameCount = 0;
1766
+ outputFrameCount = 0;
1767
+ /**
1768
+ * Process incoming source frame and output target frames
1769
+ */
1770
+ processSourceFrame(sourceFrame, controller) {
1771
+ this.sourceFrameBuffer.push(sourceFrame);
1772
+ this.sourceFrameCount++;
1773
+ while (this.targetFrameTimeUs < this.clipDurationUs) {
1774
+ const closestFrame = this.findClosestFrame(this.targetFrameTimeUs);
1775
+ if (!closestFrame) {
1776
+ break;
1777
+ }
1778
+ if (this.shouldWaitForNextFrame(closestFrame)) {
1779
+ break;
1780
+ }
1781
+ try {
1782
+ const targetFrame = new VideoFrame(closestFrame, {
1783
+ timestamp: this.targetFrameTimeUs,
1784
+ duration: this.frameDurationUs
1785
+ });
1786
+ controller.enqueue(targetFrame);
1787
+ this.outputFrameCount++;
1788
+ this.cleanupOldFrames(this.targetFrameTimeUs);
1789
+ this.targetFrameIndex++;
1790
+ this.targetFrameTimeUs = this.targetFrameIndex * this.frameDurationUs;
1791
+ } catch (error) {
1792
+ console.error("[FrameRateConverter] Failed to create target frame:", error);
1793
+ this.targetFrameIndex++;
1794
+ this.targetFrameTimeUs = this.targetFrameIndex * this.frameDurationUs;
1795
+ }
1796
+ }
1797
+ }
1798
+ /**
1799
+ * Flush remaining target frames at end of stream
1800
+ */
1801
+ flushRemainingFrames(controller) {
1802
+ while (this.sourceFrameBuffer.length > 0) {
1803
+ const closestFrame = this.findClosestFrame(this.targetFrameTimeUs);
1804
+ if (!closestFrame) {
1805
+ break;
1806
+ }
1807
+ if (this.targetFrameTimeUs >= this.clipDurationUs) {
1808
+ break;
1809
+ }
1810
+ try {
1811
+ const targetFrame = new VideoFrame(closestFrame, {
1812
+ timestamp: this.targetFrameTimeUs,
1813
+ duration: this.frameDurationUs
1814
+ });
1815
+ controller.enqueue(targetFrame);
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 in flush:", error);
1821
+ break;
1822
+ }
1823
+ }
1824
+ for (const frame of this.sourceFrameBuffer) {
1825
+ try {
1826
+ frame.close();
1827
+ } catch (error) {
1828
+ }
1829
+ }
1830
+ this.sourceFrameBuffer = [];
1831
+ }
1832
+ /**
1833
+ * Find the source frame closest to target time
1834
+ */
1835
+ findClosestFrame(targetTimeUs) {
1836
+ if (this.sourceFrameBuffer.length === 0) {
1837
+ return null;
1838
+ }
1839
+ let closestFrame = this.sourceFrameBuffer[0];
1840
+ let minError = Math.abs((closestFrame?.timestamp ?? 0) - targetTimeUs);
1841
+ for (const frame of this.sourceFrameBuffer) {
1842
+ const error = Math.abs((frame.timestamp ?? 0) - targetTimeUs);
1843
+ if (error < minError) {
1844
+ minError = error;
1845
+ closestFrame = frame;
1846
+ }
1847
+ }
1848
+ return closestFrame ?? null;
1849
+ }
1850
+ /**
1851
+ * Check if we should wait for next source frame before outputting
1852
+ * Returns true if:
1853
+ * - We only have 1 frame in buffer
1854
+ * - The closest frame is before target time
1855
+ * - We might get a better match from next frame
1856
+ */
1857
+ shouldWaitForNextFrame(closestFrame) {
1858
+ if (this.sourceFrameBuffer.length <= 1) {
1859
+ const frameTimestamp = closestFrame.timestamp ?? 0;
1860
+ if (frameTimestamp < this.targetFrameTimeUs) {
1861
+ return true;
1862
+ }
1863
+ }
1864
+ return false;
1865
+ }
1866
+ /**
1867
+ * Clean up source frames that are no longer needed
1868
+ * Keep frames that might be needed for future target frames
1869
+ */
1870
+ cleanupOldFrames(currentTargetTimeUs) {
1871
+ const nextTargetTimeUs = currentTargetTimeUs + this.frameDurationUs;
1872
+ const previousTargetTimeUs = currentTargetTimeUs - this.frameDurationUs;
1873
+ let removeCount = 0;
1874
+ for (let i = 0; i < this.sourceFrameBuffer.length; i++) {
1875
+ const frame = this.sourceFrameBuffer[i];
1876
+ if (!frame) continue;
1877
+ const frameTimestamp = frame.timestamp ?? 0;
1878
+ const isNeededForNext = Math.abs(frameTimestamp - nextTargetTimeUs) < Math.abs(frameTimestamp - previousTargetTimeUs);
1879
+ if (frameTimestamp < previousTargetTimeUs && !isNeededForNext) {
1880
+ try {
1881
+ frame.close();
1882
+ } catch (error) {
1883
+ }
1884
+ removeCount++;
1885
+ } else {
1886
+ break;
1887
+ }
1888
+ }
1889
+ if (removeCount > 0) {
1890
+ this.sourceFrameBuffer.splice(0, removeCount);
1891
+ }
1892
+ }
1893
+ /**
1894
+ * Get current conversion state (for debugging)
1895
+ */
1896
+ getState() {
1897
+ return {
1898
+ targetFrameIndex: this.targetFrameIndex,
1899
+ targetFrameTimeUs: this.targetFrameTimeUs,
1900
+ bufferSize: this.sourceFrameBuffer.length,
1901
+ frameDurationUs: this.frameDurationUs
1902
+ };
1903
+ }
1904
+ }
1730
1905
  function resolveActiveLayers(layers, timestamp) {
1731
1906
  return layers.filter((layer) => {
1732
1907
  if (!layer.payload.attachmentId) {
@@ -1842,7 +2017,6 @@ class VideoComposeWorker {
1842
2017
  downstreamPort = null;
1843
2018
  upstreamPort = null;
1844
2019
  instructions = null;
1845
- streamState = null;
1846
2020
  imageMap = /* @__PURE__ */ new Map();
1847
2021
  constructor() {
1848
2022
  this.channel = new WorkerChannel(self, {
@@ -1925,7 +2099,13 @@ class VideoComposeWorker {
1925
2099
  console.warn("[VideoComposeWorker] No instructions installed");
1926
2100
  return;
1927
2101
  }
1928
- const filteredStream = stream.pipeThrough(
2102
+ const timeline = this.instructions.baseConfig.timeline;
2103
+ const fpsConverter = new FrameRateConverter(
2104
+ timeline?.compositionFps ?? 30,
2105
+ timeline?.clipDurationUs ?? Infinity
2106
+ );
2107
+ const cfrStream = stream.pipeThrough(fpsConverter.createStream());
2108
+ const filteredStream = cfrStream.pipeThrough(
1929
2109
  new TransformStream({
1930
2110
  transform: (frame, controller) => {
1931
2111
  try {
@@ -2004,7 +2184,6 @@ class VideoComposeWorker {
2004
2184
  this.imageMap.forEach((bitmap) => bitmap.close());
2005
2185
  this.imageMap.clear();
2006
2186
  this.instructions = null;
2007
- this.streamState = null;
2008
2187
  this.channel.state = WorkerState.Disposed;
2009
2188
  return { success: true };
2010
2189
  }
@@ -2045,7 +2224,6 @@ class VideoComposeWorker {
2045
2224
  }
2046
2225
  async handleDisposeClip() {
2047
2226
  this.instructions = null;
2048
- this.streamState = null;
2049
2227
  this.downstreamPort?.close();
2050
2228
  this.upstreamPort?.close();
2051
2229
  this.downstreamPort = null;
@@ -2115,7 +2293,7 @@ class VideoComposeWorker {
2115
2293
  }
2116
2294
  }
2117
2295
  buildComposeRequest(instruction, frame) {
2118
- const clipRelativeTime = this.computeTimelineTimestamp(frame, instruction.baseConfig);
2296
+ const clipRelativeTime = frame.timestamp ?? 0;
2119
2297
  const clipDurationUs = instruction.baseConfig.timeline?.clipDurationUs ?? Infinity;
2120
2298
  if (clipRelativeTime < 0 || clipRelativeTime >= clipDurationUs) {
2121
2299
  return null;
@@ -2159,35 +2337,6 @@ class VideoComposeWorker {
2159
2337
  direction: entry.params.payload?.direction
2160
2338
  };
2161
2339
  }
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
2340
  }
2192
2341
  const worker = new VideoComposeWorker();
2193
2342
  self.addEventListener("beforeunload", () => {
@@ -2198,4 +2347,4 @@ export {
2198
2347
  VideoComposeWorker,
2199
2348
  videoCompose_worker as default
2200
2349
  };
2201
- //# sourceMappingURL=video-compose.worker.M5uomNVr.js.map
2350
+ //# sourceMappingURL=video-compose.worker.CIeEIJO7.js.map