remotion 4.0.482 → 4.0.484

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.
@@ -11,6 +11,7 @@ const enable_sequence_stack_traces_js_1 = require("./enable-sequence-stack-trace
11
11
  const interactivity_schema_js_1 = require("./interactivity-schema.js");
12
12
  const Sequence_js_1 = require("./Sequence.js");
13
13
  const use_delay_render_js_1 = require("./use-delay-render.js");
14
+ const use_remotion_environment_js_1 = require("./use-remotion-environment.js");
14
15
  const use_video_config_js_1 = require("./use-video-config.js");
15
16
  const with_interactivity_schema_js_1 = require("./with-interactivity-schema.js");
16
17
  // Memoize the support check across the session — neither the platform
@@ -58,6 +59,10 @@ function resolveHtmlInCanvasPixelDensity(pixelDensity) {
58
59
  }
59
60
  return pixelDensity;
60
61
  }
62
+ const isMissingPaintRecordError = (error) => {
63
+ return error instanceof DOMException && error.name === 'InvalidStateError';
64
+ };
65
+ const missingPaintRecordMessage = 'HtmlInCanvas: Expected the element to be inside the viewport during rendering, but Chrome had no cached paint record for it.';
61
66
  const resizeOffscreenCanvas = ({ offscreen, width, height, }) => {
62
67
  if (offscreen.width !== width) {
63
68
  offscreen.width = width;
@@ -85,6 +90,7 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, c
85
90
  const canvasWidth = Math.ceil(width * resolvedPixelDensity);
86
91
  const canvasHeight = Math.ceil(height * resolvedPixelDensity);
87
92
  const { continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)();
93
+ const { isRendering } = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
88
94
  if (!(0, exports.isHtmlInCanvasSupported)()) {
89
95
  cancelRender(new Error(exports.HTML_IN_CANVAS_UNSUPPORTED_MESSAGE));
90
96
  }
@@ -139,35 +145,68 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, c
139
145
  }
140
146
  const handle = (0, delay_render_js_1.delayRender)('onPaint');
141
147
  if (!initializedRef.current) {
142
- initializedRef.current = true;
143
148
  // `onInit` may be async (e.g. WebGPU `requestAdapter`/`requestDevice`).
144
149
  // Capture an `ElementImage` here only for `onInit` consumers — do NOT
145
150
  // reuse it for the paint handler below, because awaiting `onInit`
146
151
  // can invalidate the capture's paint context, leaving subsequent
147
152
  // uploads (e.g. `copyElementImageToTexture`) failing with
148
153
  // "No context found for ElementImage" on the very first paint.
149
- const initImage = placeholderCanvas.captureElementImage(element);
150
- const currentOnInit = onInitRef.current;
151
- if (currentOnInit) {
152
- const cleanup = await currentOnInit({
153
- canvas: offscreen,
154
- element,
155
- elementImage: initImage,
156
- pixelDensity: resolvedPixelDensity,
157
- });
158
- if (typeof cleanup !== 'function') {
159
- throw new Error('HtmlInCanvas: when `onInit` is provided, it must return a cleanup function, or a Promise that resolves to one.');
154
+ let initImage = null;
155
+ try {
156
+ initImage = placeholderCanvas.captureElementImage(element);
157
+ }
158
+ catch (error) {
159
+ if (isMissingPaintRecordError(error) && !isRendering) {
160
+ // Element may be outside viewport (no cached paint record).
161
+ // Skip init — will retry on the next paint cycle.
160
162
  }
161
- if (unmountedRef.current) {
162
- cleanup();
163
+ else if (isMissingPaintRecordError(error)) {
164
+ throw new Error(missingPaintRecordMessage);
163
165
  }
164
166
  else {
165
- onInitCleanupRef.current = cleanup;
167
+ throw error;
168
+ }
169
+ }
170
+ if (initImage) {
171
+ initializedRef.current = true;
172
+ const currentOnInit = onInitRef.current;
173
+ if (currentOnInit) {
174
+ const cleanup = await currentOnInit({
175
+ canvas: offscreen,
176
+ element,
177
+ elementImage: initImage,
178
+ pixelDensity: resolvedPixelDensity,
179
+ });
180
+ if (typeof cleanup !== 'function') {
181
+ throw new Error('HtmlInCanvas: when `onInit` is provided, it must return a cleanup function, or a Promise that resolves to one.');
182
+ }
183
+ if (unmountedRef.current) {
184
+ cleanup();
185
+ }
186
+ else {
187
+ onInitCleanupRef.current = cleanup;
188
+ }
166
189
  }
167
190
  }
168
191
  }
169
192
  const handler = (_a = onPaintRef.current) !== null && _a !== void 0 ? _a : defaultOnPaint;
170
- const elImage = placeholderCanvas.captureElementImage(element);
193
+ let elImage;
194
+ try {
195
+ elImage = placeholderCanvas.captureElementImage(element);
196
+ }
197
+ catch (error) {
198
+ // `captureElementImage` throws `InvalidStateError` when the
199
+ // element is outside the viewport (no cached paint record).
200
+ // Skip this paint cycle — the canvas retains its last state.
201
+ if (isMissingPaintRecordError(error) && !isRendering) {
202
+ continueRender(handle);
203
+ return;
204
+ }
205
+ if (isMissingPaintRecordError(error)) {
206
+ throw new Error(missingPaintRecordMessage);
207
+ }
208
+ throw error;
209
+ }
171
210
  await handler({
172
211
  canvas: offscreen,
173
212
  element,
@@ -194,6 +233,7 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, c
194
233
  continueRender,
195
234
  cancelRender,
196
235
  resolvedPixelDensity,
236
+ isRendering,
197
237
  ]);
198
238
  // Transfer control once per layout canvas instance, then listen for paint on
199
239
  // the placeholder (capture) while drawing on the linked offscreen surface.
@@ -22,13 +22,17 @@ const shared_audio_tags_js_1 = require("./shared-audio-tags.js");
22
22
  const AudioRefForwardingFunction = (props, ref) => {
23
23
  var _a, _b, _c;
24
24
  const audioTagsContext = (0, react_1.useContext)(shared_audio_tags_js_1.SharedAudioTagsContext);
25
- const { startFrom, endAt, trimBefore, trimAfter, name, stack, pauseWhenBuffering, showInTimeline, onError: onRemotionError, ...otherProps } = props;
26
- const { loop, ...propsOtherThanLoop } = props;
25
+ const propsWithFreeze = props;
26
+ const { startFrom, endAt, trimBefore, trimAfter, name, stack, pauseWhenBuffering, showInTimeline, onError: onRemotionError, freeze, ...otherProps } = propsWithFreeze;
27
+ const { loop, freeze: _freeze, ...propsOtherThanLoop } = propsWithFreeze;
27
28
  const { fps } = (0, use_video_config_js_1.useVideoConfig)();
28
29
  const environment = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
29
30
  if (environment.isClientSideRendering) {
30
31
  throw new Error('<Html5Audio> is not supported in @remotion/web-renderer. Use <Audio> from @remotion/media instead. See https://remotion.dev/docs/client-side-rendering/limitations');
31
32
  }
33
+ if (typeof freeze !== 'undefined') {
34
+ throw new TypeError('The "freeze" prop is not supported on <Html5Audio />. Use <Sequence freeze={...}> to freeze media playback.');
35
+ }
32
36
  const { durations, setDurations } = (0, react_1.useContext)(duration_state_js_1.DurationsContext);
33
37
  if (typeof props.src !== 'string') {
34
38
  throw new TypeError(`The \`<Html5Audio>\` tag requires a string for \`src\`, but got ${JSON.stringify(props.src)} instead.`);
@@ -1,5 +1,8 @@
1
1
  import type { SpringConfig } from './spring/spring-utils.js';
2
- export type EasingSpringConfig = Partial<Pick<SpringConfig, 'damping' | 'mass' | 'stiffness' | 'overshootClamping'>>;
2
+ export type EasingSpringConfig = Partial<Pick<SpringConfig, 'damping' | 'mass' | 'stiffness' | 'overshootClamping'>> & {
3
+ readonly allowTail?: boolean;
4
+ readonly durationRestThreshold?: number;
5
+ };
3
6
  /**
4
7
  * @description The Easing module implements common easing functions. You can use it with the interpolate() API.
5
8
  * @see [Documentation](https://www.remotion.dev/docs/easing)
@@ -17,7 +20,7 @@ export declare class Easing {
17
20
  static exp(t: number): number;
18
21
  static elastic(bounciness?: number): (t: number) => number;
19
22
  static back(s?: number): (t: number) => number;
20
- static spring(config?: EasingSpringConfig): (t: number) => number;
23
+ static spring({ allowTail, durationRestThreshold, ...config }?: EasingSpringConfig): (t: number) => number;
21
24
  static bounce(t: number): number;
22
25
  static bezier(x1: number, y1: number, x2: number, y2: number): (t: number) => number;
23
26
  static in(easing: (t: number) => number): (t: number) => number;
@@ -52,21 +52,37 @@ class Easing {
52
52
  static back(s = 1.70158) {
53
53
  return (t) => t * t * ((s + 1) * t - s);
54
54
  }
55
- static spring(config = {}) {
56
- return (t) => {
55
+ static spring({ allowTail = false, durationRestThreshold, ...config } = {}) {
56
+ const easing = (t) => {
57
57
  if (t <= 0) {
58
58
  return 0;
59
59
  }
60
- if (t >= 1) {
60
+ if (!allowTail && t >= 1) {
61
61
  return 1;
62
62
  }
63
+ if (allowTail) {
64
+ return (0, index_js_1.spring)({
65
+ fps: springEasingDurationInFrames,
66
+ frame: t *
67
+ (0, index_js_1.measureSpring)({
68
+ fps: springEasingDurationInFrames,
69
+ config,
70
+ threshold: durationRestThreshold,
71
+ }),
72
+ config,
73
+ });
74
+ }
63
75
  return (0, index_js_1.spring)({
64
76
  fps: springEasingDurationInFrames,
65
77
  frame: t * springEasingDurationInFrames,
66
78
  config,
67
79
  durationInFrames: springEasingDurationInFrames,
80
+ durationRestThreshold,
68
81
  });
69
82
  };
83
+ return Object.assign(easing, {
84
+ remotionShouldExtendRight: allowTail,
85
+ });
70
86
  }
71
87
  static bounce(t) {
72
88
  const u = clampUnit(t);
@@ -51,6 +51,7 @@ const resolvePropStatusOverrides = (propStatus, frame) => {
51
51
  }
52
52
  if (status.status === 'keyframed') {
53
53
  const value = (0, interpolate_keyframed_status_js_1.interpolateKeyframedStatus)({
54
+ forceSpringAllowTail: null,
54
55
  frame,
55
56
  status,
56
57
  });
@@ -13,6 +13,7 @@ const resolveDragOverrideValue = ({ dragOverrideValue, frame, }) => {
13
13
  return { type: 'none' };
14
14
  }
15
15
  const interpolated = (0, interpolate_keyframed_status_1.interpolateKeyframedStatus)({
16
+ forceSpringAllowTail: null,
16
17
  frame,
17
18
  status: dragOverrideValue.status,
18
19
  });
@@ -33,6 +34,7 @@ const getEffectiveVisualModeValue = ({ propStatus, dragOverrideValue, defaultVal
33
34
  if (propStatus.status === 'keyframed') {
34
35
  if (frame !== null) {
35
36
  return (0, interpolate_keyframed_status_1.interpolateKeyframedStatus)({
37
+ forceSpringAllowTail: null,
36
38
  frame,
37
39
  status: propStatus,
38
40
  });
@@ -62,7 +62,16 @@ export type UvCoordinateFieldSchema = {
62
62
  min?: number;
63
63
  max?: number;
64
64
  step?: number;
65
- lineTo?: string;
65
+ visual?: {
66
+ readonly type: 'line';
67
+ readonly to: string;
68
+ } | {
69
+ readonly type: 'ellipse';
70
+ readonly width: string;
71
+ readonly height: string;
72
+ readonly rotation?: string;
73
+ readonly innerScale?: string;
74
+ };
66
75
  default: readonly [number, number] | undefined;
67
76
  description?: string;
68
77
  keyframable?: boolean;
@@ -859,8 +859,9 @@ export declare const Internals: {
859
859
  merged: Record<string, unknown>;
860
860
  propsToDelete: Set<string>;
861
861
  };
862
- readonly interpolateKeyframedStatus: ({ frame, status, }: {
862
+ readonly interpolateKeyframedStatus: ({ frame, forceSpringAllowTail, status, }: {
863
863
  frame: number;
864
+ forceSpringAllowTail: boolean | null;
864
865
  status: CanUpdateSequencePropStatusKeyframed;
865
866
  }) => string | number | readonly number[] | null;
866
867
  readonly makeStaticDragOverride: (value: unknown) => DragOverrideValue;
@@ -1,7 +1,8 @@
1
1
  import type { CanUpdateSequencePropStatusKeyframed } from './use-schema.js';
2
2
  type InterpolateKeyframedStatusResult = number | string | readonly number[] | null;
3
- export declare const interpolateKeyframedStatus: ({ frame, status, }: {
3
+ export declare const interpolateKeyframedStatus: ({ frame, forceSpringAllowTail, status, }: {
4
4
  frame: number;
5
+ forceSpringAllowTail: boolean | null;
5
6
  status: CanUpdateSequencePropStatusKeyframed;
6
7
  }) => InterpolateKeyframedStatusResult;
7
8
  export {};
@@ -5,24 +5,27 @@ const bezier_js_1 = require("./bezier.js");
5
5
  const easing_js_1 = require("./easing.js");
6
6
  const interpolate_colors_js_1 = require("./interpolate-colors.js");
7
7
  const interpolate_js_1 = require("./interpolate.js");
8
- const easingToFn = (e) => {
9
- switch (e.type) {
8
+ const easingToFn = ({ easing, forceSpringAllowTail, }) => {
9
+ var _a, _b;
10
+ switch (easing.type) {
10
11
  case 'linear':
11
12
  return easing_js_1.Easing.linear;
12
13
  case 'spring':
13
14
  return easing_js_1.Easing.spring({
14
- damping: e.damping,
15
- mass: e.mass,
16
- overshootClamping: e.overshootClamping,
17
- stiffness: e.stiffness,
15
+ allowTail: (_a = forceSpringAllowTail !== null && forceSpringAllowTail !== void 0 ? forceSpringAllowTail : easing.allowTail) !== null && _a !== void 0 ? _a : undefined,
16
+ damping: easing.damping,
17
+ durationRestThreshold: (_b = easing.durationRestThreshold) !== null && _b !== void 0 ? _b : undefined,
18
+ mass: easing.mass,
19
+ overshootClamping: easing.overshootClamping,
20
+ stiffness: easing.stiffness,
18
21
  });
19
22
  case 'bezier':
20
- return (0, bezier_js_1.bezier)(e.x1, e.y1, e.x2, e.y2);
23
+ return (0, bezier_js_1.bezier)(easing.x1, easing.y1, easing.x2, easing.y2);
21
24
  default:
22
- throw new TypeError(`Unsupported easing: ${JSON.stringify(e)}`);
25
+ throw new TypeError(`Unsupported easing: ${JSON.stringify(easing)}`);
23
26
  }
24
27
  };
25
- const interpolateKeyframedStatus = ({ frame, status, }) => {
28
+ const interpolateKeyframedStatus = ({ frame, forceSpringAllowTail, status, }) => {
26
29
  const { keyframes, easing, clamping, interpolationFunction } = status;
27
30
  if (keyframes.length === 0) {
28
31
  return null;
@@ -39,7 +42,7 @@ const interpolateKeyframedStatus = ({ frame, status, }) => {
39
42
  }
40
43
  try {
41
44
  return (0, interpolate_colors_js_1.interpolateColors)(frame, inputRange, outputs, {
42
- easing: easing.map(easingToFn),
45
+ easing: easing.map((e) => easingToFn({ easing: e, forceSpringAllowTail })),
43
46
  posterize: status.posterize,
44
47
  });
45
48
  }
@@ -52,7 +55,7 @@ const interpolateKeyframedStatus = ({ frame, status, }) => {
52
55
  }
53
56
  try {
54
57
  return (0, interpolate_js_1.interpolate)(frame, inputRange, outputs, {
55
- easing: easing.map(easingToFn),
58
+ easing: easing.map((e) => easingToFn({ easing: e, forceSpringAllowTail })),
56
59
  extrapolateLeft: clamping.left,
57
60
  extrapolateRight: clamping.right,
58
61
  posterize: status.posterize,
@@ -3,7 +3,9 @@ export type ExtrapolateType = 'extend' | 'identity' | 'clamp' | 'wrap';
3
3
  * @description This function allows you to map a range of values to another with a concise syntax
4
4
  * @see [Documentation](https://www.remotion.dev/docs/interpolate)
5
5
  */
6
- export type EasingFunction = (input: number) => number;
6
+ export type EasingFunction = ((input: number) => number) & {
7
+ readonly remotionShouldExtendRight?: boolean;
8
+ };
7
9
  export type InterpolateOptions = Partial<{
8
10
  easing: EasingFunction | readonly EasingFunction[];
9
11
  extrapolateLeft: ExtrapolateType;
@@ -325,21 +325,35 @@ function findRange(input, inputRange) {
325
325
  return i - 1;
326
326
  }
327
327
  const defaultEasing = (num) => num;
328
+ const shouldExtendRightForEasing = (easing) => {
329
+ return easing.remotionShouldExtendRight === true;
330
+ };
331
+ const resolveEasingForSegment = ({ easing, segmentIndex, }) => {
332
+ if (easing === undefined) {
333
+ return defaultEasing;
334
+ }
335
+ if (typeof easing === 'function') {
336
+ return easing;
337
+ }
338
+ // `segmentIndex` is in [0, inputRange.length - 2]; array length was validated above.
339
+ return easing[segmentIndex];
340
+ };
341
+ const interpolateSegment = ({ input, inputRange, outputRange, easing, extrapolateLeft, extrapolateRight, }) => {
342
+ return interpolateFunction(input, inputRange, outputRange, {
343
+ easing,
344
+ extrapolateLeft,
345
+ extrapolateRight: input > inputRange[1] &&
346
+ extrapolateRight === 'clamp' &&
347
+ shouldExtendRightForEasing(easing)
348
+ ? 'extend'
349
+ : extrapolateRight,
350
+ });
351
+ };
328
352
  const interpolateNumber = ({ input, inputRange, outputRange, options, }) => {
329
353
  if (inputRange.length === 1) {
330
354
  return outputRange[0];
331
355
  }
332
356
  const easingOption = options === null || options === void 0 ? void 0 : options.easing;
333
- const resolveEasingForSegment = (segmentIndex) => {
334
- if (easingOption === undefined) {
335
- return defaultEasing;
336
- }
337
- if (typeof easingOption === 'function') {
338
- return easingOption;
339
- }
340
- // `segmentIndex` is in [0, inputRange.length - 2]; array length was validated above.
341
- return easingOption[segmentIndex];
342
- };
343
357
  let extrapolateLeft = 'extend';
344
358
  if ((options === null || options === void 0 ? void 0 : options.extrapolateLeft) !== undefined) {
345
359
  extrapolateLeft = options.extrapolateLeft;
@@ -352,11 +366,41 @@ const interpolateNumber = ({ input, inputRange, outputRange, options, }) => {
352
366
  ? input
353
367
  : Math.floor(input / options.posterize) * options.posterize;
354
368
  const range = findRange(posterizedInput, inputRange);
355
- return interpolateFunction(posterizedInput, [inputRange[range], inputRange[range + 1]], [outputRange[range], outputRange[range + 1]], {
356
- easing: resolveEasingForSegment(range),
369
+ const easing = resolveEasingForSegment({
370
+ easing: easingOption,
371
+ segmentIndex: range,
372
+ });
373
+ let result = interpolateSegment({
374
+ input: posterizedInput,
375
+ inputRange: [inputRange[range], inputRange[range + 1]],
376
+ outputRange: [outputRange[range], outputRange[range + 1]],
377
+ easing,
357
378
  extrapolateLeft,
358
379
  extrapolateRight,
359
380
  });
381
+ for (let segmentIndex = 0; segmentIndex < range; segmentIndex++) {
382
+ const previousEasing = resolveEasingForSegment({
383
+ easing: easingOption,
384
+ segmentIndex,
385
+ });
386
+ if (!shouldExtendRightForEasing(previousEasing)) {
387
+ continue;
388
+ }
389
+ const previousSegmentEnd = inputRange[segmentIndex + 1];
390
+ if (posterizedInput <= previousSegmentEnd) {
391
+ continue;
392
+ }
393
+ const continuedSegmentValue = interpolateSegment({
394
+ input: posterizedInput,
395
+ inputRange: [inputRange[segmentIndex], previousSegmentEnd],
396
+ outputRange: [outputRange[segmentIndex], outputRange[segmentIndex + 1]],
397
+ easing: previousEasing,
398
+ extrapolateLeft,
399
+ extrapolateRight: 'extend',
400
+ });
401
+ result += continuedSegmentValue - outputRange[segmentIndex + 1];
402
+ }
403
+ return result;
360
404
  };
361
405
  const interpolateString = ({ input, inputRange, outputRange, options, }) => {
362
406
  var _a;
@@ -21,10 +21,12 @@ export type CanUpdateSequencePropStatusBezierEasing = {
21
21
  };
22
22
  export type CanUpdateSequencePropStatusSpringEasing = {
23
23
  type: 'spring';
24
+ allowTail: boolean | null;
24
25
  damping: number;
25
26
  mass: number;
26
27
  stiffness: number;
27
28
  overshootClamping: boolean;
29
+ durationRestThreshold: number | null;
28
30
  };
29
31
  export type CanUpdateSequencePropStatusEasing = CanUpdateSequencePropStatusLinearEasing | CanUpdateSequencePropStatusBezierEasing | CanUpdateSequencePropStatusSpringEasing;
30
32
  export declare const DEFAULT_LINEAR_EASING: CanUpdateSequencePropStatusLinearEasing;
@@ -7,6 +7,13 @@ const interpolate_keyframed_status_js_1 = require("./interpolate-keyframed-statu
7
7
  exports.DEFAULT_LINEAR_EASING = {
8
8
  type: 'linear',
9
9
  };
10
+ const getEasingIndexToDuplicate = ({ insertedKeyframeIndex, easingLength, keyframeCount, }) => {
11
+ const isSplittingExistingSegment = insertedKeyframeIndex > 0 && insertedKeyframeIndex < keyframeCount - 1;
12
+ if (!isSplittingExistingSegment || easingLength === 0) {
13
+ return null;
14
+ }
15
+ return Math.min(insertedKeyframeIndex - 1, easingLength - 1);
16
+ };
10
17
  const makeStaticDragOverride = (value) => {
11
18
  return { type: 'static', value };
12
19
  };
@@ -17,6 +24,18 @@ const makeKeyframedDragOverride = ({ status, frame, value, }) => {
17
24
  ? [...status.keyframes, { frame, value }].sort((first, second) => first.frame - second.frame)
18
25
  : status.keyframes.map((keyframe, index) => index === existingIndex ? { frame, value } : keyframe);
19
26
  const easing = [...status.easing];
27
+ if (existingIndex === -1) {
28
+ const insertedKeyframeIndex = keyframes.findIndex((keyframe) => keyframe.frame === frame);
29
+ const easingIndexToDuplicate = getEasingIndexToDuplicate({
30
+ insertedKeyframeIndex,
31
+ easingLength: easing.length,
32
+ keyframeCount: keyframes.length,
33
+ });
34
+ const easingToDuplicate = easingIndexToDuplicate === null
35
+ ? exports.DEFAULT_LINEAR_EASING
36
+ : easing[easingIndexToDuplicate];
37
+ easing.splice(insertedKeyframeIndex, 0, easingToDuplicate);
38
+ }
20
39
  while (easing.length < keyframes.length - 1) {
21
40
  easing.push(exports.DEFAULT_LINEAR_EASING);
22
41
  }
@@ -90,6 +109,7 @@ const computeEffectiveSchemaValuesDotNotation = ({ schema, currentValue, overrid
90
109
  }
91
110
  else if (frame !== null) {
92
111
  const interpolated = (0, interpolate_keyframed_status_js_1.interpolateKeyframedStatus)({
112
+ forceSpringAllowTail: null,
93
113
  frame,
94
114
  status,
95
115
  });
@@ -3,4 +3,4 @@
3
3
  * @see [Documentation](https://remotion.dev/docs/version)
4
4
  * @returns {string} The current version of the remotion package
5
5
  */
6
- export declare const VERSION = "4.0.482";
6
+ export declare const VERSION = "4.0.484";
@@ -7,4 +7,4 @@ exports.VERSION = void 0;
7
7
  * @see [Documentation](https://remotion.dev/docs/version)
8
8
  * @returns {string} The current version of the remotion package
9
9
  */
10
- exports.VERSION = '4.0.482';
10
+ exports.VERSION = '4.0.484';
@@ -1291,7 +1291,7 @@ var addSequenceStackTraces = (component) => {
1291
1291
  };
1292
1292
 
1293
1293
  // src/version.ts
1294
- var VERSION = "4.0.482";
1294
+ var VERSION = "4.0.484";
1295
1295
 
1296
1296
  // src/multiple-versions-warning.ts
1297
1297
  var checkMultipleRemotionVersions = () => {
@@ -2593,6 +2593,35 @@ function findRange(input, inputRange) {
2593
2593
  return i - 1;
2594
2594
  }
2595
2595
  var defaultEasing = (num) => num;
2596
+ var shouldExtendRightForEasing = (easing) => {
2597
+ return easing.remotionShouldExtendRight === true;
2598
+ };
2599
+ var resolveEasingForSegment = ({
2600
+ easing,
2601
+ segmentIndex
2602
+ }) => {
2603
+ if (easing === undefined) {
2604
+ return defaultEasing;
2605
+ }
2606
+ if (typeof easing === "function") {
2607
+ return easing;
2608
+ }
2609
+ return easing[segmentIndex];
2610
+ };
2611
+ var interpolateSegment = ({
2612
+ input,
2613
+ inputRange,
2614
+ outputRange,
2615
+ easing,
2616
+ extrapolateLeft,
2617
+ extrapolateRight
2618
+ }) => {
2619
+ return interpolateFunction(input, inputRange, outputRange, {
2620
+ easing,
2621
+ extrapolateLeft,
2622
+ extrapolateRight: input > inputRange[1] && extrapolateRight === "clamp" && shouldExtendRightForEasing(easing) ? "extend" : extrapolateRight
2623
+ });
2624
+ };
2596
2625
  var interpolateNumber = ({
2597
2626
  input,
2598
2627
  inputRange,
@@ -2603,15 +2632,6 @@ var interpolateNumber = ({
2603
2632
  return outputRange[0];
2604
2633
  }
2605
2634
  const easingOption = options?.easing;
2606
- const resolveEasingForSegment = (segmentIndex) => {
2607
- if (easingOption === undefined) {
2608
- return defaultEasing;
2609
- }
2610
- if (typeof easingOption === "function") {
2611
- return easingOption;
2612
- }
2613
- return easingOption[segmentIndex];
2614
- };
2615
2635
  let extrapolateLeft = "extend";
2616
2636
  if (options?.extrapolateLeft !== undefined) {
2617
2637
  extrapolateLeft = options.extrapolateLeft;
@@ -2622,11 +2642,41 @@ var interpolateNumber = ({
2622
2642
  }
2623
2643
  const posterizedInput = options?.posterize === undefined ? input : Math.floor(input / options.posterize) * options.posterize;
2624
2644
  const range = findRange(posterizedInput, inputRange);
2625
- return interpolateFunction(posterizedInput, [inputRange[range], inputRange[range + 1]], [outputRange[range], outputRange[range + 1]], {
2626
- easing: resolveEasingForSegment(range),
2645
+ const easing = resolveEasingForSegment({
2646
+ easing: easingOption,
2647
+ segmentIndex: range
2648
+ });
2649
+ let result = interpolateSegment({
2650
+ input: posterizedInput,
2651
+ inputRange: [inputRange[range], inputRange[range + 1]],
2652
+ outputRange: [outputRange[range], outputRange[range + 1]],
2653
+ easing,
2627
2654
  extrapolateLeft,
2628
2655
  extrapolateRight
2629
2656
  });
2657
+ for (let segmentIndex = 0;segmentIndex < range; segmentIndex++) {
2658
+ const previousEasing = resolveEasingForSegment({
2659
+ easing: easingOption,
2660
+ segmentIndex
2661
+ });
2662
+ if (!shouldExtendRightForEasing(previousEasing)) {
2663
+ continue;
2664
+ }
2665
+ const previousSegmentEnd = inputRange[segmentIndex + 1];
2666
+ if (posterizedInput <= previousSegmentEnd) {
2667
+ continue;
2668
+ }
2669
+ const continuedSegmentValue = interpolateSegment({
2670
+ input: posterizedInput,
2671
+ inputRange: [inputRange[segmentIndex], previousSegmentEnd],
2672
+ outputRange: [outputRange[segmentIndex], outputRange[segmentIndex + 1]],
2673
+ easing: previousEasing,
2674
+ extrapolateLeft,
2675
+ extrapolateRight: "extend"
2676
+ });
2677
+ result += continuedSegmentValue - outputRange[segmentIndex + 1];
2678
+ }
2679
+ return result;
2630
2680
  };
2631
2681
  var interpolateString = ({
2632
2682
  input,
@@ -3112,21 +3162,40 @@ class Easing {
3112
3162
  static back(s = 1.70158) {
3113
3163
  return (t) => t * t * ((s + 1) * t - s);
3114
3164
  }
3115
- static spring(config = {}) {
3116
- return (t) => {
3165
+ static spring({
3166
+ allowTail = false,
3167
+ durationRestThreshold,
3168
+ ...config
3169
+ } = {}) {
3170
+ const easing = (t) => {
3117
3171
  if (t <= 0) {
3118
3172
  return 0;
3119
3173
  }
3120
- if (t >= 1) {
3174
+ if (!allowTail && t >= 1) {
3121
3175
  return 1;
3122
3176
  }
3177
+ if (allowTail) {
3178
+ return spring({
3179
+ fps: springEasingDurationInFrames,
3180
+ frame: t * measureSpring({
3181
+ fps: springEasingDurationInFrames,
3182
+ config,
3183
+ threshold: durationRestThreshold
3184
+ }),
3185
+ config
3186
+ });
3187
+ }
3123
3188
  return spring({
3124
3189
  fps: springEasingDurationInFrames,
3125
3190
  frame: t * springEasingDurationInFrames,
3126
3191
  config,
3127
- durationInFrames: springEasingDurationInFrames
3192
+ durationInFrames: springEasingDurationInFrames,
3193
+ durationRestThreshold
3128
3194
  });
3129
3195
  };
3196
+ return Object.assign(easing, {
3197
+ remotionShouldExtendRight: allowTail
3198
+ });
3130
3199
  }
3131
3200
  static bounce(t) {
3132
3201
  const u = clampUnit(t);
@@ -3661,25 +3730,31 @@ var interpolateColors = (input, inputRange, outputRange, options) => {
3661
3730
  };
3662
3731
 
3663
3732
  // src/interpolate-keyframed-status.ts
3664
- var easingToFn = (e) => {
3665
- switch (e.type) {
3733
+ var easingToFn = ({
3734
+ easing,
3735
+ forceSpringAllowTail
3736
+ }) => {
3737
+ switch (easing.type) {
3666
3738
  case "linear":
3667
3739
  return Easing.linear;
3668
3740
  case "spring":
3669
3741
  return Easing.spring({
3670
- damping: e.damping,
3671
- mass: e.mass,
3672
- overshootClamping: e.overshootClamping,
3673
- stiffness: e.stiffness
3742
+ allowTail: forceSpringAllowTail ?? easing.allowTail ?? undefined,
3743
+ damping: easing.damping,
3744
+ durationRestThreshold: easing.durationRestThreshold ?? undefined,
3745
+ mass: easing.mass,
3746
+ overshootClamping: easing.overshootClamping,
3747
+ stiffness: easing.stiffness
3674
3748
  });
3675
3749
  case "bezier":
3676
- return bezier(e.x1, e.y1, e.x2, e.y2);
3750
+ return bezier(easing.x1, easing.y1, easing.x2, easing.y2);
3677
3751
  default:
3678
- throw new TypeError(`Unsupported easing: ${JSON.stringify(e)}`);
3752
+ throw new TypeError(`Unsupported easing: ${JSON.stringify(easing)}`);
3679
3753
  }
3680
3754
  };
3681
3755
  var interpolateKeyframedStatus = ({
3682
3756
  frame,
3757
+ forceSpringAllowTail,
3683
3758
  status
3684
3759
  }) => {
3685
3760
  const { keyframes, easing, clamping, interpolationFunction } = status;
@@ -3698,7 +3773,7 @@ var interpolateKeyframedStatus = ({
3698
3773
  }
3699
3774
  try {
3700
3775
  return interpolateColors(frame, inputRange, outputs, {
3701
- easing: easing.map(easingToFn),
3776
+ easing: easing.map((e) => easingToFn({ easing: e, forceSpringAllowTail })),
3702
3777
  posterize: status.posterize
3703
3778
  });
3704
3779
  } catch {
@@ -3710,7 +3785,7 @@ var interpolateKeyframedStatus = ({
3710
3785
  }
3711
3786
  try {
3712
3787
  return interpolate(frame, inputRange, outputs, {
3713
- easing: easing.map(easingToFn),
3788
+ easing: easing.map((e) => easingToFn({ easing: e, forceSpringAllowTail })),
3714
3789
  extrapolateLeft: clamping.left,
3715
3790
  extrapolateRight: clamping.right,
3716
3791
  posterize: status.posterize
@@ -3735,6 +3810,7 @@ var resolveDragOverrideValue = ({
3735
3810
  return { type: "none" };
3736
3811
  }
3737
3812
  const interpolated = interpolateKeyframedStatus({
3813
+ forceSpringAllowTail: null,
3738
3814
  frame,
3739
3815
  status: dragOverrideValue.status
3740
3816
  });
@@ -3760,6 +3836,7 @@ var getEffectiveVisualModeValue = ({
3760
3836
  if (propStatus.status === "keyframed") {
3761
3837
  if (frame !== null) {
3762
3838
  return interpolateKeyframedStatus({
3839
+ forceSpringAllowTail: null,
3763
3840
  frame,
3764
3841
  status: propStatus
3765
3842
  });
@@ -3833,6 +3910,7 @@ var resolvePropStatusOverrides = (propStatus, frame) => {
3833
3910
  }
3834
3911
  if (status.status === "keyframed") {
3835
3912
  const value = interpolateKeyframedStatus({
3913
+ forceSpringAllowTail: null,
3836
3914
  frame,
3837
3915
  status
3838
3916
  });
@@ -4014,6 +4092,17 @@ var findPropsToDelete = ({
4014
4092
  var DEFAULT_LINEAR_EASING = {
4015
4093
  type: "linear"
4016
4094
  };
4095
+ var getEasingIndexToDuplicate = ({
4096
+ insertedKeyframeIndex,
4097
+ easingLength,
4098
+ keyframeCount
4099
+ }) => {
4100
+ const isSplittingExistingSegment = insertedKeyframeIndex > 0 && insertedKeyframeIndex < keyframeCount - 1;
4101
+ if (!isSplittingExistingSegment || easingLength === 0) {
4102
+ return null;
4103
+ }
4104
+ return Math.min(insertedKeyframeIndex - 1, easingLength - 1);
4105
+ };
4017
4106
  var makeStaticDragOverride = (value) => {
4018
4107
  return { type: "static", value };
4019
4108
  };
@@ -4025,6 +4114,16 @@ var makeKeyframedDragOverride = ({
4025
4114
  const existingIndex = status.keyframes.findIndex((keyframe) => keyframe.frame === frame);
4026
4115
  const keyframes = existingIndex === -1 ? [...status.keyframes, { frame, value }].sort((first, second) => first.frame - second.frame) : status.keyframes.map((keyframe, index) => index === existingIndex ? { frame, value } : keyframe);
4027
4116
  const easing = [...status.easing];
4117
+ if (existingIndex === -1) {
4118
+ const insertedKeyframeIndex = keyframes.findIndex((keyframe) => keyframe.frame === frame);
4119
+ const easingIndexToDuplicate = getEasingIndexToDuplicate({
4120
+ insertedKeyframeIndex,
4121
+ easingLength: easing.length,
4122
+ keyframeCount: keyframes.length
4123
+ });
4124
+ const easingToDuplicate = easingIndexToDuplicate === null ? DEFAULT_LINEAR_EASING : easing[easingIndexToDuplicate];
4125
+ easing.splice(insertedKeyframeIndex, 0, easingToDuplicate);
4126
+ }
4028
4127
  while (easing.length < keyframes.length - 1) {
4029
4128
  easing.push(DEFAULT_LINEAR_EASING);
4030
4129
  }
@@ -4096,6 +4195,7 @@ var computeEffectiveSchemaValuesDotNotation = ({
4096
4195
  value = dragOverride.value;
4097
4196
  } else if (frame !== null) {
4098
4197
  const interpolated = interpolateKeyframedStatus({
4198
+ forceSpringAllowTail: null,
4099
4199
  frame,
4100
4200
  status
4101
4201
  });
@@ -8476,6 +8576,7 @@ var AudioForRendering = forwardRef6(AudioForRenderingRefForwardingFunction);
8476
8576
  import { jsx as jsx23 } from "react/jsx-runtime";
8477
8577
  var AudioRefForwardingFunction = (props, ref) => {
8478
8578
  const audioTagsContext = useContext30(SharedAudioTagsContext);
8579
+ const propsWithFreeze = props;
8479
8580
  const {
8480
8581
  startFrom,
8481
8582
  endAt,
@@ -8486,14 +8587,18 @@ var AudioRefForwardingFunction = (props, ref) => {
8486
8587
  pauseWhenBuffering,
8487
8588
  showInTimeline,
8488
8589
  onError: onRemotionError,
8590
+ freeze,
8489
8591
  ...otherProps
8490
- } = props;
8491
- const { loop, ...propsOtherThanLoop } = props;
8592
+ } = propsWithFreeze;
8593
+ const { loop, freeze: _freeze, ...propsOtherThanLoop } = propsWithFreeze;
8492
8594
  const { fps } = useVideoConfig();
8493
8595
  const environment = useRemotionEnvironment();
8494
8596
  if (environment.isClientSideRendering) {
8495
8597
  throw new Error("<Html5Audio> is not supported in @remotion/web-renderer. Use <Audio> from @remotion/media instead. See https://remotion.dev/docs/client-side-rendering/limitations");
8496
8598
  }
8599
+ if (typeof freeze !== "undefined") {
8600
+ throw new TypeError('The "freeze" prop is not supported on <Html5Audio />. Use <Sequence freeze={...}> to freeze media playback.');
8601
+ }
8497
8602
  const { durations, setDurations } = useContext30(DurationsContext);
8498
8603
  if (typeof props.src !== "string") {
8499
8604
  throw new TypeError(`The \`<Html5Audio>\` tag requires a string for \`src\`, but got ${JSON.stringify(props.src)} instead.`);
@@ -8854,6 +8959,10 @@ function resolveHtmlInCanvasPixelDensity(pixelDensity) {
8854
8959
  }
8855
8960
  return pixelDensity;
8856
8961
  }
8962
+ var isMissingPaintRecordError = (error2) => {
8963
+ return error2 instanceof DOMException && error2.name === "InvalidStateError";
8964
+ };
8965
+ var missingPaintRecordMessage = "HtmlInCanvas: Expected the element to be inside the viewport during rendering, but Chrome had no cached paint record for it.";
8857
8966
  var resizeOffscreenCanvas = ({
8858
8967
  offscreen,
8859
8968
  width,
@@ -8897,6 +9006,7 @@ var HtmlInCanvasContent = forwardRef9(({
8897
9006
  const canvasWidth = Math.ceil(width * resolvedPixelDensity);
8898
9007
  const canvasHeight = Math.ceil(height * resolvedPixelDensity);
8899
9008
  const { continueRender: continueRender2, cancelRender: cancelRender2 } = useDelayRender();
9009
+ const { isRendering } = useRemotionEnvironment();
8900
9010
  if (!isHtmlInCanvasSupported()) {
8901
9011
  cancelRender2(new Error(HTML_IN_CANVAS_UNSUPPORTED_MESSAGE));
8902
9012
  }
@@ -8947,28 +9057,51 @@ var HtmlInCanvasContent = forwardRef9(({
8947
9057
  }
8948
9058
  const handle = delayRender("onPaint");
8949
9059
  if (!initializedRef.current) {
8950
- initializedRef.current = true;
8951
- const initImage = placeholderCanvas.captureElementImage(element);
8952
- const currentOnInit = onInitRef.current;
8953
- if (currentOnInit) {
8954
- const cleanup = await currentOnInit({
8955
- canvas: offscreen,
8956
- element,
8957
- elementImage: initImage,
8958
- pixelDensity: resolvedPixelDensity
8959
- });
8960
- if (typeof cleanup !== "function") {
8961
- throw new Error("HtmlInCanvas: when `onInit` is provided, it must return a cleanup function, or a Promise that resolves to one.");
8962
- }
8963
- if (unmountedRef.current) {
8964
- cleanup();
9060
+ let initImage = null;
9061
+ try {
9062
+ initImage = placeholderCanvas.captureElementImage(element);
9063
+ } catch (error2) {
9064
+ if (isMissingPaintRecordError(error2) && !isRendering) {} else if (isMissingPaintRecordError(error2)) {
9065
+ throw new Error(missingPaintRecordMessage);
8965
9066
  } else {
8966
- onInitCleanupRef.current = cleanup;
9067
+ throw error2;
9068
+ }
9069
+ }
9070
+ if (initImage) {
9071
+ initializedRef.current = true;
9072
+ const currentOnInit = onInitRef.current;
9073
+ if (currentOnInit) {
9074
+ const cleanup = await currentOnInit({
9075
+ canvas: offscreen,
9076
+ element,
9077
+ elementImage: initImage,
9078
+ pixelDensity: resolvedPixelDensity
9079
+ });
9080
+ if (typeof cleanup !== "function") {
9081
+ throw new Error("HtmlInCanvas: when `onInit` is provided, it must return a cleanup function, or a Promise that resolves to one.");
9082
+ }
9083
+ if (unmountedRef.current) {
9084
+ cleanup();
9085
+ } else {
9086
+ onInitCleanupRef.current = cleanup;
9087
+ }
8967
9088
  }
8968
9089
  }
8969
9090
  }
8970
9091
  const handler = onPaintRef.current ?? defaultOnPaint;
8971
- const elImage = placeholderCanvas.captureElementImage(element);
9092
+ let elImage;
9093
+ try {
9094
+ elImage = placeholderCanvas.captureElementImage(element);
9095
+ } catch (error2) {
9096
+ if (isMissingPaintRecordError(error2) && !isRendering) {
9097
+ continueRender2(handle);
9098
+ return;
9099
+ }
9100
+ if (isMissingPaintRecordError(error2)) {
9101
+ throw new Error(missingPaintRecordMessage);
9102
+ }
9103
+ throw error2;
9104
+ }
8972
9105
  await handler({
8973
9106
  canvas: offscreen,
8974
9107
  element,
@@ -8993,7 +9126,8 @@ var HtmlInCanvasContent = forwardRef9(({
8993
9126
  chainState,
8994
9127
  continueRender2,
8995
9128
  cancelRender2,
8996
- resolvedPixelDensity
9129
+ resolvedPixelDensity,
9130
+ isRendering
8997
9131
  ]);
8998
9132
  useLayoutEffect9(() => {
8999
9133
  const placeholder = canvas2dRef.current;
@@ -296,6 +296,35 @@ function findRange(input, inputRange) {
296
296
  return i - 1;
297
297
  }
298
298
  var defaultEasing = (num) => num;
299
+ var shouldExtendRightForEasing = (easing) => {
300
+ return easing.remotionShouldExtendRight === true;
301
+ };
302
+ var resolveEasingForSegment = ({
303
+ easing,
304
+ segmentIndex
305
+ }) => {
306
+ if (easing === undefined) {
307
+ return defaultEasing;
308
+ }
309
+ if (typeof easing === "function") {
310
+ return easing;
311
+ }
312
+ return easing[segmentIndex];
313
+ };
314
+ var interpolateSegment = ({
315
+ input,
316
+ inputRange,
317
+ outputRange,
318
+ easing,
319
+ extrapolateLeft,
320
+ extrapolateRight
321
+ }) => {
322
+ return interpolateFunction(input, inputRange, outputRange, {
323
+ easing,
324
+ extrapolateLeft,
325
+ extrapolateRight: input > inputRange[1] && extrapolateRight === "clamp" && shouldExtendRightForEasing(easing) ? "extend" : extrapolateRight
326
+ });
327
+ };
299
328
  var interpolateNumber = ({
300
329
  input,
301
330
  inputRange,
@@ -306,15 +335,6 @@ var interpolateNumber = ({
306
335
  return outputRange[0];
307
336
  }
308
337
  const easingOption = options?.easing;
309
- const resolveEasingForSegment = (segmentIndex) => {
310
- if (easingOption === undefined) {
311
- return defaultEasing;
312
- }
313
- if (typeof easingOption === "function") {
314
- return easingOption;
315
- }
316
- return easingOption[segmentIndex];
317
- };
318
338
  let extrapolateLeft = "extend";
319
339
  if (options?.extrapolateLeft !== undefined) {
320
340
  extrapolateLeft = options.extrapolateLeft;
@@ -325,11 +345,41 @@ var interpolateNumber = ({
325
345
  }
326
346
  const posterizedInput = options?.posterize === undefined ? input : Math.floor(input / options.posterize) * options.posterize;
327
347
  const range = findRange(posterizedInput, inputRange);
328
- return interpolateFunction(posterizedInput, [inputRange[range], inputRange[range + 1]], [outputRange[range], outputRange[range + 1]], {
329
- easing: resolveEasingForSegment(range),
348
+ const easing = resolveEasingForSegment({
349
+ easing: easingOption,
350
+ segmentIndex: range
351
+ });
352
+ let result = interpolateSegment({
353
+ input: posterizedInput,
354
+ inputRange: [inputRange[range], inputRange[range + 1]],
355
+ outputRange: [outputRange[range], outputRange[range + 1]],
356
+ easing,
330
357
  extrapolateLeft,
331
358
  extrapolateRight
332
359
  });
360
+ for (let segmentIndex = 0;segmentIndex < range; segmentIndex++) {
361
+ const previousEasing = resolveEasingForSegment({
362
+ easing: easingOption,
363
+ segmentIndex
364
+ });
365
+ if (!shouldExtendRightForEasing(previousEasing)) {
366
+ continue;
367
+ }
368
+ const previousSegmentEnd = inputRange[segmentIndex + 1];
369
+ if (posterizedInput <= previousSegmentEnd) {
370
+ continue;
371
+ }
372
+ const continuedSegmentValue = interpolateSegment({
373
+ input: posterizedInput,
374
+ inputRange: [inputRange[segmentIndex], previousSegmentEnd],
375
+ outputRange: [outputRange[segmentIndex], outputRange[segmentIndex + 1]],
376
+ easing: previousEasing,
377
+ extrapolateLeft,
378
+ extrapolateRight: "extend"
379
+ });
380
+ result += continuedSegmentValue - outputRange[segmentIndex + 1];
381
+ }
382
+ return result;
333
383
  };
334
384
  var interpolateString = ({
335
385
  input,
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "4.0.482";
2
+ var VERSION = "4.0.484";
3
3
  export {
4
4
  VERSION
5
5
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/core"
4
4
  },
5
5
  "name": "remotion",
6
- "version": "4.0.482",
6
+ "version": "4.0.484",
7
7
  "description": "Make videos programmatically",
8
8
  "main": "dist/cjs/index.js",
9
9
  "types": "dist/cjs/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  "react-dom": "19.2.3",
36
36
  "webpack": "5.105.0",
37
37
  "zod": "4.3.6",
38
- "@remotion/eslint-config-internal": "4.0.482",
38
+ "@remotion/eslint-config-internal": "4.0.484",
39
39
  "eslint": "9.19.0",
40
40
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
41
41
  },