remotion 4.0.526 → 4.0.528

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/cjs/AbsoluteFill.d.ts +29 -3
  2. package/dist/cjs/AbsoluteFill.js +19 -2
  3. package/dist/cjs/CompositionManager.d.ts +2 -0
  4. package/dist/cjs/HtmlInCanvas.d.ts +28 -2
  5. package/dist/cjs/HtmlInCanvas.js +17 -3
  6. package/dist/cjs/Img.d.ts +2 -2
  7. package/dist/cjs/Img.js +1 -1
  8. package/dist/cjs/Interactive.d.ts +20 -2
  9. package/dist/cjs/Interactive.js +30 -8
  10. package/dist/cjs/Sequence.d.ts +1 -0
  11. package/dist/cjs/Sequence.js +49 -18
  12. package/dist/cjs/SequenceContext.d.ts +5 -0
  13. package/dist/cjs/TimelineContext.d.ts +2 -0
  14. package/dist/cjs/TimelineContext.js +8 -1
  15. package/dist/cjs/animated-image/AnimatedImage.d.ts +9 -10
  16. package/dist/cjs/animated-image/AnimatedImage.js +1 -12
  17. package/dist/cjs/audio/AudioForPreview.js +1 -0
  18. package/dist/cjs/audio/AudioForRendering.js +11 -7
  19. package/dist/cjs/audio/html5-audio.js +14 -7
  20. package/dist/cjs/audio/use-audio-frame.d.ts +1 -0
  21. package/dist/cjs/audio/use-audio-frame.js +14 -2
  22. package/dist/cjs/calculate-media-duration.js +1 -2
  23. package/dist/cjs/canvas-image/CanvasImage.d.ts +1 -1
  24. package/dist/cjs/canvas-image/CanvasImage.js +1 -1
  25. package/dist/cjs/canvas-image/props.d.ts +1 -1
  26. package/dist/cjs/effects/Solid.d.ts +28 -2
  27. package/dist/cjs/effects/Solid.js +16 -3
  28. package/dist/cjs/freeze.js +11 -3
  29. package/dist/cjs/get-asset-file-name.js +7 -1
  30. package/dist/cjs/get-sequence-boundary-tolerance.d.ts +7 -0
  31. package/dist/cjs/get-sequence-boundary-tolerance.js +10 -0
  32. package/dist/cjs/index.d.ts +3 -3
  33. package/dist/cjs/interactivity-schema.d.ts +75 -1
  34. package/dist/cjs/interactivity-schema.js +21 -1
  35. package/dist/cjs/internals.d.ts +23 -2
  36. package/dist/cjs/internals.js +2 -0
  37. package/dist/cjs/interpolate.d.ts +5 -0
  38. package/dist/cjs/interpolate.js +85 -5
  39. package/dist/cjs/loop/index.d.ts +7 -1
  40. package/dist/cjs/loop/index.js +35 -9
  41. package/dist/cjs/no-react.d.ts +10 -1
  42. package/dist/cjs/series/index.d.ts +1 -1
  43. package/dist/cjs/series/index.js +1 -0
  44. package/dist/cjs/spring/spring-utils.js +15 -12
  45. package/dist/cjs/timeline-position-state.d.ts +1 -0
  46. package/dist/cjs/timeline-position-state.js +8 -1
  47. package/dist/cjs/use-current-frame.js +2 -1
  48. package/dist/cjs/use-media-in-timeline.d.ts +5 -2
  49. package/dist/cjs/use-media-in-timeline.js +78 -13
  50. package/dist/cjs/use-media-playback.js +8 -3
  51. package/dist/cjs/use-premounting.js +19 -5
  52. package/dist/cjs/use-timeline-seek.d.ts +6 -0
  53. package/dist/cjs/use-timeline-seek.js +18 -0
  54. package/dist/cjs/version.d.ts +1 -1
  55. package/dist/cjs/version.js +1 -1
  56. package/dist/cjs/video/OffthreadVideo.js +7 -1
  57. package/dist/cjs/video/OffthreadVideoForRendering.js +11 -8
  58. package/dist/cjs/video/VideoForPreview.js +1 -0
  59. package/dist/cjs/video/VideoForRendering.js +12 -10
  60. package/dist/cjs/video/html5-video.js +12 -7
  61. package/dist/esm/index.mjs +927 -477
  62. package/dist/esm/no-react.mjs +103 -17
  63. package/dist/esm/version.mjs +1 -1
  64. package/package.json +2 -2
  65. package/dist/cjs/nonce.d.ts +0 -11
  66. package/dist/cjs/nonce.js +0 -42
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useBuffering = exports.usePlaying = exports.useTimelineSetFrame = exports.useAbsoluteTimelinePosition = exports.useIsInsideFreeze = exports.useTimelinePosition = exports.usePlaybackRate = exports.useTimelineContext = exports.clampFrameToCompositionRange = exports.getFrameForComposition = exports.getInitialFrameState = exports.persistCurrentFrame = void 0;
3
+ exports.useBuffering = exports.usePlaying = exports.useTimelineSeekFrame = exports.useTimelineSetFrame = exports.useAbsoluteTimelinePosition = exports.useIsInsideFreeze = exports.useTimelinePosition = exports.usePlaybackRate = exports.useTimelineContext = exports.clampFrameToCompositionRange = exports.getFrameForComposition = exports.getInitialFrameState = exports.persistCurrentFrame = void 0;
4
4
  const react_1 = require("react");
5
5
  const TimelineContext_js_1 = require("./TimelineContext.js");
6
6
  const use_remotion_environment_js_1 = require("./use-remotion-environment.js");
