@meframe/core 0.0.10 → 0.0.12

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 (57) hide show
  1. package/dist/Meframe.d.ts.map +1 -1
  2. package/dist/Meframe.js +3 -0
  3. package/dist/Meframe.js.map +1 -1
  4. package/dist/cache/CacheManager.d.ts +12 -0
  5. package/dist/cache/CacheManager.d.ts.map +1 -1
  6. package/dist/cache/CacheManager.js +16 -4
  7. package/dist/cache/CacheManager.js.map +1 -1
  8. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  9. package/dist/controllers/PlaybackController.js +3 -0
  10. package/dist/controllers/PlaybackController.js.map +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/model/CompositionModel.d.ts +2 -1
  15. package/dist/model/CompositionModel.d.ts.map +1 -1
  16. package/dist/model/CompositionModel.js +80 -11
  17. package/dist/model/CompositionModel.js.map +1 -1
  18. package/dist/model/types.d.ts +30 -1
  19. package/dist/model/types.d.ts.map +1 -1
  20. package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
  21. package/dist/orchestrator/CompositionPlanner.js +18 -2
  22. package/dist/orchestrator/CompositionPlanner.js.map +1 -1
  23. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  24. package/dist/orchestrator/Orchestrator.js +25 -6
  25. package/dist/orchestrator/Orchestrator.js.map +1 -1
  26. package/dist/orchestrator/VideoClipSession.d.ts +2 -1
  27. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  28. package/dist/orchestrator/VideoClipSession.js +16 -1
  29. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  30. package/dist/orchestrator/types.d.ts +1 -0
  31. package/dist/orchestrator/types.d.ts.map +1 -1
  32. package/dist/stages/compose/LayerRenderer.d.ts +2 -0
  33. package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
  34. package/dist/stages/compose/instructions.d.ts +24 -1
  35. package/dist/stages/compose/instructions.d.ts.map +1 -1
  36. package/dist/stages/compose/types.d.ts +2 -0
  37. package/dist/stages/compose/types.d.ts.map +1 -1
  38. package/dist/stages/load/ResourceLoader.d.ts +8 -0
  39. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  40. package/dist/stages/load/ResourceLoader.js +74 -12
  41. package/dist/stages/load/ResourceLoader.js.map +1 -1
  42. package/dist/stages/load/types.d.ts +1 -0
  43. package/dist/stages/load/types.d.ts.map +1 -1
  44. package/dist/stages/mux/MP4Muxer.js +1 -1
  45. package/dist/stages/mux/MP4Muxer.js.map +1 -1
  46. package/dist/stages/mux/MuxManager.d.ts.map +1 -1
  47. package/dist/stages/mux/MuxManager.js +36 -5
  48. package/dist/stages/mux/MuxManager.js.map +1 -1
  49. package/dist/utils/animation-utils.d.ts +16 -0
  50. package/dist/utils/animation-utils.d.ts.map +1 -0
  51. package/dist/utils/image-utils.d.ts +5 -0
  52. package/dist/utils/image-utils.d.ts.map +1 -0
  53. package/dist/utils/image-utils.js +32 -0
  54. package/dist/utils/image-utils.js.map +1 -0
  55. package/dist/workers/stages/compose/video-compose.worker.js +263 -85
  56. package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
  57. package/package.json +1 -1
@@ -12,30 +12,6 @@ function frameDurationFromFps(fps) {
12
12
  const duration = MICROSECONDS_PER_SECOND / normalized;
13
13
  return Math.max(Math.round(duration), 1);
14
14
  }
