remotion 4.0.0-alpha5 → 4.0.0-alpha6

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.
@@ -36,7 +36,14 @@ const AudioForDevelopmentForwardRefFunction = (props, ref) => {
36
36
  const sequenceContext = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
37
37
  // Generate a string that's as unique as possible for this asset
38
38
  // but at the same time deterministic. We use it to combat strict mode issues.
39
- const id = (0, react_1.useMemo)(() => `audio-${(0, random_js_1.random)(src !== null && src !== void 0 ? src : '')}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.durationInFrames}-muted:${props.muted}`, [props.muted, src, sequenceContext]);
39
+ const id = (0, react_1.useMemo)(() => `audio-${(0, random_js_1.random)(src !== null && src !== void 0 ? src : '')}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.durationInFrames}-muted:${props.muted}-loop:${props.loop}`, [
40
+ src,
41
+ sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom,
42
+ sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedFrom,
43
+ sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.durationInFrames,
44
+ props.muted,
45
+ props.loop,
46
+ ]);
40
47
  const audioRef = (0, shared_audio_tags_js_1.useSharedAudio)(propsToPass, id).el;
41
48
  const actualVolume = (0, use_media_tag_volume_js_1.useMediaTagVolume)(audioRef);
42
49
  (0, use_sync_volume_with_media_tag_js_1.useSyncVolumeWithMediaTag)({
@@ -74,12 +81,12 @@ const AudioForDevelopmentForwardRefFunction = (props, ref) => {
74
81
  return;
75
82
  }
76
83
  if (current.duration) {
77
- (_a = currentOnDurationCallback.current) === null || _a === void 0 ? void 0 : _a.call(currentOnDurationCallback, src, current.duration);
84
+ (_a = currentOnDurationCallback.current) === null || _a === void 0 ? void 0 : _a.call(currentOnDurationCallback, current.src, current.duration);
78
85
  return;
79
86
  }
80
87
  const onLoadedMetadata = () => {
81
88
  var _a;
82
- (_a = currentOnDurationCallback.current) === null || _a === void 0 ? void 0 : _a.call(currentOnDurationCallback, src, current.duration);
89
+ (_a = currentOnDurationCallback.current) === null || _a === void 0 ? void 0 : _a.call(currentOnDurationCallback, current.src, current.duration);
83
90
  };
84
91
  current.addEventListener('loadedmetadata', onLoadedMetadata);
85
92
  return () => {
@@ -4,6 +4,7 @@ import type { SpringConfig } from './spring-utils.js';
4
4
  * @see [Documentation](https://www.remotion.dev/docs/spring)
5
5
  * @param {number} frame The current time value. Most of the time you want to pass in the return value of useCurrentFrame.
6
6
  * @param {number} fps The framerate at which the animation runs. Pass in the value obtained by `useVideoConfig()`.
7
+ * @param {?boolean} reverse Whether the animation plays in reverse or not. Default `false`.
7
8
  * @param {?Object} config optional object that allows you to customize the physical properties of the animation.
8
9
  * @param {number} [config.mass=1] The weight of the spring. If you reduce the mass, the animation becomes faster!
9
10
  * @param {number} [config.damping=10] How hard the animation decelerates.
@@ -13,8 +14,9 @@ import type { SpringConfig } from './spring-utils.js';
13
14
  * @param {?number} [config.to] The end value of the animation. Default `1`
14
15
  * @param {?number} [config.durationInFrames] Stretch the duration of an animation to a set value.. Default `undefined`
15
16
  * @param {?number} [config.durationThreshold] How close to the end the animation is considered to be done. Default `0.005`
17
+ * @param {?number} [config.delay] Delay the animation for this amount of frames. Default `0`
16
18
  */
17
- export declare function spring({ frame, fps, config, from, to, durationInFrames, durationRestThreshold, }: {
19
+ export declare function spring({ frame: passedFrame, fps, config, from, to, durationInFrames: passedDurationInFrames, durationRestThreshold, delay, reverse, }: {
18
20
  frame: number;
19
21
  fps: number;
20
22
  config?: Partial<SpringConfig>;
@@ -22,6 +24,8 @@ export declare function spring({ frame, fps, config, from, to, durationInFrames,
22
24
  to?: number;
23
25
  durationInFrames?: number;
24
26
  durationRestThreshold?: number;
27
+ delay?: number;
28
+ reverse?: boolean;
25
29
  }): number;
26
30
  export { measureSpring } from './measure-spring.js';
27
31
  export { SpringConfig } from './spring-utils.js';
@@ -11,6 +11,7 @@ const spring_utils_js_1 = require("./spring-utils.js");
11
11
  * @see [Documentation](https://www.remotion.dev/docs/spring)
12
12
  * @param {number} frame The current time value. Most of the time you want to pass in the return value of useCurrentFrame.
13
13
  * @param {number} fps The framerate at which the animation runs. Pass in the value obtained by `useVideoConfig()`.
14
+ * @param {?boolean} reverse Whether the animation plays in reverse or not. Default `false`.
14
15
  * @param {?Object} config optional object that allows you to customize the physical properties of the animation.
15
16
  * @param {number} [config.mass=1] The weight of the spring. If you reduce the mass, the animation becomes faster!
16
17
  * @param {number} [config.damping=10] How hard the animation decelerates.
@@ -20,24 +21,44 @@ const spring_utils_js_1 = require("./spring-utils.js");
20
21
  * @param {?number} [config.to] The end value of the animation. Default `1`
21
22
  * @param {?number} [config.durationInFrames] Stretch the duration of an animation to a set value.. Default `undefined`
22
23
  * @param {?number} [config.durationThreshold] How close to the end the animation is considered to be done. Default `0.005`
24
+ * @param {?number} [config.delay] Delay the animation for this amount of frames. Default `0`
23
25
  */
24
- function spring({ frame, fps, config = {}, from = 0, to = 1, durationInFrames, durationRestThreshold, }) {
25
- (0, validation_spring_duration_js_1.validateSpringDuration)(durationInFrames);
26
- (0, validate_frame_js_1.validateFrame)({ frame, durationInFrames: Infinity, allowFloats: true });
26
+ function spring({ frame: passedFrame, fps, config = {}, from = 0, to = 1, durationInFrames: passedDurationInFrames, durationRestThreshold, delay = 0, reverse = false, }) {
27
+ (0, validation_spring_duration_js_1.validateSpringDuration)(passedDurationInFrames);
28
+ (0, validate_frame_js_1.validateFrame)({
29
+ frame: passedFrame,
30
+ durationInFrames: Infinity,
31
+ allowFloats: true,
32
+ });
27
33
  (0, validate_fps_js_1.validateFps)(fps, 'to spring()', false);
28
- const durationRatio = durationInFrames === undefined
29
- ? 1
30
- : durationInFrames /
31
- (0, measure_spring_js_1.measureSpring)({
32
- fps,
33
- config,
34
- from,
35
- to,
36
- threshold: durationRestThreshold,
37
- });
34
+ const needsToCalculateNaturalDuration = reverse || typeof passedDurationInFrames !== 'undefined';
35
+ const naturalDuration = needsToCalculateNaturalDuration
36
+ ? (0, measure_spring_js_1.measureSpring)({
37
+ fps,
38
+ config,
39
+ from,
40
+ to,
41
+ threshold: durationRestThreshold,
42
+ })
43
+ : undefined;
44
+ const naturalDurationGetter = needsToCalculateNaturalDuration
45
+ ? {
46
+ get: () => naturalDuration,
47
+ }
48
+ : {
49
+ get: () => {
50
+ throw new Error('did not calculate natural duration, this is an error with Remotion. Please report');
51
+ },
52
+ };
53
+ const frame = reverse
54
+ ? (passedDurationInFrames !== null && passedDurationInFrames !== void 0 ? passedDurationInFrames : naturalDurationGetter.get()) - passedFrame
55
+ : passedFrame;
38
56
  const spr = (0, spring_utils_js_1.springCalculation)({
39
57
  fps,
40
- frame: frame / durationRatio,
58
+ frame: (passedDurationInFrames === undefined
59
+ ? frame
60
+ : frame / (passedDurationInFrames / naturalDurationGetter.get())) -
61
+ delay,
41
62
  config,
42
63
  from,
43
64
  to,
@@ -1,5 +1,33 @@
1
+ declare const problematicCharacters: {
2
+ '%3A': string;
3
+ '%2F': string;
4
+ '%3F': string;
5
+ '%23': string;
6
+ '%5B': string;
7
+ '%5D': string;
8
+ '%40': string;
9
+ '%21': string;
10
+ '%24': string;
11
+ '%26': string;
12
+ '%27': string;
13
+ '%28': string;
14
+ '%29': string;
15
+ '%2A': string;
16
+ '%2B': string;
17
+ '%2C': string;
18
+ '%3B': string;
19
+ };
20
+ declare type HexCode = keyof typeof problematicCharacters;
21
+ export declare type HexInfo = {
22
+ containsHex: false;
23
+ } | {
24
+ containsHex: true;
25
+ hexCode: HexCode;
26
+ };
27
+ export declare const includesHexOfUnsafeChar: (path: string) => HexInfo;
1
28
  /**
2
29
  * @description Reference a file from the public/ folder. If the file does not appear in the autocomplete, type the path manually.
3
30
  * @see [Documentation](https://www.remotion.dev/docs/staticfile)
4
31
  */
5
32
  export declare const staticFile: (path: string) => string;
33
+ export {};
@@ -1,9 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.staticFile = void 0;
3
+ exports.staticFile = exports.includesHexOfUnsafeChar = void 0;
4
+ const problematicCharacters = {
5
+ '%3A': ':',
6
+ '%2F': '/',
7
+ '%3F': '?',
8
+ '%23': '#',
9
+ '%5B': '[',
10
+ '%5D': ']',
11
+ '%40': '@',
12
+ '%21': '!',
13
+ '%24': '$',
14
+ '%26': '&',
15
+ '%27': "'",
16
+ '%28': '(',
17
+ '%29': ')',
18
+ '%2A': '*',
19
+ '%2B': '+',
20
+ '%2C': ',',
21
+ '%3B': ';',
22
+ };
23
+ const didWarn = {};
24
+ const warnOnce = (message) => {
25
+ if (didWarn[message]) {
26
+ return;
27
+ }
28
+ console.warn(message);
29
+ didWarn[message] = true;
30
+ };
31
+ const includesHexOfUnsafeChar = (path) => {
32
+ for (const key of Object.keys(problematicCharacters)) {
33
+ if (path.includes(key)) {
34
+ return { containsHex: true, hexCode: key };
35
+ }
36
+ }
37
+ return { containsHex: false };
38
+ };
39
+ exports.includesHexOfUnsafeChar = includesHexOfUnsafeChar;
4
40
  const trimLeadingSlash = (path) => {
5
41
  if (path.startsWith('/')) {
6
- return trimLeadingSlash(path.substr(1));
42
+ return trimLeadingSlash(path.substring(1));
7
43
  }
8
44
  return path;
9
45
  };
@@ -38,7 +74,11 @@ const staticFile = (path) => {
38
74
  if (path.startsWith('public/')) {
39
75
  throw new TypeError(`Do not include the public/ prefix when using staticFile() - got "${path}". See: https://remotion.dev/docs/staticfile-relative-paths`);
40
76
  }
41
- const preparsed = inner(path);
77
+ const includesHex = (0, exports.includesHexOfUnsafeChar)(path);
78
+ if (includesHex.containsHex) {
79
+ warnOnce(`WARNING: You seem to pass an already encoded path (path contains ${includesHex.hexCode}). The encoding gets automatically handled by staticFile() `);
80
+ }
81
+ const preparsed = inner(encodeURIComponent(path));
42
82
  if (!preparsed.startsWith('/')) {
43
83
  return `/${preparsed}`;
44
84
  }
@@ -51,12 +51,21 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
51
51
  (0, warn_about_non_seekable_media_js_1.warnAboutNonSeekableMedia)(mediaRef.current, onlyWarnForMediaSeekingError ? 'console-warning' : 'console-error');
52
52
  }
53
53
  }
54
+ // Only perform a seek if the time is not already the same.
55
+ // Chrome rounds to 6 digits, so 0.033333333 -> 0.033333,
56
+ // therefore a threshold is allowed.
57
+ // Refer to the https://github.com/remotion-dev/video-buffering-example
58
+ // which is fixed by only seeking conditionally.
59
+ const makesSenseToSeek = Math.abs(mediaRef.current.currentTime - shouldBeTime) > 0.00001;
54
60
  if (!playing || absoluteFrame === 0) {
55
- mediaRef.current.currentTime = shouldBeTime;
61
+ if (makesSenseToSeek) {
62
+ mediaRef.current.currentTime = shouldBeTime;
63
+ }
56
64
  }
57
65
  if (mediaRef.current.paused && !mediaRef.current.ended && playing) {
58
- const { current } = mediaRef;
59
- current.currentTime = shouldBeTime;
66
+ if (makesSenseToSeek) {
67
+ mediaRef.current.currentTime = shouldBeTime;
68
+ }
60
69
  (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)(mediaRef, mediaType);
61
70
  }
62
71
  }, [
@@ -1 +1 @@
1
- export declare const VERSION = "4.0.0-alpha5";
1
+ export declare const VERSION = "4.0.0-alpha6";
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // Automatically generated on publish
5
- exports.VERSION = '4.0.0-alpha5';
5
+ exports.VERSION = '4.0.0-alpha6';
@@ -14,7 +14,7 @@ const VideoForDevelopment_js_1 = require("./VideoForDevelopment.js");
14
14
  * @see [Documentation](https://www.remotion.dev/docs/offthreadvideo)
15
15
  */
16
16
  const OffthreadVideo = (props) => {
17
- const { startFrom, endAt, ...otherProps } = props;
17
+ const { startFrom, endAt, imageFormat, ...otherProps } = props;
18
18
  const environment = (0, get_environment_js_1.useRemotionEnvironment)();
19
19
  const onDuration = (0, react_1.useCallback)(() => undefined, []);
20
20
  if (typeof props.src !== 'string') {
@@ -31,7 +31,7 @@ const OffthreadVideo = (props) => {
31
31
  }
32
32
  (0, validate_media_props_js_1.validateMediaProps)(props, 'Video');
33
33
  if (environment === 'rendering') {
34
- return (0, jsx_runtime_1.jsx)(OffthreadVideoForRendering_js_1.OffthreadVideoForRendering, { ...otherProps });
34
+ return ((0, jsx_runtime_1.jsx)(OffthreadVideoForRendering_js_1.OffthreadVideoForRendering, { imageFormat: imageFormat, ...otherProps }));
35
35
  }
36
36
  return ((0, jsx_runtime_1.jsx)(VideoForDevelopment_js_1.VideoForDevelopment, { onDuration: onDuration, onlyWarnForMediaSeekingError: true, ...otherProps }));
37
37
  };
@@ -75,7 +75,11 @@ const VideoForDevelopmentRefForwardingFunction = (props, ref) => {
75
75
  var _a;
76
76
  if (current === null || current === void 0 ? void 0 : current.error) {
77
77
  console.error('Error occurred in video', current === null || current === void 0 ? void 0 : current.error);
78
- throw new Error(`The browser threw an error while playing the video ${src}: Code ${current.error.code} - ${(_a = current === null || current === void 0 ? void 0 : current.error) === null || _a === void 0 ? void 0 : _a.message}. See https://remotion.dev/docs/media-playback-error for help`);
78
+ // If user is handling the error, we don't cause an unhandled exception
79
+ if (props.onError) {
80
+ return;
81
+ }
82
+ throw new Error(`The browser threw an error while playing the video ${src}: Code ${current.error.code} - ${(_a = current === null || current === void 0 ? void 0 : current.error) === null || _a === void 0 ? void 0 : _a.message}. See https://remotion.dev/docs/media-playback-error for help. Pass an onError() prop to handle the error.`);
79
83
  }
80
84
  else {
81
85
  throw new Error('The browser threw an error');
@@ -85,7 +89,7 @@ const VideoForDevelopmentRefForwardingFunction = (props, ref) => {
85
89
  return () => {
86
90
  current.removeEventListener('error', errorHandler);
87
91
  };
88
- }, [src]);
92
+ }, [props.onError, src]);
89
93
  const currentOnDurationCallback = (0, react_1.useRef)();
90
94
  currentOnDurationCallback.current = onDuration;
91
95
  (0, react_1.useEffect)(() => {
@@ -146,7 +146,11 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
146
146
  var _a;
147
147
  if (current === null || current === void 0 ? void 0 : current.error) {
148
148
  console.error('Error occurred in video', current === null || current === void 0 ? void 0 : current.error);
149
- throw new Error(`The browser threw an error while playing the video ${props.src}: Code ${current.error.code} - ${(_a = current === null || current === void 0 ? void 0 : current.error) === null || _a === void 0 ? void 0 : _a.message}. See https://remotion.dev/docs/media-playback-error for help`);
149
+ // If user is handling the error, we don't cause an unhandled exception
150
+ if (onError) {
151
+ return;
152
+ }
153
+ throw new Error(`The browser threw an error while playing the video ${props.src}: Code ${current.error.code} - ${(_a = current === null || current === void 0 ? void 0 : current.error) === null || _a === void 0 ? void 0 : _a.message}. See https://remotion.dev/docs/media-playback-error for help. Pass an onError() prop to handle the error.`);
150
154
  }
151
155
  else {
152
156
  throw new Error('The browser threw an error');
@@ -167,6 +171,7 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
167
171
  videoConfig.fps,
168
172
  frame,
169
173
  mediaStartsAt,
174
+ onError,
170
175
  ]);
171
176
  const { src } = props;
172
177
  // If video source switches, make new handle
@@ -19,6 +19,7 @@ declare type DeprecatedOffthreadVideoProps = {
19
19
  export declare type OffthreadVideoProps = {
20
20
  src: string;
21
21
  className?: string;
22
+ id?: string;
22
23
  style?: React.CSSProperties;
23
24
  volume?: VolumeProp;
24
25
  playbackRate?: number;
@@ -58,7 +58,7 @@ function truthy(value) {
58
58
  }
59
59
 
60
60
  // Automatically generated on publish
61
- const VERSION = '4.0.0-alpha5';
61
+ const VERSION = '4.0.0-alpha6';
62
62
 
63
63
  const checkMultipleRemotionVersions = () => {
64
64
  if (typeof globalThis === 'undefined') {
@@ -1176,13 +1176,13 @@ const evaluateVolume = ({ frame, volume, mediaVolume = 1, allowAmplificationDuri
1176
1176
  return Math.max(0, Math.min(maxVolume, evaluated));
1177
1177
  };
1178
1178
 
1179
- const didWarn = {};
1180
- const warnOnce = (message) => {
1181
- if (didWarn[message]) {
1179
+ const didWarn$1 = {};
1180
+ const warnOnce$1 = (message) => {
1181
+ if (didWarn$1[message]) {
1182
1182
  return;
1183
1183
  }
1184
1184
  console.warn(message);
1185
- didWarn[message] = true;
1185
+ didWarn$1[message] = true;
1186
1186
  };
1187
1187
  const useMediaInTimeline = ({ volume, mediaVolume, mediaRef, src, mediaType, playbackRate, }) => {
1188
1188
  const videoConfig = useVideoConfig();
@@ -1220,7 +1220,7 @@ const useMediaInTimeline = ({ volume, mediaVolume, mediaRef, src, mediaType, pla
1220
1220
  }, [duration, startsAt, volume, mediaVolume]);
1221
1221
  useEffect(() => {
1222
1222
  if (typeof volume === 'number' && volume !== initialVolume) {
1223
- warnOnce(`Remotion: The ${mediaType} with src ${src} has changed it's volume. Prefer the callback syntax for setting volume to get better timeline display: https://www.remotion.dev/docs/using-audio/#controlling-volume`);
1223
+ warnOnce$1(`Remotion: The ${mediaType} with src ${src} has changed it's volume. Prefer the callback syntax for setting volume to get better timeline display: https://www.remotion.dev/docs/using-audio/#controlling-volume`);
1224
1224
  }
1225
1225
  }, [initialVolume, mediaType, src, volume]);
1226
1226
  useEffect(() => {
@@ -1505,12 +1505,21 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
1505
1505
  warnAboutNonSeekableMedia(mediaRef.current, onlyWarnForMediaSeekingError ? 'console-warning' : 'console-error');
1506
1506
  }
1507
1507
  }
1508
+ // Only perform a seek if the time is not already the same.
1509
+ // Chrome rounds to 6 digits, so 0.033333333 -> 0.033333,
1510
+ // therefore a threshold is allowed.
1511
+ // Refer to the https://github.com/remotion-dev/video-buffering-example
1512
+ // which is fixed by only seeking conditionally.
1513
+ const makesSenseToSeek = Math.abs(mediaRef.current.currentTime - shouldBeTime) > 0.00001;
1508
1514
  if (!playing || absoluteFrame === 0) {
1509
- mediaRef.current.currentTime = shouldBeTime;
1515
+ if (makesSenseToSeek) {
1516
+ mediaRef.current.currentTime = shouldBeTime;
1517
+ }
1510
1518
  }
1511
1519
  if (mediaRef.current.paused && !mediaRef.current.ended && playing) {
1512
- const { current } = mediaRef;
1513
- current.currentTime = shouldBeTime;
1520
+ if (makesSenseToSeek) {
1521
+ mediaRef.current.currentTime = shouldBeTime;
1522
+ }
1514
1523
  playAndHandleNotAllowedError(mediaRef, mediaType);
1515
1524
  }
1516
1525
  }, [
@@ -1626,7 +1635,14 @@ const AudioForDevelopmentForwardRefFunction = (props, ref) => {
1626
1635
  const sequenceContext = useContext(SequenceContext);
1627
1636
  // Generate a string that's as unique as possible for this asset
1628
1637
  // but at the same time deterministic. We use it to combat strict mode issues.
1629
- const id = useMemo(() => `audio-${random(src !== null && src !== void 0 ? src : '')}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.durationInFrames}-muted:${props.muted}`, [props.muted, src, sequenceContext]);
1638
+ const id = useMemo(() => `audio-${random(src !== null && src !== void 0 ? src : '')}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.durationInFrames}-muted:${props.muted}-loop:${props.loop}`, [
1639
+ src,
1640
+ sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom,
1641
+ sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedFrom,
1642
+ sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.durationInFrames,
1643
+ props.muted,
1644
+ props.loop,
1645
+ ]);
1630
1646
  const audioRef = useSharedAudio(propsToPass, id).el;
1631
1647
  const actualVolume = useMediaTagVolume(audioRef);
1632
1648
  useSyncVolumeWithMediaTag({
@@ -1664,12 +1680,12 @@ const AudioForDevelopmentForwardRefFunction = (props, ref) => {
1664
1680
  return;
1665
1681
  }
1666
1682
  if (current.duration) {
1667
- (_a = currentOnDurationCallback.current) === null || _a === void 0 ? void 0 : _a.call(currentOnDurationCallback, src, current.duration);
1683
+ (_a = currentOnDurationCallback.current) === null || _a === void 0 ? void 0 : _a.call(currentOnDurationCallback, current.src, current.duration);
1668
1684
  return;
1669
1685
  }
1670
1686
  const onLoadedMetadata = () => {
1671
1687
  var _a;
1672
- (_a = currentOnDurationCallback.current) === null || _a === void 0 ? void 0 : _a.call(currentOnDurationCallback, src, current.duration);
1688
+ (_a = currentOnDurationCallback.current) === null || _a === void 0 ? void 0 : _a.call(currentOnDurationCallback, current.src, current.duration);
1673
1689
  };
1674
1690
  current.addEventListener('loadedmetadata', onLoadedMetadata);
1675
1691
  return () => {
@@ -3552,6 +3568,7 @@ function measureSpring({ fps, config = {}, threshold = 0.005, from = 0, to = 1,
3552
3568
  * @see [Documentation](https://www.remotion.dev/docs/spring)
3553
3569
  * @param {number} frame The current time value. Most of the time you want to pass in the return value of useCurrentFrame.
3554
3570
  * @param {number} fps The framerate at which the animation runs. Pass in the value obtained by `useVideoConfig()`.
3571
+ * @param {?boolean} reverse Whether the animation plays in reverse or not. Default `false`.
3555
3572
  * @param {?Object} config optional object that allows you to customize the physical properties of the animation.
3556
3573
  * @param {number} [config.mass=1] The weight of the spring. If you reduce the mass, the animation becomes faster!
3557
3574
  * @param {number} [config.damping=10] How hard the animation decelerates.
@@ -3561,24 +3578,44 @@ function measureSpring({ fps, config = {}, threshold = 0.005, from = 0, to = 1,
3561
3578
  * @param {?number} [config.to] The end value of the animation. Default `1`
3562
3579
  * @param {?number} [config.durationInFrames] Stretch the duration of an animation to a set value.. Default `undefined`
3563
3580
  * @param {?number} [config.durationThreshold] How close to the end the animation is considered to be done. Default `0.005`
3581
+ * @param {?number} [config.delay] Delay the animation for this amount of frames. Default `0`
3564
3582
  */
3565
- function spring({ frame, fps, config = {}, from = 0, to = 1, durationInFrames, durationRestThreshold, }) {
3566
- validateSpringDuration(durationInFrames);
3567
- validateFrame({ frame, durationInFrames: Infinity, allowFloats: true });
3583
+ function spring({ frame: passedFrame, fps, config = {}, from = 0, to = 1, durationInFrames: passedDurationInFrames, durationRestThreshold, delay = 0, reverse = false, }) {
3584
+ validateSpringDuration(passedDurationInFrames);
3585
+ validateFrame({
3586
+ frame: passedFrame,
3587
+ durationInFrames: Infinity,
3588
+ allowFloats: true,
3589
+ });
3568
3590
  validateFps(fps, 'to spring()', false);
3569
- const durationRatio = durationInFrames === undefined
3570
- ? 1
3571
- : durationInFrames /
3572
- measureSpring({
3573
- fps,
3574
- config,
3575
- from,
3576
- to,
3577
- threshold: durationRestThreshold,
3578
- });
3591
+ const needsToCalculateNaturalDuration = reverse || typeof passedDurationInFrames !== 'undefined';
3592
+ const naturalDuration = needsToCalculateNaturalDuration
3593
+ ? measureSpring({
3594
+ fps,
3595
+ config,
3596
+ from,
3597
+ to,
3598
+ threshold: durationRestThreshold,
3599
+ })
3600
+ : undefined;
3601
+ const naturalDurationGetter = needsToCalculateNaturalDuration
3602
+ ? {
3603
+ get: () => naturalDuration,
3604
+ }
3605
+ : {
3606
+ get: () => {
3607
+ throw new Error('did not calculate natural duration, this is an error with Remotion. Please report');
3608
+ },
3609
+ };
3610
+ const frame = reverse
3611
+ ? (passedDurationInFrames !== null && passedDurationInFrames !== void 0 ? passedDurationInFrames : naturalDurationGetter.get()) - passedFrame
3612
+ : passedFrame;
3579
3613
  const spr = springCalculation({
3580
3614
  fps,
3581
- frame: frame / durationRatio,
3615
+ frame: (passedDurationInFrames === undefined
3616
+ ? frame
3617
+ : frame / (passedDurationInFrames / naturalDurationGetter.get())) -
3618
+ delay,
3582
3619
  config,
3583
3620
  from,
3584
3621
  to,
@@ -3592,9 +3629,44 @@ function spring({ frame, fps, config = {}, from = 0, to = 1, durationInFrames, d
3592
3629
  return Math.max(spr.current, to);
3593
3630
  }
3594
3631
 
3632
+ const problematicCharacters = {
3633
+ '%3A': ':',
3634
+ '%2F': '/',
3635
+ '%3F': '?',
3636
+ '%23': '#',
3637
+ '%5B': '[',
3638
+ '%5D': ']',
3639
+ '%40': '@',
3640
+ '%21': '!',
3641
+ '%24': '$',
3642
+ '%26': '&',
3643
+ '%27': "'",
3644
+ '%28': '(',
3645
+ '%29': ')',
3646
+ '%2A': '*',
3647
+ '%2B': '+',
3648
+ '%2C': ',',
3649
+ '%3B': ';',
3650
+ };
3651
+ const didWarn = {};
3652
+ const warnOnce = (message) => {
3653
+ if (didWarn[message]) {
3654
+ return;
3655
+ }
3656
+ console.warn(message);
3657
+ didWarn[message] = true;
3658
+ };
3659
+ const includesHexOfUnsafeChar = (path) => {
3660
+ for (const key of Object.keys(problematicCharacters)) {
3661
+ if (path.includes(key)) {
3662
+ return { containsHex: true, hexCode: key };
3663
+ }
3664
+ }
3665
+ return { containsHex: false };
3666
+ };
3595
3667
  const trimLeadingSlash = (path) => {
3596
3668
  if (path.startsWith('/')) {
3597
- return trimLeadingSlash(path.substr(1));
3669
+ return trimLeadingSlash(path.substring(1));
3598
3670
  }
3599
3671
  return path;
3600
3672
  };
@@ -3629,7 +3701,11 @@ const staticFile = (path) => {
3629
3701
  if (path.startsWith('public/')) {
3630
3702
  throw new TypeError(`Do not include the public/ prefix when using staticFile() - got "${path}". See: https://remotion.dev/docs/staticfile-relative-paths`);
3631
3703
  }
3632
- const preparsed = inner(path);
3704
+ const includesHex = includesHexOfUnsafeChar(path);
3705
+ if (includesHex.containsHex) {
3706
+ warnOnce(`WARNING: You seem to pass an already encoded path (path contains ${includesHex.hexCode}). The encoding gets automatically handled by staticFile() `);
3707
+ }
3708
+ const preparsed = inner(encodeURIComponent(path));
3633
3709
  if (!preparsed.startsWith('/')) {
3634
3710
  return `/${preparsed}`;
3635
3711
  }
@@ -3852,7 +3928,11 @@ const VideoForDevelopmentRefForwardingFunction = (props, ref) => {
3852
3928
  var _a;
3853
3929
  if (current === null || current === void 0 ? void 0 : current.error) {
3854
3930
  console.error('Error occurred in video', current === null || current === void 0 ? void 0 : current.error);
3855
- throw new Error(`The browser threw an error while playing the video ${src}: Code ${current.error.code} - ${(_a = current === null || current === void 0 ? void 0 : current.error) === null || _a === void 0 ? void 0 : _a.message}. See https://remotion.dev/docs/media-playback-error for help`);
3931
+ // If user is handling the error, we don't cause an unhandled exception
3932
+ if (props.onError) {
3933
+ return;
3934
+ }
3935
+ throw new Error(`The browser threw an error while playing the video ${src}: Code ${current.error.code} - ${(_a = current === null || current === void 0 ? void 0 : current.error) === null || _a === void 0 ? void 0 : _a.message}. See https://remotion.dev/docs/media-playback-error for help. Pass an onError() prop to handle the error.`);
3856
3936
  }
3857
3937
  else {
3858
3938
  throw new Error('The browser threw an error');
@@ -3862,7 +3942,7 @@ const VideoForDevelopmentRefForwardingFunction = (props, ref) => {
3862
3942
  return () => {
3863
3943
  current.removeEventListener('error', errorHandler);
3864
3944
  };
3865
- }, [src]);
3945
+ }, [props.onError, src]);
3866
3946
  const currentOnDurationCallback = useRef();
3867
3947
  currentOnDurationCallback.current = onDuration;
3868
3948
  useEffect(() => {
@@ -3894,7 +3974,7 @@ const VideoForDevelopment = forwardRef(VideoForDevelopmentRefForwardingFunction)
3894
3974
  * @see [Documentation](https://www.remotion.dev/docs/offthreadvideo)
3895
3975
  */
3896
3976
  const OffthreadVideo = (props) => {
3897
- const { startFrom, endAt, ...otherProps } = props;
3977
+ const { startFrom, endAt, imageFormat, ...otherProps } = props;
3898
3978
  const environment = useRemotionEnvironment();
3899
3979
  const onDuration = useCallback(() => undefined, []);
3900
3980
  if (typeof props.src !== 'string') {
@@ -3911,7 +3991,7 @@ const OffthreadVideo = (props) => {
3911
3991
  }
3912
3992
  validateMediaProps(props, 'Video');
3913
3993
  if (environment === 'rendering') {
3914
- return jsx(OffthreadVideoForRendering, { ...otherProps });
3994
+ return (jsx(OffthreadVideoForRendering, { imageFormat: imageFormat, ...otherProps }));
3915
3995
  }
3916
3996
  return (jsx(VideoForDevelopment, { onDuration: onDuration, onlyWarnForMediaSeekingError: true, ...otherProps }));
3917
3997
  };
@@ -4045,7 +4125,11 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
4045
4125
  var _a;
4046
4126
  if (current === null || current === void 0 ? void 0 : current.error) {
4047
4127
  console.error('Error occurred in video', current === null || current === void 0 ? void 0 : current.error);
4048
- throw new Error(`The browser threw an error while playing the video ${props.src}: Code ${current.error.code} - ${(_a = current === null || current === void 0 ? void 0 : current.error) === null || _a === void 0 ? void 0 : _a.message}. See https://remotion.dev/docs/media-playback-error for help`);
4128
+ // If user is handling the error, we don't cause an unhandled exception
4129
+ if (onError) {
4130
+ return;
4131
+ }
4132
+ throw new Error(`The browser threw an error while playing the video ${props.src}: Code ${current.error.code} - ${(_a = current === null || current === void 0 ? void 0 : current.error) === null || _a === void 0 ? void 0 : _a.message}. See https://remotion.dev/docs/media-playback-error for help. Pass an onError() prop to handle the error.`);
4049
4133
  }
4050
4134
  else {
4051
4135
  throw new Error('The browser threw an error');
@@ -4066,6 +4150,7 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
4066
4150
  videoConfig.fps,
4067
4151
  frame,
4068
4152
  mediaStartsAt,
4153
+ onError,
4069
4154
  ]);
4070
4155
  const { src } = props;
4071
4156
  // If video source switches, make new handle
@@ -1,4 +1,4 @@
1
1
  // Automatically generated on publish
2
- const VERSION = '4.0.0-alpha5';
2
+ const VERSION = '4.0.0-alpha6';
3
3
 
4
4
  export { VERSION };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remotion",
3
- "version": "4.0.0-alpha5",
3
+ "version": "4.0.0-alpha6",
4
4
  "description": "Render videos in React",
5
5
  "main": "dist/cjs/index.js",
6
6
  "types": "dist/cjs/index.d.ts",