@@ -86,6 +86,13 @@ const useTimelineSetFrame = () => {
86
86
  return setFrame;
87
87
  };
88
88
  exports.useTimelineSetFrame = useTimelineSetFrame;
89
+ // Explicit navigation only. Playback and frame persistence must use setFrame.
90
+ const useTimelineSeekFrame = () => {
91
+ var _a;
92
+ const { seek, setFrame } = (0, react_1.useContext)(TimelineContext_js_1.SetTimelineContext);
93
+ return (_a = seek === null || seek === void 0 ? void 0 : seek.setFrame) !== null && _a !== void 0 ? _a : setFrame;
94
+ };
95
+ exports.useTimelineSeekFrame = useTimelineSeekFrame;
89
96
  const use_playing_js_1 = require("./use-playing.js");
90
97
  Object.defineProperty(exports, "usePlaying", { enumerable: true, get: function () { return use_playing_js_1.usePlaying; } });
91
98
  const use_buffering_js_1 = require("./use-buffering.js");
@@ -11,6 +11,7 @@ const use_remotion_environment_js_1 = require("./use-remotion-environment.js");
11
11
  * @see [Documentation](https://www.remotion.dev/docs/use-current-frame)
12
12
  */
13
13
  const useCurrentFrame = () => {
14
+ var _a;
14
15
  const canUseRemotionHooks = (0, react_1.useContext)(CanUseRemotionHooks_js_1.CanUseRemotionHooks);
15
16
  const env = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
16
17
  if (!canUseRemotionHooks) {
@@ -24,6 +25,6 @@ const useCurrentFrame = () => {
24
25
  const contextOffset = context
25
26
  ? context.cumulatedFrom + context.relativeFrom
26
27
  : 0;
27
- return frame - contextOffset;
28
+ return (frame - contextOffset) * ((_a = context === null || context === void 0 ? void 0 : context.playbackRate) !== null && _a !== void 0 ? _a : 1);
28
29
  };
29
30
  exports.useCurrentFrame = useCurrentFrame;
@@ -1,6 +1,7 @@
1
+ import { type LoopVolumeCurveBehavior } from './audio/use-audio-frame.js';
1
2
  import type { LoopDisplay } from './CompositionManager.js';
2
3
  import type { VolumeProp } from './volume-prop.js';
3
- export declare const useBasicMediaInTimeline: ({ volume, mediaVolume, mediaType, src, displayName, trimBefore, trimAfter, playbackRate, sequenceDurationInFrames, mediaStartsAt, loop, muted, }: {
4
+ export declare const useBasicMediaInTimeline: ({ volume, mediaVolume, mediaType, src, displayName, trimBefore, trimAfter, playbackRate, sequenceDurationInFrames, mediaStartsAt, mediaFrom, loop, muted, }: {
4
5
  volume: VolumeProp | undefined;
5
6
  mediaVolume: number;
6
7
  mediaType: "audio" | "image" | "video";
@@ -11,6 +12,7 @@ export declare const useBasicMediaInTimeline: ({ volume, mediaVolume, mediaType,
11
12
  playbackRate: number;
12
13
  sequenceDurationInFrames: number;
13
14
  mediaStartsAt: number;
15
+ mediaFrom: number;
14
16
  loop: boolean;
15
17
  muted: boolean;
16
18
  }) => {
@@ -24,7 +26,7 @@ export declare const useBasicMediaInTimeline: ({ volume, mediaVolume, mediaType,
24
26
  muted: boolean;
25
27
  };
26
28
  export type BasicMediaInTimelineReturnType = ReturnType<typeof useBasicMediaInTimeline>;
27
- export declare const useMediaInTimeline: ({ volume, mediaVolume, src, mediaType, playbackRate, displayName, id, getStack, showInTimeline, premountDisplay, postmountDisplay, loopDisplay, documentationLink, refForOutline, muted, }: {
29
+ export declare const useMediaInTimeline: ({ volume, mediaVolume, src, mediaType, playbackRate, displayName, id, getStack, showInTimeline, premountDisplay, postmountDisplay, loopDisplay, loopVolumeCurveBehavior, documentationLink, refForOutline, muted, }: {
28
30
  volume: VolumeProp | undefined;
29
31
  mediaVolume: number;
30
32
  src: string | undefined;
@@ -37,6 +39,7 @@ export declare const useMediaInTimeline: ({ volume, mediaVolume, src, mediaType,
37
39
  premountDisplay: number | null;
38
40
  postmountDisplay: number | null;
39
41
  loopDisplay: LoopDisplay | undefined;
42
+ loopVolumeCurveBehavior: LoopVolumeCurveBehavior;
40
43
  documentationLink: string | null;
41
44
  refForOutline: import("react").RefObject<Element | null> | null;
42
45
  muted: boolean;
@@ -5,6 +5,7 @@ const react_1 = require("react");
5
5
  const use_audio_frame_js_1 = require("./audio/use-audio-frame.js");
6
6
  const get_asset_file_name_js_1 = require("./get-asset-file-name.js");
7
7
  const get_timeline_duration_js_1 = require("./get-timeline-duration.js");
8
+ const index_js_1 = require("./loop/index.js");
8
9
  const SequenceContext_js_1 = require("./SequenceContext.js");
9
10
  const SequenceManager_js_1 = require("./SequenceManager.js");
10
11
  const use_remotion_environment_js_1 = require("./use-remotion-environment.js");
@@ -20,19 +21,20 @@ const warnOnce = (message) => {
20
21
  console.warn(message);
21
22
  didWarn[message] = true;
22
23
  };
23
- const useBasicMediaInTimeline = ({ volume, mediaVolume, mediaType, src, displayName, trimBefore, trimAfter, playbackRate, sequenceDurationInFrames, mediaStartsAt, loop, muted, }) => {
24
- var _a;
24
+ const useBasicMediaInTimeline = ({ volume, mediaVolume, mediaType, src, displayName, trimBefore, trimAfter, playbackRate, sequenceDurationInFrames, mediaStartsAt, mediaFrom, loop, muted, }) => {
25
+ var _a, _b;
25
26
  if (!src) {
26
27
  throw new Error('No src passed');
27
28
  }
28
29
  const parentSequence = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
30
+ const sequencePlaybackRate = (_a = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.playbackRate) !== null && _a !== void 0 ? _a : 1;
29
31
  const [initialVolume] = (0, react_1.useState)(() => volume);
30
32
  const duration = (0, get_timeline_duration_js_1.getTimelineDuration)({
31
33
  compositionDurationInFrames: sequenceDurationInFrames,
32
34
  playbackRate,
33
35
  trimBefore,
34
36
  trimAfter,
35
- parentSequenceDurationInFrames: (_a = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.durationInFrames) !== null && _a !== void 0 ? _a : null,
37
+ parentSequenceDurationInFrames: (_b = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.durationInFrames) !== null && _b !== void 0 ? _b : null,
36
38
  loop,
37
39
  });
38
40
  const volumes = (0, react_1.useMemo)(() => {
@@ -46,17 +48,27 @@ const useBasicMediaInTimeline = ({ volume, mediaVolume, mediaType, src, displayN
46
48
  mediaVolume,
47
49
  });
48
50
  }
49
- return new Array(Math.floor(Math.max(0, duration + mediaStartsAt)))
51
+ // Curves start at the first visible frame, just like the live volume callback.
52
+ // Sampling composition frames also preserves fractional local frames at slow rates.
53
+ return new Array(Math.ceil(Math.max(0, duration + Math.min(0, mediaStartsAt + mediaFrom)) /
54
+ sequencePlaybackRate))
50
55
  .fill(true)
51
56
  .map((_, i) => {
52
57
  return (0, volume_prop_js_1.evaluateVolume)({
53
- frame: i + mediaStartsAt,
58
+ frame: i * sequencePlaybackRate,
54
59
  volume,
55
60
  mediaVolume,
56
61
  });
57
62
  })
58
63
  .join(',');
59
- }, [duration, mediaStartsAt, volume, mediaVolume]);
64
+ }, [
65
+ duration,
66
+ mediaStartsAt,
67
+ mediaFrom,
68
+ volume,
69
+ mediaVolume,
70
+ sequencePlaybackRate,
71
+ ]);
60
72
  (0, react_1.useEffect)(() => {
61
73
  if (typeof volume === 'number' && volume !== initialVolume) {
62
74
  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/audio/volume`);
@@ -88,15 +100,17 @@ const useBasicMediaInTimeline = ({ volume, mediaVolume, mediaType, src, displayN
88
100
  return memoizedResult;
89
101
  };
90
102
  exports.useBasicMediaInTimeline = useBasicMediaInTimeline;
91
- const useMediaInTimeline = ({ volume, mediaVolume, src, mediaType, playbackRate, displayName, id, getStack, showInTimeline, premountDisplay, postmountDisplay, loopDisplay, documentationLink, refForOutline, muted, }) => {
103
+ const useMediaInTimeline = ({ volume, mediaVolume, src, mediaType, playbackRate, displayName, id, getStack, showInTimeline, premountDisplay, postmountDisplay, loopDisplay, loopVolumeCurveBehavior, documentationLink, refForOutline, muted, }) => {
92
104
  var _a, _b;
93
105
  const parentSequence = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
94
- const startsAt = (0, use_audio_frame_js_1.useMediaStartsAt)();
106
+ const mediaTrimBefore = (0, react_1.useContext)(use_audio_frame_js_1.Html5MediaTrimContext);
95
107
  const sequenceRegistrationEnabled = (0, react_1.useContext)(SequenceManager_js_1.SequenceRegistrationContext);
96
108
  const { durationInFrames } = (0, use_video_config_js_1.useVideoConfig)();
97
109
  const mediaStartsAt = (0, use_audio_frame_js_1.useMediaStartsAt)();
98
- const { volumes, duration, doesVolumeChange, finalDisplayName } = (0, exports.useBasicMediaInTimeline)({
99
- volume,
110
+ const loopContext = index_js_1.Loop.useLoop();
111
+ const loopTimeline = (0, react_1.useContext)(index_js_1.LoopTimelineContext);
112
+ const { volumes: basicVolumes, duration, finalDisplayName, } = (0, exports.useBasicMediaInTimeline)({
113
+ volume: loopContext && typeof volume === 'function' ? undefined : volume,
100
114
  mediaVolume,
101
115
  mediaType,
102
116
  src,
@@ -106,9 +120,59 @@ const useMediaInTimeline = ({ volume, mediaVolume, src, mediaType, playbackRate,
106
120
  playbackRate,
107
121
  sequenceDurationInFrames: durationInFrames,
108
122
  mediaStartsAt,
123
+ mediaFrom: 0,
109
124
  loop: false,
110
125
  muted,
111
126
  });
127
+ const doesVolumeChange = typeof volume === 'function';
128
+ const volumes = (0, react_1.useMemo)(() => {
129
+ var _a;
130
+ if (!loopContext || !loopTimeline || typeof volume !== 'function') {
131
+ return basicVolumes;
132
+ }
133
+ const timeline = loopTimeline;
134
+ const loopDuration = loopContext.durationInFrames;
135
+ const sequenceRate = (_a = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.playbackRate) !== null && _a !== void 0 ? _a : 1;
136
+ const mediaOrigin = parentSequence
137
+ ? parentSequence.cumulatedFrom + parentSequence.relativeFrom
138
+ : 0;
139
+ const mediaOffset = mediaOrigin +
140
+ mediaTrimBefore / sequenceRate -
141
+ (timeline.startFrame +
142
+ (loopContext.iteration * loopDuration) / timeline.playbackRate);
143
+ const firstVisible = Math.max(0, timeline.firstVisibleFrame);
144
+ const firstCompositionFrame = Math.ceil(firstVisible);
145
+ return Array.from({
146
+ length: Math.max(0, Math.ceil(timeline.endFrame) - firstCompositionFrame),
147
+ }, (_, index) => {
148
+ const compositionFrame = firstCompositionFrame + index;
149
+ const loopsElapsed = ((compositionFrame - timeline.startFrame) * timeline.playbackRate) /
150
+ loopDuration;
151
+ const nearestIteration = Math.round(loopsElapsed);
152
+ const iteration = Math.abs(loopsElapsed - nearestIteration) <=
153
+ Number.EPSILON * Math.max(1, Math.abs(loopsElapsed)) * 4
154
+ ? nearestIteration
155
+ : Math.floor(loopsElapsed);
156
+ const iterationStart = timeline.startFrame +
157
+ (iteration * loopDuration) / timeline.playbackRate +
158
+ mediaOffset;
159
+ const frame = Math.max(0, compositionFrame - Math.max(firstVisible, iterationStart)) *
160
+ sequenceRate +
161
+ (loopVolumeCurveBehavior === 'extend'
162
+ ? (iteration * loopDuration * sequenceRate) / timeline.playbackRate
163
+ : 0);
164
+ return (0, volume_prop_js_1.evaluateVolume)({ frame, volume, mediaVolume });
165
+ }).join(',');
166
+ }, [
167
+ basicVolumes,
168
+ loopContext,
169
+ loopTimeline,
170
+ mediaTrimBefore,
171
+ mediaVolume,
172
+ parentSequence,
173
+ volume,
174
+ loopVolumeCurveBehavior,
175
+ ]);
112
176
  const { isStudio } = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
113
177
  const getSequenceForRegistration = (0, react_1.useCallback)(() => {
114
178
  var _a;
@@ -130,11 +194,12 @@ const useMediaInTimeline = ({ volume, mediaVolume, src, mediaType, playbackRate,
130
194
  muted,
131
195
  showInTimeline: true,
132
196
  timelineOrder: null,
133
- startMediaFrom: 0 - startsAt,
134
- mediaFrameAtSequenceZero: null,
197
+ startMediaFrom: mediaTrimBefore,
198
+ mediaFrameAtSequenceZero: mediaTrimBefore * (1 - playbackRate),
135
199
  doesVolumeChange,
136
200
  loopDisplay,
137
201
  playbackRate,
202
+ sequencePlaybackRate: 1,
138
203
  getStack,
139
204
  premountDisplay,
140
205
  postmountDisplay,
@@ -153,7 +218,7 @@ const useMediaInTimeline = ({ volume, mediaVolume, src, mediaType, playbackRate,
153
218
  volumes,
154
219
  doesVolumeChange,
155
220
  mediaType,
156
- startsAt,
221
+ mediaTrimBefore,
157
222
  playbackRate,
158
223
  getStack,
159
224
  premountDisplay,
@@ -11,6 +11,7 @@ const media_tag_current_time_timestamp_js_1 = require("./media-tag-current-time-
11
11
  const play_and_handle_not_allowed_error_js_1 = require("./play-and-handle-not-allowed-error.js");
12
12
  const playback_logging_js_1 = require("./playback-logging.js");
13
13
  const seek_js_1 = require("./seek.js");
14
+ const SequenceContext_js_1 = require("./SequenceContext.js");
14
15
  const timeline_position_state_js_1 = require("./timeline-position-state.js");
15
16
  const use_buffering_js_1 = require("./use-buffering.js");
16
17
  const use_current_frame_js_1 = require("./use-current-frame.js");
@@ -35,13 +36,14 @@ const getPauseReason = ({ reason, isPremounting, isPostmounting, }) => {
35
36
  return 'Player is not playing';
36
37
  };
37
38
  const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybackRate, preservePitch = true, onlyWarnForMediaSeekingError, acceptableTimeshift, pauseWhenBuffering, isPremounting, isPostmounting, onAutoPlayError, }) => {
39
+ var _a;
38
40
  const { playbackRate: globalPlaybackRate } = (0, timeline_position_state_js_1.usePlaybackRate)();
39
41
  const frame = (0, use_current_frame_js_1.useCurrentFrame)();
40
42
  const absoluteFrame = (0, timeline_position_state_js_1.useTimelinePosition)();
41
43
  const playing = (0, timeline_position_state_js_1.usePlaying)();
42
44
  const playerBuffering = (0, use_buffering_js_1.useBuffering)();
43
45
  const { fps } = (0, use_video_config_js_1.useVideoConfig)();
44
- const mediaStartsAt = (0, use_audio_frame_js_1.useMediaStartsAt)();
46
+ const mediaTrimBefore = (0, react_1.useContext)(use_audio_frame_js_1.Html5MediaTrimContext);
45
47
  const lastSeekDueToShift = (0, react_1.useRef)(null);
46
48
  const lastSeek = (0, react_1.useRef)(null);
47
49
  const logLevel = (0, log_level_context_js_1.useLogLevel)();
@@ -67,7 +69,7 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
67
69
  const desiredUnclampedTime = (0, get_current_time_js_1.getMediaTime)({
68
70
  frame,
69
71
  playbackRate: localPlaybackRate,
70
- startFrom: -mediaStartsAt,
72
+ startFrom: mediaTrimBefore,
71
73
  fps,
72
74
  });
73
75
  const isMediaTagBuffering = (0, use_media_buffering_js_1.useMediaBuffering)({
@@ -87,7 +89,10 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
87
89
  logLevel,
88
90
  mountTime,
89
91
  });
90
- const playbackRate = localPlaybackRate * globalPlaybackRate;
92
+ const sequenceContext = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
93
+ const playbackRate = localPlaybackRate *
94
+ globalPlaybackRate *
95
+ ((_a = sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.playbackRate) !== null && _a !== void 0 ? _a : 1);
91
96
  const acceptableTimeShiftButLessThanDuration = (() => {
92
97
  var _a;
93
98
  // For short audio, a lower acceptable time shift is used
@@ -2,12 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.usePremounting = void 0;
4
4
  const react_1 = require("react");
5
+ const get_sequence_boundary_tolerance_js_1 = require("./get-sequence-boundary-tolerance.js");
5
6
  const PremountContext_js_1 = require("./PremountContext.js");
7
+ const SequenceContext_js_1 = require("./SequenceContext.js");
8
+ const timeline_position_state_js_1 = require("./timeline-position-state.js");
6
9
  const use_current_frame_js_1 = require("./use-current-frame.js");
7
10
  const use_remotion_environment_js_1 = require("./use-remotion-environment.js");
8
11
  const use_video_config_js_1 = require("./use-video-config.js");
9
12
  const v5_flag_js_1 = require("./v5-flag.js");
10
13
  const usePremounting = ({ from, durationInFrames, premountFor, postmountFor, style, styleWhilePremounted, styleWhilePostmounted, hideWhilePremounted, }) => {
14
+ var _a;
11
15
  const parentPremountContext = (0, react_1.useContext)(PremountContext_js_1.PremountContext);
12
16
  const frame = (0, use_current_frame_js_1.useCurrentFrame)() - parentPremountContext.premountFramesRemaining;
13
17
  const environment = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
@@ -16,13 +20,23 @@ const usePremounting = ({ from, durationInFrames, premountFor, postmountFor, sty
16
20
  ? (premountFor !== null && premountFor !== void 0 ? premountFor : fps)
17
21
  : (premountFor !== null && premountFor !== void 0 ? premountFor : 0);
18
22
  const effectivePostmountFor = postmountFor !== null && postmountFor !== void 0 ? postmountFor : 0;
19
- const endThreshold = Math.ceil(from + durationInFrames - 1);
23
+ const endExclusive = from + durationInFrames;
24
+ const sequenceContext = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
25
+ const boundaryTolerance = (0, get_sequence_boundary_tolerance_js_1.getSequenceBoundaryTolerance)({
26
+ absoluteFrame: (0, timeline_position_state_js_1.useTimelinePosition)(),
27
+ cumulatedFrom: sequenceContext
28
+ ? sequenceContext.cumulatedFrom + sequenceContext.relativeFrom
29
+ : 0,
30
+ from,
31
+ parentPlaybackRate: (_a = sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.playbackRate) !== null && _a !== void 0 ? _a : 1,
32
+ durationInFrames,
33
+ });
20
34
  const premountingActive = !environment.isRendering &&
21
- frame < from &&
22
- frame >= from - effectivePremountFor;
35
+ frame - from < -boundaryTolerance &&
36
+ frame - (from - effectivePremountFor) >= -boundaryTolerance;
23
37
  const postmountingActive = !environment.isRendering &&
24
- frame > endThreshold &&
25
- frame <= endThreshold + effectivePostmountFor;
38
+ frame - endExclusive >= -boundaryTolerance &&
39
+ frame - (endExclusive + effectivePostmountFor) < -boundaryTolerance;
26
40
  const isPremountingOrPostmounting = premountingActive || postmountingActive;
27
41
  const freezeFrame = premountingActive
28
42
  ? from
@@ -0,0 +1,6 @@
1
+ import type { RefObject, SetStateAction } from 'react';
2
+ export type TimelineSeek = {
3
+ readonly revision: RefObject<number>;
4
+ readonly setFrame: (frame: SetStateAction<Record<string, number>>) => void;
5
+ };
6
+ export declare const useTimelineSeek: (setFrame: (frame: SetStateAction<Record<string, number>>) => void) => TimelineSeek;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useTimelineSeek = void 0;
4
+ const react_1 = require("react");
5
+ // One instance per timeline, shared by Studio and Player. Keep the revision
6
+ // outside the React updater: updaters may be deferred or run more than once.
7
+ const useTimelineSeek = (setFrame) => {
8
+ const revision = (0, react_1.useRef)(0);
9
+ return (0, react_1.useMemo)(() => ({
10
+ revision,
11
+ setFrame: (frame) => {
12
+ // Even a same-frame seek is a boundary for the next media update.
13
+ revision.current++;
14
+ setFrame(frame);
15
+ },
16
+ }), [setFrame]);
17
+ };
18
+ exports.useTimelineSeek = useTimelineSeek;
@@ -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.526";
6
+ export declare const VERSION = "4.0.528";
@@ -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.526';
10
+ exports.VERSION = '4.0.528';
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OffthreadVideo = exports.InnerOffthreadVideo = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
+ const use_audio_frame_js_1 = require("../audio/use-audio-frame.js");
6
7
  const enable_sequence_stack_traces_js_1 = require("../enable-sequence-stack-traces.js");
7
8
  const Sequence_js_1 = require("../Sequence.js");
8
9
  const use_remotion_environment_js_1 = require("../use-remotion-environment.js");
@@ -12,6 +13,7 @@ const validate_start_from_props_js_1 = require("../validate-start-from-props.js"
12
13
  const OffthreadVideoForRendering_js_1 = require("./OffthreadVideoForRendering.js");
13
14
  const VideoForPreview_js_1 = require("./VideoForPreview.js");
14
15
  const InnerOffthreadVideo = (props) => {
16
+ var _a;
15
17
  // Should only destruct `startFrom` and `endAt` from props,
16
18
  // rest gets drilled down
17
19
  const { startFrom, endAt, trimBefore, trimAfter, name, pauseWhenBuffering, _remotionInternalStack, showInTimeline, ...otherProps } = props;
@@ -33,7 +35,11 @@ const InnerOffthreadVideo = (props) => {
33
35
  });
34
36
  if (typeof trimBeforeValue !== 'undefined' ||
35
37
  typeof trimAfterValue !== 'undefined') {
36
- return (jsx_runtime_1.jsx(Sequence_js_1.Sequence, { layout: "none", from: 0 - (trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0), showInTimeline: false, durationInFrames: trimAfterValue, name: name, children: jsx_runtime_1.jsx(exports.InnerOffthreadVideo, { pauseWhenBuffering: shouldPauseWhenBuffering, ...otherProps, trimAfter: undefined, name: undefined, showInTimeline: showInTimeline, trimBefore: undefined, _remotionInternalStack: undefined, startFrom: undefined, endAt: undefined }) }));
38
+ return (jsx_runtime_1.jsx(use_audio_frame_js_1.Html5MediaTrimContext.Provider, { value: trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0, children: jsx_runtime_1.jsx(Sequence_js_1.Sequence, { layout: "none", from: 0 - (trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0), showInTimeline: false, durationInFrames: trimAfterValue === undefined
39
+ ? undefined
40
+ : (trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0) +
41
+ (trimAfterValue - (trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0)) /
42
+ ((_a = props.playbackRate) !== null && _a !== void 0 ? _a : 1), name: name, children: jsx_runtime_1.jsx(exports.InnerOffthreadVideo, { pauseWhenBuffering: shouldPauseWhenBuffering, ...otherProps, trimAfter: undefined, name: undefined, showInTimeline: showInTimeline, trimBefore: undefined, _remotionInternalStack: undefined, startFrom: undefined, endAt: undefined }) }) }));
37
43
  }
38
44
  (0, validate_media_props_js_1.validateMediaProps)(props, 'Video');
39
45
  if (environment.isRendering) {
@@ -26,12 +26,14 @@ const OffthreadVideoForRendering = ({ onError, volume: volumeProp, playbackRate,
26
26
  // Remove crossOrigin prop during rendering
27
27
  // https://discord.com/channels/809501355504959528/844143007183667220/1311639632496033813
28
28
  crossOrigin, audioStreamIndex, preservePitch: _preservePitch, ...props }) => {
29
+ var _a;
29
30
  const absoluteFrame = (0, timeline_position_state_js_1.useTimelinePosition)();
30
31
  const frame = (0, use_current_frame_js_1.useCurrentFrame)();
31
32
  const volumePropsFrame = (0, use_audio_frame_js_1.useFrameForVolumeProp)(loopVolumeCurveBehavior);
32
33
  const videoConfig = (0, use_unsafe_video_config_js_1.useUnsafeVideoConfig)();
33
34
  const sequenceContext = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
34
- const mediaStartsAt = (0, use_audio_frame_js_1.useMediaStartsAt)();
35
+ const sequencePlaybackRate = (_a = sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.playbackRate) !== null && _a !== void 0 ? _a : 1;
36
+ const audioStartFrame = (0, react_1.useContext)(use_audio_frame_js_1.Html5MediaTrimContext);
35
37
  const { registerRenderAsset, unregisterRenderAsset } = (0, react_1.useContext)(RenderAssetManager_js_1.RenderAssetManager);
36
38
  if (!src) {
37
39
  throw new TypeError('No `src` was passed to <OffthreadVideo>.');
@@ -60,7 +62,6 @@ crossOrigin, audioStreamIndex, preservePitch: _preservePitch, ...props }) => {
60
62
  throw new Error('No video config found');
61
63
  }
62
64
  (0, react_1.useEffect)(() => {
63
- var _a;
64
65
  if (!src) {
65
66
  throw new Error('No src passed');
66
67
  }
@@ -73,10 +74,11 @@ crossOrigin, audioStreamIndex, preservePitch: _preservePitch, ...props }) => {
73
74
  id,
74
75
  frame: absoluteFrame,
75
76
  volume,
76
- mediaFrame: frame,
77
- playbackRate,
77
+ // Render assets use composition frames; source trimming stays in media frames.
78
+ mediaFrame: audioStartFrame + (frame - audioStartFrame) / sequencePlaybackRate,
79
+ playbackRate: playbackRate * sequencePlaybackRate,
78
80
  toneFrequency,
79
- audioStartFrame: Math.max(0, -((_a = sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedNegativeFrom) !== null && _a !== void 0 ? _a : 0)),
81
+ audioStartFrame,
80
82
  audioStreamIndex,
81
83
  });
82
84
  return () => unregisterRenderAsset(id);
@@ -91,16 +93,17 @@ crossOrigin, audioStreamIndex, preservePitch: _preservePitch, ...props }) => {
91
93
  absoluteFrame,
92
94
  playbackRate,
93
95
  toneFrequency,
94
- sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedNegativeFrom,
96
+ audioStartFrame,
97
+ sequencePlaybackRate,
95
98
  audioStreamIndex,
96
99
  ]);
97
100
  const currentTime = (0, react_1.useMemo)(() => {
98
101
  return ((0, get_current_time_js_1.getExpectedMediaFrameUncorrected)({
99
102
  frame,
100
103
  playbackRate: playbackRate || 1,
101
- startFrom: -mediaStartsAt,
104
+ startFrom: audioStartFrame,
102
105
  }) / videoConfig.fps);
103
- }, [frame, mediaStartsAt, playbackRate, videoConfig.fps]);
106
+ }, [frame, audioStartFrame, playbackRate, videoConfig.fps]);
104
107
  const actualSrc = (0, react_1.useMemo)(() => {
105
108
  return (0, offthread_video_source_js_1.getOffthreadVideoSource)({
106
109
  src,
@@ -139,6 +139,7 @@ const VideoForDevelopmentRefForwardingFunction = (props, ref) => {
139
139
  premountDisplay: (_c = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.premountDisplay) !== null && _c !== void 0 ? _c : null,
140
140
  postmountDisplay: (_d = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.postmountDisplay) !== null && _d !== void 0 ? _d : null,
141
141
  loopDisplay: undefined,
142
+ loopVolumeCurveBehavior: loopVolumeCurveBehavior !== null && loopVolumeCurveBehavior !== void 0 ? loopVolumeCurveBehavior : 'repeat',
142
143
  documentationLink: onlyWarnForMediaSeekingError
143
144
  ? 'https://www.remotion.dev/docs/offthreadvideo'
144
145
  : 'https://www.remotion.dev/docs/html5-video',
@@ -24,14 +24,15 @@ const get_current_time_js_1 = require("./get-current-time.js");
24
24
  const MediaPlaybackError_js_1 = require("./MediaPlaybackError.js");
25
25
  const seek_until_right_js_1 = require("./seek-until-right.js");
26
26
  const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAmplificationDuringRender, playbackRate, onDuration, toneFrequency, name, acceptableTimeShiftInSeconds, delayRenderRetries, delayRenderTimeoutInMilliseconds, loopVolumeCurveBehavior, audioStreamIndex, onVideoFrame, preservePitch: _preservePitch, ...props }, ref) => {
27
- var _a;
27
+ var _a, _b;
28
28
  const absoluteFrame = (0, timeline_position_state_js_1.useTimelinePosition)();
29
29
  const frame = (0, use_current_frame_js_1.useCurrentFrame)();
30
30
  const volumePropsFrame = (0, use_audio_frame_js_1.useFrameForVolumeProp)(loopVolumeCurveBehavior !== null && loopVolumeCurveBehavior !== void 0 ? loopVolumeCurveBehavior : 'repeat');
31
31
  const videoConfig = (0, use_unsafe_video_config_js_1.useUnsafeVideoConfig)();
32
32
  const videoRef = (0, react_1.useRef)(null);
33
33
  const sequenceContext = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
34
- const mediaStartsAt = (0, use_audio_frame_js_1.useMediaStartsAt)();
34
+ const sequencePlaybackRate = (_a = sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.playbackRate) !== null && _a !== void 0 ? _a : 1;
35
+ const audioStartFrame = (0, react_1.useContext)(use_audio_frame_js_1.Html5MediaTrimContext);
35
36
  const environment = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
36
37
  const logLevel = (0, log_level_context_js_1.useLogLevel)();
37
38
  const mountTime = (0, log_level_context_js_1.useMountTime)();
@@ -56,7 +57,7 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
56
57
  (0, volume_safeguard_js_1.warnAboutTooHighVolume)(volume);
57
58
  const audioEnabled = (0, use_media_enabled_js_1.useAudioEnabled)();
58
59
  const { shouldUseAudio } = (0, use_media_audio_state_js_1.useMediaAudioState)({
59
- muted: (_a = props.muted) !== null && _a !== void 0 ? _a : false,
60
+ muted: (_b = props.muted) !== null && _b !== void 0 ? _b : false,
60
61
  volume,
61
62
  audioEnabled,
62
63
  });
@@ -64,7 +65,6 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
64
65
  throw new Error('No video config found');
65
66
  }
66
67
  (0, react_1.useEffect)(() => {
67
- var _a;
68
68
  if (!props.src) {
69
69
  throw new Error('No src passed');
70
70
  }
@@ -77,10 +77,11 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
77
77
  id,
78
78
  frame: absoluteFrame,
79
79
  volume,
80
- mediaFrame: frame,
81
- playbackRate: playbackRate !== null && playbackRate !== void 0 ? playbackRate : 1,
80
+ // Render assets use composition frames; source trimming stays in media frames.
81
+ mediaFrame: audioStartFrame + (frame - audioStartFrame) / sequencePlaybackRate,
82
+ playbackRate: (playbackRate !== null && playbackRate !== void 0 ? playbackRate : 1) * sequencePlaybackRate,
82
83
  toneFrequency: toneFrequency !== null && toneFrequency !== void 0 ? toneFrequency : 1,
83
- audioStartFrame: Math.max(0, -((_a = sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedNegativeFrom) !== null && _a !== void 0 ? _a : 0)),
84
+ audioStartFrame,
84
85
  audioStreamIndex: audioStreamIndex !== null && audioStreamIndex !== void 0 ? audioStreamIndex : 0,
85
86
  });
86
87
  return () => unregisterRenderAsset(id);
@@ -95,7 +96,8 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
95
96
  absoluteFrame,
96
97
  playbackRate,
97
98
  toneFrequency,
98
- sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedNegativeFrom,
99
+ audioStartFrame,
100
+ sequencePlaybackRate,
99
101
  audioStreamIndex,
100
102
  ]);
101
103
  (0, react_1.useImperativeHandle)(ref, () => {
@@ -114,7 +116,7 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
114
116
  const currentTime = (0, get_current_time_js_1.getMediaTime)({
115
117
  frame,
116
118
  playbackRate: playbackRate || 1,
117
- startFrom: -mediaStartsAt,
119
+ startFrom: audioStartFrame,
118
120
  fps: videoConfig.fps,
119
121
  });
120
122
  const handle = delayRender(`Rendering <Html5Video /> with src="${props.src}" at time ${currentTime}`, {
@@ -187,7 +189,7 @@ const VideoForRenderingForwardFunction = ({ onError, volume: volumeProp, allowAm
187
189
  playbackRate,
188
190
  videoConfig.fps,
189
191
  frame,
190
- mediaStartsAt,
192
+ audioStartFrame,
191
193
  onError,
192
194
  delayRenderRetries,
193
195
  delayRenderTimeoutInMilliseconds,
@@ -5,6 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  /* eslint-disable @typescript-eslint/no-use-before-define */
6
6
  const react_1 = require("react");
7
7
  const absolute_src_js_1 = require("../absolute-src.js");
8
+ const use_audio_frame_js_1 = require("../audio/use-audio-frame.js");
8
9
  const calculate_media_duration_js_1 = require("../calculate-media-duration.js");
9
10
  const enable_sequence_stack_traces_js_1 = require("../enable-sequence-stack-traces.js");
10
11
  const index_js_1 = require("../loop/index.js");
@@ -47,23 +48,27 @@ const VideoForwardingFunction = (props, ref) => {
47
48
  trimBefore,
48
49
  trimAfter,
49
50
  });
50
- if (loop && durationFetched !== undefined) {
51
- if (!Number.isFinite(durationFetched)) {
51
+ // An explicit trim end defines the loop before metadata has loaded. Waiting
52
+ // for metadata would leave the media unmounted when seeking into later loops.
53
+ const loopDuration = trimAfterValue !== null && trimAfterValue !== void 0 ? trimAfterValue : (durationFetched === undefined ? undefined : durationFetched * fps);
54
+ if (loop && loopDuration !== undefined) {
55
+ if (!Number.isFinite(loopDuration)) {
52
56
  return (jsx_runtime_1.jsx(exports.Html5Video, { ...propsOtherThanLoop, ref: ref, _remotionInternalStack: _remotionInternalStack, _remotionInternalNativeLoopPassed: true }));
53
57
  }
54
- const mediaDuration = durationFetched * fps;
55
58
  return (jsx_runtime_1.jsx(index_js_1.Loop, { durationInFrames: (0, calculate_media_duration_js_1.calculateMediaDuration)({
56
59
  trimAfter: trimAfterValue,
57
- mediaDurationInFrames: mediaDuration,
60
+ mediaDurationInFrames: loopDuration,
58
61
  playbackRate: (_b = props.playbackRate) !== null && _b !== void 0 ? _b : 1,
59
62
  trimBefore: trimBeforeValue,
60
63
  }), layout: "none", name: name, showInTimeline: false, children: jsx_runtime_1.jsx(exports.Html5Video, { ...propsOtherThanLoop, ref: ref, _remotionInternalStack: _remotionInternalStack, _remotionInternalNativeLoopPassed: true }) }));
61
64
  }
62
65
  if (typeof trimBeforeValue !== 'undefined' ||
63
66
  typeof trimAfterValue !== 'undefined') {
64
- return (jsx_runtime_1.jsx(Sequence_js_1.Sequence, { layout: "none", from: 0 - (trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0), showInTimeline: false, durationInFrames: trimAfterValue === undefined
65
- ? undefined
66
- : trimAfterValue / ((_c = props.playbackRate) !== null && _c !== void 0 ? _c : 1), name: name, children: jsx_runtime_1.jsx(exports.Html5Video, { pauseWhenBuffering: shouldPauseWhenBuffering, onVideoFrame: onVideoFrame, ...otherProps, ref: ref, _remotionInternalStack: _remotionInternalStack }) }));
67
+ return (jsx_runtime_1.jsx(use_audio_frame_js_1.Html5MediaTrimContext.Provider, { value: trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0, children: jsx_runtime_1.jsx(Sequence_js_1.Sequence, { layout: "none", from: 0 - (trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0), showInTimeline: false, durationInFrames: trimAfterValue === undefined
68
+ ? undefined
69
+ : (trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0) +
70
+ (trimAfterValue - (trimBeforeValue !== null && trimBeforeValue !== void 0 ? trimBeforeValue : 0)) /
71
+ ((_c = props.playbackRate) !== null && _c !== void 0 ? _c : 1), name: name, children: jsx_runtime_1.jsx(exports.Html5Video, { pauseWhenBuffering: shouldPauseWhenBuffering, onVideoFrame: onVideoFrame, ...otherProps, ref: ref, _remotionInternalStack: _remotionInternalStack }) }) }));
67
72
  }
68
73
  (0, validate_media_props_js_1.validateMediaProps)({
69
74
  playbackRate: props.playbackRate,