@remotion/media 4.0.484 → 4.0.485

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.
@@ -6,6 +6,7 @@ export type CanvasAheadOfTimeNext = {
6
6
  type: 'pending';
7
7
  wait: () => Promise<WrappedCanvas | null>;
8
8
  };
9
+ export declare const releaseStableFrame: (frame: WrappedCanvas | null) => void;
9
10
  export declare const canvasesAheadOfTime: (videoSink: CanvasSink, startTimestamp?: number | undefined) => {
10
11
  next: () => CanvasAheadOfTimeNext;
11
12
  closeIterator: () => Promise<void>;
@@ -100,13 +100,13 @@ var getTimeInSeconds = ({
100
100
  if (mediaDurationInSeconds === null && loop && ifNoMediaDuration === "fail") {
101
101
  throw new Error(`Could not determine duration of ${src}, but "loop" was set.`);
102
102
  }
103
- const loopDuration = loop ? Internals2.calculateMediaDuration({
103
+ const loopDurationInFrames = loop ? Internals2.calculateMediaDuration({
104
104
  trimAfter,
105
105
  mediaDurationInFrames: mediaDurationInSeconds ? mediaDurationInSeconds * fps : Infinity,
106
106
  playbackRate: 1,
107
107
  trimBefore
108
- }) / fps : Infinity;
109
- const timeInSeconds = unloopedTimeInSeconds * playbackRate % loopDuration;
108
+ }) : Infinity;
109
+ const timeInSeconds = loop ? unloopedTimeInSeconds * playbackRate * fps % loopDurationInFrames / fps : unloopedTimeInSeconds * playbackRate;
110
110
  if ((trimAfter ?? null) !== null && !loop) {
111
111
  const time = (trimAfter - (trimBefore ?? 0)) / fps;
112
112
  if (timeInSeconds >= time) {
@@ -1043,6 +1043,43 @@ var roundTo4Digits = (timestamp) => {
1043
1043
 
1044
1044
  // src/canvas-ahead-of-time.ts
1045
1045
  var BUFFER_SIZE = 3;
1046
+ var releaseCanvas = Symbol("releaseCanvas");
1047
+ var releaseStableFrame = (frame) => {
1048
+ frame?.[releaseCanvas]?.();
1049
+ };
1050
+ var makeStableFramePool = () => {
1051
+ const availableCanvases = [];
1052
+ const makeStableFrame = (frame) => {
1053
+ const { canvas } = frame;
1054
+ const stableCanvas = availableCanvases.pop() ?? new OffscreenCanvas(canvas.width, canvas.height);
1055
+ if (stableCanvas.width !== canvas.width) {
1056
+ stableCanvas.width = canvas.width;
1057
+ }
1058
+ if (stableCanvas.height !== canvas.height) {
1059
+ stableCanvas.height = canvas.height;
1060
+ }
1061
+ const context = stableCanvas.getContext("2d");
1062
+ if (!context) {
1063
+ throw new Error("Could not create canvas context");
1064
+ }
1065
+ context.drawImage(canvas, 0, 0);
1066
+ let released = false;
1067
+ const stableFrame = {
1068
+ canvas: stableCanvas,
1069
+ duration: frame.duration,
1070
+ timestamp: frame.timestamp,
1071
+ [releaseCanvas]: () => {
1072
+ if (released) {
1073
+ return;
1074
+ }
1075
+ released = true;
1076
+ availableCanvases.push(stableCanvas);
1077
+ }
1078
+ };
1079
+ return stableFrame;
1080
+ };
1081
+ return { makeStableFrame };
1082
+ };
1046
1083
  var canvasesAheadOfTime = (videoSink, startTimestamp) => {
1047
1084
  const iterator = videoSink.canvases(startTimestamp);
1048
1085
  const buffer = [];
@@ -1050,8 +1087,9 @@ var canvasesAheadOfTime = (videoSink, startTimestamp) => {
1050
1087
  let reachedEnd = false;
1051
1088
  let closed = false;
1052
1089
  let inFlight = null;
1053
- const closeFrame = (frame) => {
1054
- frame.close?.();
1090
+ const stableFramePool = makeStableFramePool();
1091
+ const takeFrame = (frame) => {
1092
+ return stableFramePool.makeStableFrame(frame);
1055
1093
  };
1056
1094
  const fillNext = () => {
1057
1095
  if (chaining || reachedEnd || closed)
@@ -1062,17 +1100,18 @@ var canvasesAheadOfTime = (videoSink, startTimestamp) => {
1062
1100
  const slot = { promise: iterator.next(), resolved: null };
1063
1101
  buffer.push(slot);
1064
1102
  inFlight = slot.promise.then((result) => {
1065
- slot.resolved = result;
1066
1103
  chaining = false;
1067
1104
  inFlight = null;
1068
1105
  if (result.done) {
1106
+ slot.resolved = result;
1069
1107
  reachedEnd = true;
1070
1108
  return;
1071
1109
  }
1072
1110
  if (closed) {
1073
- closeFrame(result.value);
1111
+ slot.resolved = { done: true, value: undefined };
1074
1112
  return;
1075
1113
  }
1114
+ slot.resolved = { ...result, value: takeFrame(result.value) };
1076
1115
  fillNext();
1077
1116
  }, () => {
1078
1117
  chaining = false;
@@ -1100,7 +1139,7 @@ var canvasesAheadOfTime = (videoSink, startTimestamp) => {
1100
1139
  return next2.resolved.done ? null : next2.resolved.value;
1101
1140
  }
1102
1141
  const result = await next2.promise;
1103
- return result.done ? null : result.value;
1142
+ return result.done ? null : takeFrame(result.value);
1104
1143
  }
1105
1144
  };
1106
1145
  }
@@ -1114,7 +1153,10 @@ var canvasesAheadOfTime = (videoSink, startTimestamp) => {
1114
1153
  type: "pending",
1115
1154
  wait: async () => {
1116
1155
  const result = await slot.promise;
1117
- return result.done ? null : result.value;
1156
+ if (slot.resolved) {
1157
+ return slot.resolved.done ? null : slot.resolved.value;
1158
+ }
1159
+ return result.done ? null : takeFrame(result.value);
1118
1160
  }
1119
1161
  };
1120
1162
  };
@@ -1122,7 +1164,7 @@ var canvasesAheadOfTime = (videoSink, startTimestamp) => {
1122
1164
  closed = true;
1123
1165
  for (const slot of buffer) {
1124
1166
  if (slot.resolved && !slot.resolved.done) {
1125
- closeFrame(slot.resolved.value);
1167
+ releaseStableFrame(slot.resolved.value);
1126
1168
  }
1127
1169
  }
1128
1170
  buffer.length = 0;
@@ -1169,6 +1211,12 @@ var createVideoIterator = async (timeToSeek, cache) => {
1169
1211
  const initialFrame = firstAwait && firstAwait.type === "ready" ? firstAwait.frame : await firstAwait.wait();
1170
1212
  let lastReturnedFrame = initialFrame;
1171
1213
  let peekedFrame = null;
1214
+ const setLastReturnedFrame = (frame) => {
1215
+ if (lastReturnedFrame !== frame) {
1216
+ releaseStableFrame(lastReturnedFrame);
1217
+ }
1218
+ lastReturnedFrame = frame;
1219
+ };
1172
1220
  const peek = async () => {
1173
1221
  if (peekedFrame) {
1174
1222
  return peekedFrame;
@@ -1184,7 +1232,7 @@ var createVideoIterator = async (timeToSeek, cache) => {
1184
1232
  const getNextOrNullIfNotAvailable = () => {
1185
1233
  if (peekedFrame) {
1186
1234
  const frame = peekedFrame;
1187
- lastReturnedFrame = frame;
1235
+ setLastReturnedFrame(frame);
1188
1236
  const retValue = {
1189
1237
  type: "got-frame-or-end",
1190
1238
  frame
@@ -1199,7 +1247,7 @@ var createVideoIterator = async (timeToSeek, cache) => {
1199
1247
  waitPromise: async () => {
1200
1248
  const res = await next.wait();
1201
1249
  if (res) {
1202
- lastReturnedFrame = res;
1250
+ setLastReturnedFrame(res);
1203
1251
  } else {
1204
1252
  iteratorEnded = true;
1205
1253
  }
@@ -1208,7 +1256,7 @@ var createVideoIterator = async (timeToSeek, cache) => {
1208
1256
  };
1209
1257
  }
1210
1258
  if (next.frame) {
1211
- lastReturnedFrame = next.frame;
1259
+ setLastReturnedFrame(next.frame);
1212
1260
  } else {
1213
1261
  iteratorEnded = true;
1214
1262
  }
@@ -1219,15 +1267,21 @@ var createVideoIterator = async (timeToSeek, cache) => {
1219
1267
  };
1220
1268
  const destroy = () => {
1221
1269
  destroyed = true;
1270
+ releaseStableFrame(lastReturnedFrame);
1271
+ if (peekedFrame !== lastReturnedFrame) {
1272
+ releaseStableFrame(peekedFrame);
1273
+ }
1222
1274
  lastReturnedFrame = null;
1275
+ peekedFrame = null;
1223
1276
  iterator.closeIterator().catch(() => {
1224
1277
  return;
1225
1278
  });
1226
1279
  };
1227
1280
  const tryToSatisfySeek = async (time) => {
1281
+ const timestamp = roundTo4Digits(time);
1228
1282
  if (lastReturnedFrame) {
1229
1283
  const frameTimestamp = roundTo4Digits(lastReturnedFrame.timestamp);
1230
- if (roundTo4Digits(time) < frameTimestamp) {
1284
+ if (timestamp < frameTimestamp) {
1231
1285
  const lastFrameWasInitialFrame = lastReturnedFrame === initialFrame;
1232
1286
  const firstFrameDoesSatisfy = lastFrameWasInitialFrame && roundTo4Digits(time) >= roundTo4Digits(timeToSeek);
1233
1287
  if (firstFrameDoesSatisfy) {
@@ -1249,7 +1303,6 @@ var createVideoIterator = async (timeToSeek, cache) => {
1249
1303
  }
1250
1304
  }
1251
1305
  const frameEndTimestamp = roundTo4Digits(lastReturnedFrame.timestamp + lastFrameDuration);
1252
- const timestamp = roundTo4Digits(time);
1253
1306
  if (frameTimestamp <= timestamp && frameEndTimestamp > timestamp) {
1254
1307
  return {
1255
1308
  type: "satisfied",
@@ -1293,7 +1346,6 @@ var createVideoIterator = async (timeToSeek, cache) => {
1293
1346
  }
1294
1347
  const frameTimestamp = roundTo4Digits(frame.frame.timestamp);
1295
1348
  const frameEndTimestamp = roundTo4Digits(frame.frame.timestamp + frame.frame.duration);
1296
- const timestamp = roundTo4Digits(time);
1297
1349
  if (frameTimestamp <= timestamp && frameEndTimestamp > timestamp) {
1298
1350
  return {
1299
1351
  type: "satisfied",
@@ -1349,7 +1401,7 @@ var videoIteratorManager = async ({
1349
1401
  }
1350
1402
  }
1351
1403
  const canvasSink = new CanvasSink(videoTrack, {
1352
- poolSize: 2,
1404
+ poolSize: 3,
1353
1405
  fit: "contain",
1354
1406
  alpha: true
1355
1407
  });
@@ -4905,6 +4957,8 @@ var VideoForPreviewAssertedShowing = ({
4905
4957
  const effectChainState = useEffectChainState();
4906
4958
  const effectsRef = useRef2(effects);
4907
4959
  effectsRef.current = effects;
4960
+ const onErrorRef = useRef2(onError);
4961
+ onErrorRef.current = onError;
4908
4962
  const effectChainStateRef = useRef2(effectChainState);
4909
4963
  effectChainStateRef.current = effectChainState;
4910
4964
  const parentSequence = useContext4(SequenceContext2);
@@ -5009,7 +5063,7 @@ var VideoForPreviewAssertedShowing = ({
5009
5063
  }
5010
5064
  const handleError = (error, fallbackMessage) => {
5011
5065
  const [action, errorToUse] = callOnErrorAndResolve({
5012
- onError,
5066
+ onError: onErrorRef.current,
5013
5067
  error,
5014
5068
  disallowFallback: disallowFallbackToOffthreadVideo,
5015
5069
  isClientSideRendering: false,
@@ -5044,7 +5098,7 @@ var VideoForPreviewAssertedShowing = ({
5044
5098
  }
5045
5099
  }).catch((error) => {
5046
5100
  const [action, errorToUse] = callOnErrorAndResolve({
5047
- onError,
5101
+ onError: onErrorRef.current,
5048
5102
  error,
5049
5103
  disallowFallback: disallowFallbackToOffthreadVideo,
5050
5104
  isClientSideRendering: false,
@@ -5059,7 +5113,7 @@ var VideoForPreviewAssertedShowing = ({
5059
5113
  } catch (error) {
5060
5114
  const [action, errorToUse] = callOnErrorAndResolve({
5061
5115
  error,
5062
- onError,
5116
+ onError: onErrorRef.current,
5063
5117
  disallowFallback: disallowFallbackToOffthreadVideo,
5064
5118
  isClientSideRendering: false,
5065
5119
  clientSideError: error
@@ -5090,7 +5144,6 @@ var VideoForPreviewAssertedShowing = ({
5090
5144
  preloadedSrc,
5091
5145
  sharedAudioContext,
5092
5146
  videoConfig.fps,
5093
- onError,
5094
5147
  credentials,
5095
5148
  initialRequestInit,
5096
5149
  setMediaDurationInSeconds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.484",
3
+ "version": "4.0.485",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -22,8 +22,8 @@
22
22
  "make": "tsgo && bun --env-file=../.env.bundle bundle.ts"
23
23
  },
24
24
  "dependencies": {
25
- "mediabunny": "1.47.0",
26
- "remotion": "4.0.484",
25
+ "mediabunny": "1.50.3",
26
+ "remotion": "4.0.485",
27
27
  "zod": "4.3.6"
28
28
  },
29
29
  "peerDependencies": {
@@ -31,8 +31,8 @@
31
31
  "react-dom": ">=16.8.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@remotion/eslint-config-internal": "4.0.484",
35
- "@remotion/player": "4.0.484",
34
+ "@remotion/eslint-config-internal": "4.0.485",
35
+ "@remotion/player": "4.0.485",
36
36
  "@vitest/browser-webdriverio": "4.0.9",
37
37
  "eslint": "9.19.0",
38
38
  "react": "19.2.3",