15
- function frameIndexFromTimestamp(baseTimestampUs, timestampUs, fps, strategy = "nearest") {
16
- const frameDurationUs = frameDurationFromFps(fps);
17
- if (frameDurationUs <= 0) {
18
- return 0;
19
- }
20
- const delta = timestampUs - baseTimestampUs;
21
- const rawIndex = delta / frameDurationUs;
22
- if (!Number.isFinite(rawIndex)) {
23
- return 0;
24
- }
25
- switch (strategy) {
26
- case "floor":
27
- return Math.floor(rawIndex);
28
- case "ceil":
29
- return Math.ceil(rawIndex);
30
- default:
31
- return Math.round(rawIndex);
32
- }
33
- }
34
- function quantizeTimestampToFrame(timestampUs, baseTimestampUs, fps, strategy = "nearest") {
35
- const frameDurationUs = frameDurationFromFps(fps);
36
- const index = frameIndexFromTimestamp(baseTimestampUs, timestampUs, fps, strategy);
37
- return baseTimestampUs + index * frameDurationUs;
38
- }
39
15
  class LayerRenderer {
40
16
  ctx;
41
17
  width;
@@ -63,7 +39,8 @@ class LayerRenderer {
63
39
  this.ctx.globalCompositeOperation = layer.blendMode;
64
40
  }
65
41
  if (layer.transform) {
66
- this.applyTransform(layer.transform);
42
+ const layerDimensions = this.getLayerDimensions(layer);
43
+ this.applyTransform(layer.transform, layerDimensions);
67
44
  }
68
45
  switch (layer.type) {
69
46
  case "video":
@@ -83,9 +60,60 @@ class LayerRenderer {
83
60
  this.ctx.restore();
84
61
  }
85
62
  }
86
- applyTransform(transform) {
87
- const centerX = this.width * (transform.anchorX ?? 0.5);
88
- const centerY = this.height * (transform.anchorY ?? 0.5);
63
+ parseDimension(value, canvasSize) {
64
+ if (value === void 0) return void 0;
65
+ if (typeof value === "number") return value;
66
+ const strValue = value;
67
+ if (strValue.includes("%")) {
68
+ const numValue = parseFloat(strValue);
69
+ return isNaN(numValue) ? void 0 : numValue / 100 * canvasSize;
70
+ }
71
+ const parsed = parseFloat(strValue);
72
+ return isNaN(parsed) ? void 0 : parsed;
73
+ }
74
+ getLayerDimensions(layer) {
75
+ if (layer.type === "image") {
76
+ const imageLayer = layer;
77
+ if (imageLayer.source) {
78
+ const imgWidth = imageLayer.source.width;
79
+ const imgHeight = imageLayer.source.height;
80
+ const isAttachment = !!imageLayer.attachmentId;
81
+ if (isAttachment) {
82
+ const targetWidthRaw = imageLayer.targetWidth;
83
+ const targetHeightRaw = imageLayer.targetHeight;
84
+ const targetWidth = this.parseDimension(targetWidthRaw, this.width);
85
+ const targetHeight = this.parseDimension(targetHeightRaw, this.height);
86
+ if (targetWidth && targetHeight) {
87
+ return { width: targetWidth, height: targetHeight };
88
+ } else if (targetWidth) {
89
+ return {
90
+ width: targetWidth,
91
+ height: imgHeight / imgWidth * targetWidth
92
+ };
93
+ } else if (targetHeight) {
94
+ return {
95
+ width: imgWidth / imgHeight * targetHeight,
96
+ height: targetHeight
97
+ };
98
+ }
99
+ }
100
+ return { width: imgWidth, height: imgHeight };
101
+ }
102
+ } else if (layer.type === "video") {
103
+ const videoLayer = layer;
104
+ const videoFrame = videoLayer.videoFrame;
105
+ return {
106
+ width: videoFrame.displayWidth || videoFrame.codedWidth,
107
+ height: videoFrame.displayHeight || videoFrame.codedHeight
108
+ };
109
+ }
110
+ return { width: this.width, height: this.height };
111
+ }
112
+ applyTransform(transform, layerDimensions) {
113
+ const anchorX = transform.anchorX ?? 0.5;
114
+ const anchorY = transform.anchorY ?? 0.5;
115
+ const centerX = layerDimensions.width * anchorX;
116
+ const centerY = layerDimensions.height * anchorY;
89
117
  this.ctx.translate(transform.x + centerX, transform.y + centerY);
90
118
  if (transform.rotation) {
91
119
  this.ctx.rotate(transform.rotation);
@@ -138,6 +166,33 @@ class LayerRenderer {
138
166
  if (!source) {
139
167
  return;
140
168
  }
169
+ const isAttachment = !!layer.attachmentId;
170
+ const imgWidth = source.width;
171
+ const imgHeight = source.height;
172
+ let renderWidth;
173
+ let renderHeight;
174
+ if (isAttachment) {
175
+ const targetWidthRaw = layer.targetWidth;
176
+ const targetHeightRaw = layer.targetHeight;
177
+ const targetWidth = this.parseDimension(targetWidthRaw, this.width);
178
+ const targetHeight = this.parseDimension(targetHeightRaw, this.height);
179
+ if (targetWidth && targetHeight) {
180
+ renderWidth = targetWidth;
181
+ renderHeight = targetHeight;
182
+ } else if (targetWidth) {
183
+ renderWidth = targetWidth;
184
+ renderHeight = imgHeight / imgWidth * targetWidth;
185
+ } else if (targetHeight) {
186
+ renderHeight = targetHeight;
187
+ renderWidth = imgWidth / imgHeight * targetHeight;
188
+ } else {
189
+ renderWidth = imgWidth;
190
+ renderHeight = imgHeight;
191
+ }
192
+ } else {
193
+ renderWidth = this.width;
194
+ renderHeight = this.height;
195
+ }
141
196
  if (crop) {
142
197
  this.ctx.drawImage(
143
198
  source,
@@ -147,11 +202,11 @@ class LayerRenderer {
147
202
  crop.height,
148
203
  0,
149
204
  0,
150
- this.width,
151
- this.height
205
+ renderWidth,
206
+ renderHeight
152
207
  );
153
208
  } else {
154
- this.ctx.drawImage(source, 0, 0, this.width, this.height);
209
+ this.ctx.drawImage(source, 0, 0, renderWidth, renderHeight);
155
210
  }
156
211
  }
157
212
  }
@@ -834,9 +889,87 @@ class VideoComposer {
834
889
  this.filterProcessor.clearCache();
835
890
  }
836
891
  }
892
+ function interpolateKeyframes(keyframes, timeUs) {
893
+ const defaultTransform = {
894
+ x: 0,
895
+ y: 0,
896
+ scaleX: 1,
897
+ scaleY: 1,
898
+ rotation: 0,
899
+ anchorX: 0.5,
900
+ anchorY: 0.5
901
+ };
902
+ if (keyframes.length === 0) {
903
+ return { transform: defaultTransform };
904
+ }
905
+ const firstFrame = keyframes[0];
906
+ const lastFrame = keyframes[keyframes.length - 1];
907
+ if (timeUs <= firstFrame.time) {
908
+ return {
909
+ transform: { ...defaultTransform, ...firstFrame.transform },
910
+ opacity: firstFrame.opacity
911
+ };
912
+ }
913
+ if (timeUs >= lastFrame.time) {
914
+ return {
915
+ transform: { ...defaultTransform, ...lastFrame.transform },
916
+ opacity: lastFrame.opacity
917
+ };
918
+ }
919
+ let prevFrame = firstFrame;
920
+ let nextFrame = lastFrame;
921
+ for (let i = 0; i < keyframes.length - 1; i++) {
922
+ const currentFrame = keyframes[i];
923
+ const followingFrame = keyframes[i + 1];
924
+ if (timeUs >= currentFrame.time && timeUs < followingFrame.time) {
925
+ prevFrame = currentFrame;
926
+ nextFrame = followingFrame;
927
+ break;
928
+ }
929
+ }
930
+ const duration = nextFrame.time - prevFrame.time;
931
+ const elapsed = timeUs - prevFrame.time;
932
+ const progress = elapsed / duration;
933
+ const easedProgress = applyEasing(progress, prevFrame.easing ?? "linear");
934
+ const prevTransform = prevFrame.transform ?? { x: 0, y: 0 };
935
+ const nextTransform = nextFrame.transform ?? { x: 0, y: 0 };
936
+ const transform = interpolateTransform(prevTransform, nextTransform, easedProgress);
937
+ const opacity = prevFrame.opacity !== void 0 && nextFrame.opacity !== void 0 ? lerp(prevFrame.opacity, nextFrame.opacity, easedProgress) : void 0;
938
+ return { transform, opacity };
939
+ }
940
+ function interpolateTransform(from, to, t) {
941
+ return {
942
+ x: lerp(from.x ?? 0, to.x ?? 0, t),
943
+ y: lerp(from.y ?? 0, to.y ?? 0, t),
944
+ scaleX: lerp(from.scaleX ?? 1, to.scaleX ?? 1, t),
945
+ scaleY: lerp(from.scaleY ?? 1, to.scaleY ?? 1, t),
946
+ rotation: lerp(from.rotation ?? 0, to.rotation ?? 0, t),
947
+ anchorX: lerp(from.anchorX ?? 0.5, to.anchorX ?? 0.5, t),
948
+ anchorY: lerp(from.anchorY ?? 0.5, to.anchorY ?? 0.5, t)
949
+ };
950
+ }
951
+ function lerp(a, b, t) {
952
+ return a + (b - a) * t;
953
+ }
954
+ function applyEasing(t, easing) {
955
+ switch (easing) {
956
+ case "linear":
957
+ return t;
958
+ case "ease-in":
959
+ return t * t * t;
960
+ case "ease-out": {
961
+ const oneMinusT = 1 - t;
962
+ return 1 - oneMinusT * oneMinusT * oneMinusT;
963
+ }
964
+ case "ease-in-out":
965
+ return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
966
+ default:
967
+ return t;
968
+ }
969
+ }
837
970
  function resolveActiveLayers(layers, timestamp) {
838
971
  return layers.filter((layer) => {
839
- if (layer.layerId.includes("base-video")) {
972
+ if (!layer.payload.attachmentId) {
840
973
  return true;
841
974
  }
842
975
  if (layer.status !== "ready") {
@@ -847,7 +980,7 @@ function resolveActiveLayers(layers, timestamp) {
847
980
  );
848
981
  });
849
982
  }
850
- function materializeLayer(layer, frame) {
983
+ function materializeLayer(layer, frame, imageMap, globalTimeUs) {
851
984
  const baseLayer = {
852
985
  id: layer.layerId,
853
986
  type: layer.type,
@@ -877,22 +1010,74 @@ function materializeLayer(layer, frame) {
877
1010
  lineHeight: payload.lineHeight,
878
1011
  textAlign: payload.align,
879
1012
  verticalAlign: "bottom"
880
- // Subtitles positioned at bottom
881
1013
  };
882
1014
  }
883
1015
  if (layer.type === "image") {
884
1016
  const payload = layer.payload;
885
- const source = payload.bitmapHandle ?? null;
886
- if (source) {
887
- return {
888
- ...baseLayer,
889
- type: "image",
890
- source
1017
+ const resourceId = payload.resourceId;
1018
+ const source = imageMap.get(resourceId) ?? null;
1019
+ const imageLayer = {
1020
+ ...baseLayer,
1021
+ type: "image",
1022
+ source,
1023
+ attachmentId: payload.attachmentId
1024
+ };
1025
+ if (payload.targetWidth !== void 0) {
1026
+ imageLayer.targetWidth = payload.targetWidth;
1027
+ }
1028
+ if (payload.targetHeight !== void 0) {
1029
+ imageLayer.targetHeight = payload.targetHeight;
1030
+ }
1031
+ if (payload.animation && globalTimeUs !== void 0) {
1032
+ const animState = computeAnimationState(payload.animation, globalTimeUs);
1033
+ if (!animState.visible) {
1034
+ return null;
1035
+ }
1036
+ imageLayer.transform = {
1037
+ x: animState.transform.x ?? 0,
1038
+ y: animState.transform.y ?? 0,
1039
+ scaleX: animState.transform.scaleX ?? 1,
1040
+ scaleY: animState.transform.scaleY ?? 1,
1041
+ rotation: animState.transform.rotation ?? 0,
1042
+ anchorX: animState.transform.anchorX ?? 0.5,
1043
+ anchorY: animState.transform.anchorY ?? 0.5
891
1044
  };
1045
+ if (animState.opacity !== void 0) {
1046
+ imageLayer.opacity = animState.opacity;
1047
+ }
892
1048
  }
1049
+ return imageLayer;
893
1050
  }
894
1051
  return baseLayer;
895
1052
  }
1053
+ function computeAnimationState(animation, globalTimeUs) {
1054
+ const { position, keyframes, overlayClipStartUs } = animation;
1055
+ const relativeTimeUs = globalTimeUs - overlayClipStartUs;
1056
+ if (relativeTimeUs < 0 || relativeTimeUs > keyframes[keyframes.length - 1].time) {
1057
+ return {
1058
+ transform: { x: 0, y: 0, scaleX: 1, scaleY: 1, rotation: 0, anchorX: 0.5, anchorY: 0.5 },
1059
+ opacity: 0,
1060
+ visible: false
1061
+ };
1062
+ }
1063
+ const animState = interpolateKeyframes(keyframes, relativeTimeUs);
1064
+ const rotationDeg = animState.transform?.rotation ?? 0;
1065
+ const rotationRad = rotationDeg * Math.PI / 180;
1066
+ const finalTransform = {
1067
+ x: position.x + (animState.transform?.x ?? 0),
1068
+ y: position.y + (animState.transform?.y ?? 0),
1069
+ scaleX: animState.transform?.scaleX ?? 1,
1070
+ scaleY: animState.transform?.scaleY ?? 1,
1071
+ rotation: rotationRad,
1072
+ anchorX: animState.transform?.anchorX ?? 0.5,
1073
+ anchorY: animState.transform?.anchorY ?? 0.5
1074
+ };
1075
+ return {
1076
+ transform: finalTransform,
1077
+ opacity: animState.opacity,
1078
+ visible: true
1079
+ };
1080
+ }
896
1081
  class VideoComposeWorker {
897
1082
  channel;
898
1083
  composer = null;
@@ -902,7 +1087,7 @@ class VideoComposeWorker {
902
1087
  upstreamPort = null;
903
1088
  instructions = null;
904
1089
  streamState = null;
905
- imageBitmap = null;
1090
+ imageMap = /* @__PURE__ */ new Map();
906
1091
  constructor() {
907
1092
  this.channel = new WorkerChannel(self, {
908
1093
  name: "VideoComposeWorker",
@@ -947,7 +1132,6 @@ class VideoComposeWorker {
947
1132
  */
948
1133
  async handleConfigure(payload) {
949
1134
  const { config, initial } = payload;
950
- console.log("[VideoComposeWorker] handleConfigure", config, initial);
951
1135
  const hasValidDimensions = config.width > 0 && config.height > 0;
952
1136
  const hasValidFps = config.fps > 0;
953
1137
  if (!hasValidDimensions || !hasValidFps) {
@@ -1067,8 +1251,8 @@ class VideoComposeWorker {
1067
1251
  this.upstreamPort?.close();
1068
1252
  this.downstreamPort = null;
1069
1253
  this.upstreamPort = null;
1070
- this.imageBitmap?.close();
1071
- this.imageBitmap = null;
1254
+ this.imageMap.forEach((bitmap) => bitmap.close());
1255
+ this.imageMap.clear();
1072
1256
  this.instructions = null;
1073
1257
  this.streamState = null;
1074
1258
  this.channel.state = WorkerState.Disposed;
@@ -1091,16 +1275,21 @@ class VideoComposeWorker {
1091
1275
  * only accepts ImageBitmap/OffscreenCanvas, not HTMLImageElement or Blob
1092
1276
  */
1093
1277
  async handleReceiveImage(payload) {
1094
- const { sessionId, imageBitmap } = payload;
1278
+ const { resourceId, sessionId, imageBitmap } = payload;
1095
1279
  if (!this.sessionId) {
1096
1280
  this.sessionId = sessionId;
1097
1281
  }
1098
- if (this.imageBitmap) {
1099
- this.imageBitmap.close();
1282
+ const existing = this.imageMap.get(resourceId);
1283
+ if (existing) {
1284
+ existing.close();
1100
1285
  }
1101
- this.imageBitmap = imageBitmap;
1286
+ this.imageMap.set(resourceId, imageBitmap);
1102
1287
  if (this.instructions) {
1103
- await this.startImageFrameStream();
1288
+ const mainLayer = this.instructions.layers.find((l) => !l.payload.attachmentId);
1289
+ const mainResourceId = mainLayer?.payload.resourceId;
1290
+ if (resourceId === mainResourceId) {
1291
+ await this.startImageFrameStream();
1292
+ }
1104
1293
  }
1105
1294
  return { success: true };
1106
1295
  }
@@ -1111,8 +1300,8 @@ class VideoComposeWorker {
1111
1300
  this.upstreamPort?.close();
1112
1301
  this.downstreamPort = null;
1113
1302
  this.upstreamPort = null;
1114
- this.imageBitmap?.close();
1115
- this.imageBitmap = null;
1303
+ this.imageMap.forEach((bitmap) => bitmap.close());
1304
+ this.imageMap.clear();
1116
1305
  return { success: true };
1117
1306
  }
1118
1307
  async startImageFrameStream() {
@@ -1123,6 +1312,14 @@ class VideoComposeWorker {
1123
1312
  if (!timeline) {
1124
1313
  return;
1125
1314
  }
1315
+ const mainLayer = this.instructions.layers.find((l) => !l.payload.attachmentId);
1316
+ if (!mainLayer) return;
1317
+ const mainResourceId = mainLayer.payload.resourceId;
1318
+ const imageBitmap = this.imageMap.get(mainResourceId);
1319
+ if (!imageBitmap) {
1320
+ console.warn("[VideoComposeWorker] Main track ImageBitmap not found:", mainResourceId);
1321
+ return;
1322
+ }
1126
1323
  const { composeStream, cacheStream, encodeStream } = this.composer.createStreams();
1127
1324
  const { clipDurationUs, compositionFps } = timeline;
1128
1325
  let currentTimeUs = 0;
@@ -1132,7 +1329,7 @@ class VideoComposeWorker {
1132
1329
  controller.close();
1133
1330
  return;
1134
1331
  }
1135
- const videoFrame = new VideoFrame(this.imageBitmap, {
1332
+ const videoFrame = new VideoFrame(imageBitmap, {
1136
1333
  timestamp: currentTimeUs,
1137
1334
  duration: frameDurationFromFps(compositionFps)
1138
1335
  });
@@ -1164,21 +1361,24 @@ class VideoComposeWorker {
1164
1361
  }
1165
1362
  }
1166
1363
  buildComposeRequest(instruction, frame) {
1167
- const normalizedTime = this.computeTimelineTimestamp(frame, instruction.baseConfig);
1168
- const clipStartUs = instruction.baseConfig.timeline?.clipStartUs ?? 0;
1364
+ const clipRelativeTime = this.computeTimelineTimestamp(frame, instruction.baseConfig);
1169
1365
  const clipDurationUs = instruction.baseConfig.timeline?.clipDurationUs ?? Infinity;
1170
- const clipEndUs = clipStartUs + clipDurationUs;
1171
- if (normalizedTime < clipStartUs || normalizedTime >= clipEndUs) {
1366
+ if (clipRelativeTime < 0 || clipRelativeTime >= clipDurationUs) {
1172
1367
  return null;
1173
1368
  }
1174
- const clipRelativeTime = normalizedTime - clipStartUs;
1175
1369
  const activeLayers = resolveActiveLayers(instruction.layers, clipRelativeTime);
1176
1370
  if (!activeLayers.length) {
1177
1371
  return null;
1178
1372
  }
1179
- const layers = activeLayers.map((layer) => materializeLayer(layer, frame));
1373
+ const clipStartUs = instruction.baseConfig.timeline?.clipStartUs ?? 0;
1374
+ const globalTimeUs = clipStartUs + clipRelativeTime;
1375
+ const layers = activeLayers.map((layer) => materializeLayer(layer, frame, this.imageMap, globalTimeUs)).filter((layer) => layer !== null);
1376
+ if (!layers.length) {
1377
+ return null;
1378
+ }
1180
1379
  return {
1181
- timeUs: normalizedTime,
1380
+ timeUs: clipRelativeTime,
1381
+ globalTimeUs,
1182
1382
  layers,
1183
1383
  transition: VideoComposeWorker.buildTransition(
1184
1384
  instruction.transitions,
@@ -1208,7 +1408,6 @@ class VideoComposeWorker {
1208
1408
  computeTimelineTimestamp(frame, config) {
1209
1409
  if (!this.streamState) {
1210
1410
  this.streamState = {
1211
- baseTimestamp: null,
1212
1411
  lastSourceTimestamp: null,
1213
1412
  nextFrameIndex: 0
1214
1413
  };
@@ -1219,42 +1418,21 @@ class VideoComposeWorker {
1219
1418
  this.streamState.lastSourceTimestamp = frame.timestamp ?? null;
1220
1419
  return ts;
1221
1420
  }
1222
- const { clipStartUs, compositionFps } = timeline;
1421
+ const { compositionFps } = timeline;
1223
1422
  const sourceTimestamp = frame.timestamp ?? null;
1224
1423
  if (sourceTimestamp !== null && this.streamState.lastSourceTimestamp !== null && sourceTimestamp < this.streamState.lastSourceTimestamp) {
1225
- this.streamState.baseTimestamp = null;
1226
1424
  this.streamState.nextFrameIndex = 0;
1227
1425
  }
1228
- if (this.streamState.baseTimestamp === null) {
1229
- this.streamState.baseTimestamp = sourceTimestamp ?? 0;
1230
- this.streamState.nextFrameIndex = 0;
1231
- if (this.streamState.baseTimestamp > 1e3) {
1232
- console.warn(
1233
- `[VideoComposeWorker] First frame timestamp is ${this.streamState.baseTimestamp}us, expected ~0. Check MP4Demuxer normalization.`
1234
- );
1235
- }
1236
- }
1237
1426
  const frameDuration = frameDurationFromFps(compositionFps);
1238
1427
  let frameIndex = this.streamState.nextFrameIndex;
1239
1428
  if (sourceTimestamp !== null) {
1240
- const approxIndex = frameIndexFromTimestamp(
1241
- this.streamState.baseTimestamp,
1242
- sourceTimestamp,
1243
- compositionFps,
1244
- "nearest"
1245
- );
1429
+ const approxIndex = Math.round(sourceTimestamp / frameDuration);
1246
1430
  frameIndex = Math.max(frameIndex, approxIndex);
1247
1431
  }
1248
- const rawTimeline = clipStartUs + frameIndex * frameDuration;
1249
- const timelineTime = quantizeTimestampToFrame(
1250
- rawTimeline,
1251
- clipStartUs,
1252
- compositionFps,
1253
- "nearest"
1254
- );
1432
+ const relativeTimeUs = frameIndex * frameDuration;
1255
1433
  this.streamState.nextFrameIndex = frameIndex + 1;
1256
1434
  this.streamState.lastSourceTimestamp = sourceTimestamp;
1257
- return timelineTime;
1435
+ return relativeTimeUs;
1258
1436
  }
1259
1437
  }
1260
1438
  const worker = new VideoComposeWorker();