remotion 4.0.176 → 4.0.178

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.
@@ -12,6 +12,7 @@ const NativeLayers_js_1 = require("./NativeLayers.js");
12
12
  const ResolveCompositionConfig_js_1 = require("./ResolveCompositionConfig.js");
13
13
  const delay_render_js_1 = require("./delay-render.js");
14
14
  const get_remotion_environment_js_1 = require("./get-remotion-environment.js");
15
+ const input_props_serialization_js_1 = require("./input-props-serialization.js");
15
16
  const is_player_js_1 = require("./is-player.js");
16
17
  const loading_indicator_js_1 = require("./loading-indicator.js");
17
18
  const nonce_js_1 = require("./nonce.js");
@@ -78,7 +79,7 @@ const Composition = ({ width, height, fps, durationInFrames, id, defaultProps, s
78
79
  id,
79
80
  folderName,
80
81
  component: lazy,
81
- defaultProps: defaultProps,
82
+ defaultProps: (0, input_props_serialization_js_1.serializeThenDeserializeInStudio)((defaultProps !== null && defaultProps !== void 0 ? defaultProps : {})),
82
83
  nonce,
83
84
  parentFolderName: parentName,
84
85
  schema: schema !== null && schema !== void 0 ? schema : null,
@@ -56,7 +56,7 @@ const ResolveCompositionConfig = ({ children }) => {
56
56
  return controller;
57
57
  }
58
58
  const { signal } = controller;
59
- const promOrNot = (0, resolve_video_config_js_1.resolveVideoConfig)({
59
+ const result = (0, resolve_video_config_js_1.resolveVideoConfigOrCatch)({
60
60
  compositionId,
61
61
  calculateMetadata,
62
62
  originalProps: combinedProps,
@@ -67,6 +67,17 @@ const ResolveCompositionConfig = ({ children }) => {
67
67
  compositionHeight,
68
68
  compositionWidth,
69
69
  });
70
+ if (result.type === 'error') {
71
+ setResolvedConfigs((r) => ({
72
+ ...r,
73
+ [compositionId]: {
74
+ type: 'error',
75
+ error: result.error,
76
+ },
77
+ }));
78
+ return controller;
79
+ }
80
+ const promOrNot = result.result;
70
81
  if (typeof promOrNot === 'object' && 'then' in promOrNot) {
71
82
  setResolvedConfigs((r) => {
72
83
  const prev = r[compositionId];
@@ -1,7 +1,8 @@
1
- export declare const useBufferUntilFirstFrame: ({ mediaRef, mediaType, }: {
1
+ export declare const useBufferUntilFirstFrame: ({ mediaRef, mediaType, onVariableFpsVideoDetected, }: {
2
2
  mediaRef: React.RefObject<HTMLVideoElement | HTMLAudioElement>;
3
3
  mediaType: 'video' | 'audio';
4
+ onVariableFpsVideoDetected: () => void;
4
5
  }) => {
5
6
  isBuffering: () => boolean;
6
- bufferUntilFirstFrame: () => void;
7
+ bufferUntilFirstFrame: (requestedTime: number) => void;
7
8
  };
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useBufferUntilFirstFrame = void 0;
4
4
  const react_1 = require("react");
5
5
  const use_buffer_state_1 = require("./use-buffer-state");
6
- const useBufferUntilFirstFrame = ({ mediaRef, mediaType, }) => {
6
+ const useBufferUntilFirstFrame = ({ mediaRef, mediaType, onVariableFpsVideoDetected, }) => {
7
7
  const bufferingRef = (0, react_1.useRef)(false);
8
8
  const { delayPlayback } = (0, use_buffer_state_1.useBufferState)();
9
- const bufferUntilFirstFrame = (0, react_1.useCallback)(() => {
9
+ const bufferUntilFirstFrame = (0, react_1.useCallback)((requestedTime) => {
10
10
  if (mediaType !== 'video') {
11
11
  return;
12
12
  }
@@ -34,7 +34,11 @@ const useBufferUntilFirstFrame = ({ mediaRef, mediaType, }) => {
34
34
  const onEndedOrPause = () => {
35
35
  unblock();
36
36
  };
37
- current.requestVideoFrameCallback(() => {
37
+ current.requestVideoFrameCallback((_, info) => {
38
+ const differenceFromRequested = Math.abs(info.mediaTime - requestedTime);
39
+ if (differenceFromRequested > 0.5) {
40
+ onVariableFpsVideoDetected();
41
+ }
38
42
  // Safari often seeks and then stalls.
39
43
  // This makes sure that the video actually starts playing.
40
44
  current.requestVideoFrameCallback(() => {
@@ -43,7 +47,7 @@ const useBufferUntilFirstFrame = ({ mediaRef, mediaType, }) => {
43
47
  });
44
48
  current.addEventListener('ended', onEndedOrPause, { once: true });
45
49
  current.addEventListener('pause', onEndedOrPause, { once: true });
46
- }, [delayPlayback, mediaRef, mediaType]);
50
+ }, [delayPlayback, mediaRef, mediaType, onVariableFpsVideoDetected]);
47
51
  return (0, react_1.useMemo)(() => {
48
52
  return {
49
53
  isBuffering: () => bufferingRef.current,
@@ -13,3 +13,4 @@ export declare const serializeJSONWithDate: ({ data, indent, staticBase, }: {
13
13
  staticBase: string | null;
14
14
  }) => SerializedJSONWithCustomFields;
15
15
  export declare const deserializeJSONWithCustomFields: <T = Record<string, unknown>>(data: string) => T;
16
+ export declare const serializeThenDeserializeInStudio: (props: Record<string, unknown>) => Record<string, unknown>;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  // Must keep this file in sync with the one in packages/lambda/src/shared/serialize-props.ts!
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.deserializeJSONWithCustomFields = exports.serializeJSONWithDate = exports.FILE_TOKEN = exports.DATE_TOKEN = void 0;
4
+ exports.serializeThenDeserializeInStudio = exports.deserializeJSONWithCustomFields = exports.serializeJSONWithDate = exports.FILE_TOKEN = exports.DATE_TOKEN = void 0;
5
+ const get_remotion_environment_js_1 = require("./get-remotion-environment.js");
5
6
  const static_file_js_1 = require("./static-file.js");
6
7
  exports.DATE_TOKEN = 'remotion-date:';
7
8
  exports.FILE_TOKEN = 'remotion-file:';
@@ -10,29 +11,35 @@ const serializeJSONWithDate = ({ data, indent, staticBase, }) => {
10
11
  let customFileUsed = false;
11
12
  let mapUsed = false;
12
13
  let setUsed = false;
13
- const serializedString = JSON.stringify(data, function (key, value) {
14
- const item = this[key];
15
- if (item instanceof Date) {
16
- customDateUsed = true;
17
- return `${exports.DATE_TOKEN}${item.toISOString()}`;
18
- }
19
- if (item instanceof Map) {
20
- mapUsed = true;
21
- return value;
22
- }
23
- if (item instanceof Set) {
24
- setUsed = true;
14
+ try {
15
+ const serializedString = JSON.stringify(data, function (key, value) {
16
+ const item = this[key];
17
+ if (item instanceof Date) {
18
+ customDateUsed = true;
19
+ return `${exports.DATE_TOKEN}${item.toISOString()}`;
20
+ }
21
+ if (item instanceof Map) {
22
+ mapUsed = true;
23
+ return value;
24
+ }
25
+ if (item instanceof Set) {
26
+ setUsed = true;
27
+ return value;
28
+ }
29
+ if (typeof item === 'string' &&
30
+ staticBase !== null &&
31
+ item.startsWith(staticBase)) {
32
+ customFileUsed = true;
33
+ return `${exports.FILE_TOKEN}${item.replace(staticBase + '/', '')}`;
34
+ }
25
35
  return value;
26
- }
27
- if (typeof item === 'string' &&
28
- staticBase !== null &&
29
- item.startsWith(staticBase)) {
30
- customFileUsed = true;
31
- return `${exports.FILE_TOKEN}${item.replace(staticBase + '/', '')}`;
32
- }
33
- return value;
34
- }, indent);
35
- return { serializedString, customDateUsed, customFileUsed, mapUsed, setUsed };
36
+ }, indent);
37
+ return { serializedString, customDateUsed, customFileUsed, mapUsed, setUsed };
38
+ }
39
+ catch (err) {
40
+ throw new Error('Could not serialize the passed input props to JSON: ' +
41
+ err.message);
42
+ }
36
43
  };
37
44
  exports.serializeJSONWithDate = serializeJSONWithDate;
38
45
  const deserializeJSONWithCustomFields = (data) => {
@@ -47,3 +54,16 @@ const deserializeJSONWithCustomFields = (data) => {
47
54
  });
48
55
  };
49
56
  exports.deserializeJSONWithCustomFields = deserializeJSONWithCustomFields;
57
+ const serializeThenDeserializeInStudio = (props) => {
58
+ // Serializing once in the Studio, to catch potential serialization errors before
59
+ // you only get them during rendering
60
+ if ((0, get_remotion_environment_js_1.getRemotionEnvironment)().isStudio) {
61
+ return (0, exports.deserializeJSONWithCustomFields)((0, exports.serializeJSONWithDate)({
62
+ data: props,
63
+ indent: 2,
64
+ staticBase: window.remotion_staticBase,
65
+ }).serializedString);
66
+ }
67
+ return props;
68
+ };
69
+ exports.serializeThenDeserializeInStudio = serializeThenDeserializeInStudio;
@@ -2,7 +2,7 @@ import type { AnyZodObject } from 'zod';
2
2
  import type { CalculateMetadataFunction } from './Composition.js';
3
3
  import type { InferProps } from './props-if-has-props.js';
4
4
  import type { VideoConfig } from './video-config.js';
5
- export declare const resolveVideoConfig: ({ calculateMetadata, signal, defaultProps, originalProps, compositionId, compositionDurationInFrames, compositionFps, compositionHeight, compositionWidth, }: {
5
+ type ResolveVideoConfigParams = {
6
6
  compositionId: string;
7
7
  compositionWidth: number | null;
8
8
  compositionHeight: number | null;
@@ -12,4 +12,13 @@ export declare const resolveVideoConfig: ({ calculateMetadata, signal, defaultPr
12
12
  signal: AbortSignal;
13
13
  defaultProps: Record<string, unknown>;
14
14
  originalProps: Record<string, unknown>;
15
- }) => VideoConfig | Promise<VideoConfig>;
15
+ };
16
+ export declare const resolveVideoConfig: ({ calculateMetadata, signal, defaultProps, originalProps, compositionId, compositionDurationInFrames, compositionFps, compositionHeight, compositionWidth, }: ResolveVideoConfigParams) => VideoConfig | Promise<VideoConfig>;
17
+ export declare const resolveVideoConfigOrCatch: (params: ResolveVideoConfigParams) => {
18
+ type: 'success';
19
+ result: VideoConfig | Promise<VideoConfig>;
20
+ } | {
21
+ type: 'error';
22
+ error: Error;
23
+ };
24
+ export {};
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveVideoConfig = void 0;
3
+ exports.resolveVideoConfigOrCatch = exports.resolveVideoConfig = void 0;
4
+ const input_props_serialization_js_1 = require("./input-props-serialization.js");
4
5
  const validate_default_codec_js_1 = require("./validation/validate-default-codec.js");
5
6
  const validate_dimensions_js_1 = require("./validation/validate-dimensions.js");
6
7
  const validate_duration_in_frames_js_1 = require("./validation/validate-duration-in-frames.js");
@@ -53,8 +54,8 @@ const resolveVideoConfig = ({ calculateMetadata, signal, defaultProps, originalP
53
54
  fps,
54
55
  durationInFrames,
55
56
  id: compositionId,
56
- defaultProps,
57
- props: (_a = c.props) !== null && _a !== void 0 ? _a : originalProps,
57
+ defaultProps: (0, input_props_serialization_js_1.serializeThenDeserializeInStudio)(defaultProps),
58
+ props: (0, input_props_serialization_js_1.serializeThenDeserializeInStudio)((_a = c.props) !== null && _a !== void 0 ? _a : originalProps),
58
59
  defaultCodec: defaultCodec !== null && defaultCodec !== void 0 ? defaultCodec : null,
59
60
  };
60
61
  });
@@ -71,17 +72,33 @@ const resolveVideoConfig = ({ calculateMetadata, signal, defaultProps, originalP
71
72
  return {
72
73
  ...data,
73
74
  id: compositionId,
74
- defaultProps: defaultProps !== null && defaultProps !== void 0 ? defaultProps : {},
75
- props: originalProps,
75
+ defaultProps: (0, input_props_serialization_js_1.serializeThenDeserializeInStudio)(defaultProps !== null && defaultProps !== void 0 ? defaultProps : {}),
76
+ props: (0, input_props_serialization_js_1.serializeThenDeserializeInStudio)(originalProps),
76
77
  defaultCodec: null,
77
78
  };
78
79
  }
79
80
  return {
80
81
  ...data,
81
82
  id: compositionId,
82
- defaultProps: defaultProps !== null && defaultProps !== void 0 ? defaultProps : {},
83
- props: (_a = calculatedProm.props) !== null && _a !== void 0 ? _a : originalProps,
83
+ defaultProps: (0, input_props_serialization_js_1.serializeThenDeserializeInStudio)(defaultProps !== null && defaultProps !== void 0 ? defaultProps : {}),
84
+ props: (0, input_props_serialization_js_1.serializeThenDeserializeInStudio)((_a = calculatedProm.props) !== null && _a !== void 0 ? _a : originalProps),
84
85
  defaultCodec: (_b = calculatedProm.defaultCodec) !== null && _b !== void 0 ? _b : null,
85
86
  };
86
87
  };
87
88
  exports.resolveVideoConfig = resolveVideoConfig;
89
+ const resolveVideoConfigOrCatch = (params) => {
90
+ try {
91
+ const promiseOrReturnValue = (0, exports.resolveVideoConfig)(params);
92
+ return {
93
+ type: 'success',
94
+ result: promiseOrReturnValue,
95
+ };
96
+ }
97
+ catch (err) {
98
+ return {
99
+ type: 'error',
100
+ error: err,
101
+ };
102
+ }
103
+ };
104
+ exports.resolveVideoConfigOrCatch = resolveVideoConfigOrCatch;
@@ -28,7 +28,8 @@ const react_1 = __importStar(require("react"));
28
28
  // Expected, it can be any component props
29
29
  const useLazyComponent = (compProps) => {
30
30
  const lazy = (0, react_1.useMemo)(() => {
31
- if ('lazyComponent' in compProps) {
31
+ if ('lazyComponent' in compProps &&
32
+ typeof compProps.lazyComponent !== 'undefined') {
32
33
  return react_1.default.lazy(compProps.lazyComponent);
33
34
  }
34
35
  if ('component' in compProps) {
@@ -31,6 +31,7 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
31
31
  const buffering = (0, react_1.useContext)(buffering_js_1.BufferingContextReact);
32
32
  const { fps } = (0, use_video_config_js_1.useVideoConfig)();
33
33
  const mediaStartsAt = (0, use_audio_frame_js_1.useMediaStartsAt)();
34
+ const lastSeekDueToShift = (0, react_1.useRef)(null);
34
35
  if (!buffering) {
35
36
  throw new Error('useMediaPlayback must be used inside a <BufferingContext>');
36
37
  }
@@ -46,9 +47,21 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
46
47
  shouldBuffer: pauseWhenBuffering,
47
48
  isPremounting,
48
49
  });
50
+ const isVariableFpsVideoMap = (0, react_1.useRef)({});
51
+ const onVariableFpsVideoDetected = (0, react_1.useCallback)(() => {
52
+ if (!src) {
53
+ return;
54
+ }
55
+ if (debugSeeking) {
56
+ // eslint-disable-next-line no-console
57
+ console.log(`Detected ${src} as a variable FPS video. Disabling buffering while seeking.`);
58
+ }
59
+ isVariableFpsVideoMap.current[src] = true;
60
+ }, [debugSeeking, src]);
49
61
  const { bufferUntilFirstFrame, isBuffering } = (0, buffer_until_first_frame_js_1.useBufferUntilFirstFrame)({
50
62
  mediaRef,
51
63
  mediaType,
64
+ onVariableFpsVideoDetected,
52
65
  });
53
66
  const playbackRate = localPlaybackRate * globalPlaybackRate;
54
67
  // For short audio, a lower acceptable time shift is used
@@ -94,31 +107,42 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
94
107
  const shouldBeTime = !Number.isNaN(duration) && Number.isFinite(duration)
95
108
  ? Math.min(duration, desiredUnclampedTime)
96
109
  : desiredUnclampedTime;
97
- const isTime = mediaRef.current.currentTime;
110
+ const mediaTagTime = mediaRef.current.currentTime;
98
111
  const rvcTime = (_a = currentTime.current) !== null && _a !== void 0 ? _a : null;
99
- const timeShiftMediaTag = Math.abs(shouldBeTime - isTime);
112
+ const isVariableFpsVideo = isVariableFpsVideoMap.current[src];
113
+ const timeShiftMediaTag = Math.abs(shouldBeTime - mediaTagTime);
100
114
  const timeShiftRvcTag = rvcTime ? Math.abs(shouldBeTime - rvcTime) : null;
101
- const timeShift = timeShiftRvcTag ? timeShiftRvcTag : timeShiftMediaTag;
115
+ const timeShift = timeShiftRvcTag && !isVariableFpsVideo
116
+ ? timeShiftRvcTag
117
+ : timeShiftMediaTag;
102
118
  if (debugSeeking) {
103
119
  // eslint-disable-next-line no-console
104
120
  console.log({
105
- isTime,
121
+ mediaTagTime,
106
122
  rvcTime,
107
123
  shouldBeTime,
108
124
  state: mediaRef.current.readyState,
109
125
  playing: !mediaRef.current.paused,
126
+ isVariableFpsVideo,
110
127
  });
111
128
  }
112
- if (timeShift > acceptableTimeShiftButLessThanDuration) {
129
+ if (timeShift > acceptableTimeShiftButLessThanDuration &&
130
+ lastSeekDueToShift.current !== shouldBeTime) {
113
131
  // If scrubbing around, adjust timing
114
132
  // or if time shift is bigger than 0.45sec
115
133
  if (debugSeeking) {
116
134
  // eslint-disable-next-line no-console
117
- console.log('Seeking', { shouldBeTime, isTime, rvcTime, timeShift });
135
+ console.log('Seeking', {
136
+ shouldBeTime,
137
+ isTime: mediaTagTime,
138
+ rvcTime,
139
+ timeShift,
140
+ });
118
141
  }
119
142
  seek(mediaRef, shouldBeTime);
120
- if (playing) {
121
- bufferUntilFirstFrame();
143
+ lastSeekDueToShift.current = shouldBeTime;
144
+ if (playing && !isVariableFpsVideo) {
145
+ bufferUntilFirstFrame(shouldBeTime);
122
146
  if (mediaRef.current.paused) {
123
147
  (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)(mediaRef, mediaType);
124
148
  }
@@ -150,7 +174,9 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
150
174
  seek(mediaRef, shouldBeTime);
151
175
  }
152
176
  (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)(mediaRef, mediaType);
153
- bufferUntilFirstFrame();
177
+ if (!isVariableFpsVideo) {
178
+ bufferUntilFirstFrame(shouldBeTime);
179
+ }
154
180
  }
155
181
  }, [
156
182
  absoluteFrame,
@@ -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.176";
6
+ export declare const VERSION = "4.0.178";
@@ -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.176';
10
+ exports.VERSION = '4.0.178';
@@ -102,9 +102,9 @@ const OffthreadVideoForRendering = ({ onError, volume: volumeProp, playbackRate,
102
102
  toneMapped,
103
103
  });
104
104
  }, [toneMapped, currentTime, src, transparent]);
105
- const onErr = (0, react_1.useCallback)((e) => {
105
+ const onErr = (0, react_1.useCallback)(() => {
106
106
  if (onError) {
107
- onError === null || onError === void 0 ? void 0 : onError(e);
107
+ onError === null || onError === void 0 ? void 0 : onError(new Error('Failed to load image with src ' + actualSrc));
108
108
  }
109
109
  else {
110
110
  (0, cancel_render_js_1.cancelRender)('Failed to load image with src ' + actualSrc);
@@ -4,7 +4,7 @@ import type { RemotionMainVideoProps } from './props';
4
4
  * @description allows you to include a video file in your Remotion project. It wraps the native HTMLVideoElement.
5
5
  * @see [Documentation](https://www.remotion.dev/docs/video)
6
6
  */
7
- export declare const Video: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, "nonce" | "onEnded" | "autoPlay" | "controls"> & {
7
+ export declare const Video: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, "nonce" | "onError" | "onEnded" | "autoPlay" | "controls"> & {
8
8
  name?: string | undefined;
9
9
  volume?: import("../volume-prop.js").VolumeProp | undefined;
10
10
  playbackRate?: number | undefined;
@@ -16,6 +16,7 @@ export declare const Video: React.ForwardRefExoticComponent<Omit<Omit<React.Deta
16
16
  delayRenderTimeoutInMilliseconds?: number | undefined;
17
17
  loopVolumeCurveBehavior?: import("../audio/use-audio-frame.js").LoopVolumeCurveBehavior | undefined;
18
18
  delayRenderRetries?: number | undefined;
19
+ onError?: ((err: Error) => void) | undefined;
19
20
  } & RemotionMainVideoProps & {
20
21
  /**
21
22
  * @deprecated For internal use only
@@ -19,7 +19,7 @@ const VideoForDevelopmentRefForwardingFunction = (props, ref) => {
19
19
  const videoRef = (0, react_1.useRef)(null);
20
20
  const { volume, muted, playbackRate, onlyWarnForMediaSeekingError, src, onDuration,
21
21
  // @ts-expect-error
22
- acceptableTimeShift, acceptableTimeShiftInSeconds, toneFrequency, name, _remotionInternalNativeLoopPassed, _remotionInternalStack, _remotionDebugSeeking, style, pauseWhenBuffering, showInTimeline, loopVolumeCurveBehavior, ...nativeProps } = props;
22
+ acceptableTimeShift, acceptableTimeShiftInSeconds, toneFrequency, name, _remotionInternalNativeLoopPassed, _remotionInternalStack, _remotionDebugSeeking, style, pauseWhenBuffering, showInTimeline, loopVolumeCurveBehavior, onError, ...nativeProps } = props;
23
23
  const volumePropFrame = (0, use_audio_frame_js_1.useFrameForVolumeProp)(loopVolumeCurveBehavior !== null && loopVolumeCurveBehavior !== void 0 ? loopVolumeCurveBehavior : 'repeat');
24
24
  const { fps, durationInFrames } = (0, use_video_config_js_1.useVideoConfig)();
25
25
  const parentSequence = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
@@ -83,24 +83,32 @@ const VideoForDevelopmentRefForwardingFunction = (props, ref) => {
83
83
  }
84
84
  const errorHandler = () => {
85
85
  var _a;
86
- if (current === null || current === void 0 ? void 0 : current.error) {
86
+ if (current.error) {
87
87
  // eslint-disable-next-line no-console
88
88
  console.error('Error occurred in video', current === null || current === void 0 ? void 0 : current.error);
89
89
  // If user is handling the error, we don't cause an unhandled exception
90
- if (props.onError) {
90
+ if (onError) {
91
+ const err = new Error(`Code ${current.error.code}: ${current.error.message}`);
92
+ onError(err);
91
93
  return;
92
94
  }
93
95
  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.`);
94
96
  }
95
97
  else {
96
- throw new Error('The browser threw an error');
98
+ // If user is handling the error, we don't cause an unhandled exception
99
+ if (onError) {
100
+ const err = new Error(`The browser threw an error while playing the video ${src}`);
101
+ onError(err);
102
+ return;
103
+ }
104
+ throw new Error('The browser threw an error while playing the video');
97
105
  }
98
106
  };
99
107
  current.addEventListener('error', errorHandler, { once: true });
100
108
  return () => {
101
109
  current.removeEventListener('error', errorHandler);
102
110
  };
103
- }, [props.onError, src]);
111
+ }, [onError, src]);
104
112
  const currentOnDurationCallback = (0, react_1.useRef)();
105
113
  currentOnDurationCallback.current = onDuration;
106
114
  (0, react_1.useEffect)(() => {
@@ -1,6 +1,6 @@
1
1
  import type { ForwardRefExoticComponent, RefAttributes } from 'react';
2
2
  import React from 'react';
3
- export declare const VideoForRendering: ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, "nonce" | "onEnded" | "autoPlay" | "controls"> & {
3
+ export declare const VideoForRendering: ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, "nonce" | "onError" | "onEnded" | "autoPlay" | "controls"> & {
4
4
  name?: string | undefined;
5
5
  volume?: import("../volume-prop.js").VolumeProp | undefined;
6
6
  playbackRate?: number | undefined;
@@ -12,6 +12,7 @@ export declare const VideoForRendering: ForwardRefExoticComponent<Omit<React.Det
12
12
  delayRenderTimeoutInMilliseconds?: number | undefined;
13
13
  loopVolumeCurveBehavior?: import("../audio/use-audio-frame.js").LoopVolumeCurveBehavior | undefined;
14
14
  delayRenderRetries?: number | undefined;
15
+ onError?: ((err: Error) => void) | undefined;
15
16
  } & {
16
17
  readonly onDuration: (src: string, durationInSeconds: number) => void;
17
18
  } & RefAttributes<HTMLVideoElement>>;
@@ -203,6 +203,6 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
203
203
  };
204
204
  }, [src, onDuration, delayRenderRetries, delayRenderTimeoutInMilliseconds]);
205
205
  }
206
- return (0, jsx_runtime_1.jsx)("video", { ref: videoRef, ...props, onError: onError });
206
+ return (0, jsx_runtime_1.jsx)("video", { ref: videoRef, ...props });
207
207
  };
208
208
  exports.VideoForRendering = (0, react_1.forwardRef)(VideoForRenderingForwardFunction);
@@ -10,7 +10,7 @@ export type RemotionMainVideoProps = {
10
10
  _remotionInternalNativeLoopPassed?: boolean;
11
11
  _remotionDebugSeeking?: boolean;
12
12
  };
13
- export type RemotionVideoProps = Omit<React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, 'autoPlay' | 'controls' | 'onEnded' | 'nonce'> & {
13
+ export type RemotionVideoProps = Omit<React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>, 'autoPlay' | 'controls' | 'onEnded' | 'nonce' | 'onError'> & {
14
14
  name?: string;
15
15
  volume?: VolumeProp;
16
16
  playbackRate?: number;
@@ -22,6 +22,7 @@ export type RemotionVideoProps = Omit<React.DetailedHTMLProps<React.VideoHTMLAtt
22
22
  delayRenderTimeoutInMilliseconds?: number;
23
23
  loopVolumeCurveBehavior?: LoopVolumeCurveBehavior;
24
24
  delayRenderRetries?: number;
25
+ onError?: (err: Error) => void;
25
26
  };
26
27
  type DeprecatedOffthreadVideoProps = {
27
28
  /**
@@ -38,7 +39,7 @@ export type OffthreadVideoProps = {
38
39
  volume?: VolumeProp;
39
40
  playbackRate?: number;
40
41
  muted?: boolean;
41
- onError?: React.ReactEventHandler<HTMLVideoElement | HTMLImageElement>;
42
+ onError?: (err: Error) => void;
42
43
  acceptableTimeShiftInSeconds?: number;
43
44
  allowAmplificationDuringRender?: boolean;
44
45
  toneFrequency?: number;
@@ -47,6 +48,7 @@ export type OffthreadVideoProps = {
47
48
  pauseWhenBuffering?: boolean;
48
49
  loopVolumeCurveBehavior?: LoopVolumeCurveBehavior;
49
50
  delayRenderTimeoutInMilliseconds?: number;
51
+ delayRenderRetries?: number;
50
52
  /**
51
53
  * @deprecated For internal use only
52
54
  */