remotion 4.0.486 → 4.0.487

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.
@@ -104,8 +104,7 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, c
104
104
  ref(node);
105
105
  }
106
106
  else if (ref) {
107
- ref.current =
108
- node;
107
+ ref.current = node;
109
108
  }
110
109
  }, [ref]);
111
110
  const chainState = (0, use_effect_chain_state_js_1.useEffectChainState)();
@@ -319,8 +318,7 @@ const HtmlInCanvasInner = (0, react_1.forwardRef)(({ width, height, effects = []
319
318
  ref(node);
320
319
  }
321
320
  else if (ref) {
322
- ref.current =
323
- node;
321
+ ref.current = node;
324
322
  }
325
323
  }, [ref]);
326
324
  return (jsx_runtime_1.jsx(Sequence_js_1.Sequence, { durationInFrames: resolvedDuration, name: name !== null && name !== void 0 ? name : '<HtmlInCanvas>', _remotionInternalDocumentationLink: "https://www.remotion.dev/docs/remotion/html-in-canvas", controls: controls, _remotionInternalEffects: memoizedEffectDefinitions, outlineRef: actualRef, layout: "none", ...sequenceProps, children: jsx_runtime_1.jsx(HtmlInCanvasContent, { ref: setCanvasRef, width: width, height: height, effects: effects, onPaint: onPaint, onInit: onInit, pixelDensity: pixelDensity, controls: controls, style: style, children: children }) }));
package/dist/cjs/Still.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Still = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const Composition_js_1 = require("./Composition.js");
9
+ const enable_sequence_stack_traces_js_1 = require("./enable-sequence-stack-traces.js");
9
10
  /*
10
11
  * @description A `<Still />` is a `<Composition />` that is only 1 frame long.
11
12
  * @see [Documentation](https://www.remotion.dev/docs/still)
@@ -20,3 +21,4 @@ const Still = (props) => {
20
21
  return react_1.default.createElement(Composition_js_1.Composition, newProps);
21
22
  };
22
23
  exports.Still = Still;
24
+ (0, enable_sequence_stack_traces_js_1.addSequenceStackTraces)(exports.Still);
@@ -1,12 +1,12 @@
1
- import type { MutableRefObject } from 'react';
1
+ import type { RefObject } from 'react';
2
2
  import React from 'react';
3
3
  import { type PlayableMediaTag } from './timeline-position-state';
4
4
  export type TimelineContextValue = {
5
5
  frame: Record<string, number>;
6
6
  playing: boolean;
7
7
  rootId: string;
8
- imperativePlaying: MutableRefObject<boolean>;
9
- audioAndVideoTags: MutableRefObject<PlayableMediaTag[]>;
8
+ imperativePlaying: RefObject<boolean>;
9
+ audioAndVideoTags: RefObject<PlayableMediaTag[]>;
10
10
  };
11
11
  export type PlaybackRateContextValue = {
12
12
  playbackRate: number;
@@ -17,7 +17,7 @@ type BufferManager = {
17
17
  addBlock: AddBlock;
18
18
  listenForBuffering: ListenForBuffering;
19
19
  listenForResume: ListenForResume;
20
- buffering: React.MutableRefObject<boolean>;
20
+ buffering: React.RefObject<boolean>;
21
21
  };
22
22
  export declare const BufferingContextReact: React.Context<BufferManager | null>;
23
23
  export declare const BufferingProvider: React.FC<{
@@ -90,7 +90,11 @@ const useBufferManager = (logLevel, mountTime) => {
90
90
  if (rendering) {
91
91
  return;
92
92
  }
93
- if (blocks.length > 0) {
93
+ // Only fire on the `false -> true` transition: adding a block while
94
+ // already buffering (e.g. a second media element starts loading) must
95
+ // not re-dispatch `waiting` to listeners.
96
+ if (blocks.length > 0 && !buffering.current) {
97
+ buffering.current = true;
94
98
  onBufferingCallbacks.forEach((c) => c());
95
99
  (0, playback_logging_1.playbackLogging)({
96
100
  logLevel,
@@ -110,7 +114,11 @@ const useBufferManager = (logLevel, mountTime) => {
110
114
  if (rendering) {
111
115
  return;
112
116
  }
113
- if (blocks.length === 0) {
117
+ // Only fire on the `true -> false` transition: the initial mount and
118
+ // a block that was added and removed within the same commit must not
119
+ // dispatch `resume` to listeners.
120
+ if (blocks.length === 0 && buffering.current) {
121
+ buffering.current = false;
114
122
  onResumeCallbacks.forEach((c) => c());
115
123
  (0, playback_logging_1.playbackLogging)({
116
124
  logLevel,
@@ -145,11 +153,11 @@ const useIsPlayerBuffering = (bufferManager) => {
145
153
  const onResume = () => {
146
154
  setIsBuffering(false);
147
155
  };
148
- bufferManager.listenForBuffering(onBuffer);
149
- bufferManager.listenForResume(onResume);
156
+ const buffer = bufferManager.listenForBuffering(onBuffer);
157
+ const resume = bufferManager.listenForResume(onResume);
150
158
  return () => {
151
- bufferManager.listenForBuffering(() => undefined);
152
- bufferManager.listenForResume(() => undefined);
159
+ buffer.remove();
160
+ resume.remove();
153
161
  };
154
162
  }, [bufferManager]);
155
163
  return isBuffering;
@@ -0,0 +1,44 @@
1
+ export type MediaSyncAction = {
2
+ type: 'seek-due-to-shift';
3
+ shouldBeTime: number;
4
+ why: string;
5
+ bufferUntilFirstFrame: boolean;
6
+ playReason: string | null;
7
+ warnAboutNonSeekable: boolean;
8
+ } | {
9
+ type: 'seek-if-not-playing';
10
+ shouldBeTime: number;
11
+ why: string | null;
12
+ } | {
13
+ type: 'play-and-seek';
14
+ shouldBeTime: number;
15
+ why: string | null;
16
+ playReason: string;
17
+ bufferUntilFirstFrame: boolean;
18
+ } | {
19
+ type: 'none';
20
+ };
21
+ export type GetMediaSyncActionInput = {
22
+ duration: number;
23
+ currentTime: number;
24
+ paused: boolean;
25
+ ended: boolean;
26
+ desiredUnclampedTime: number;
27
+ mediaTagTime: number;
28
+ mediaTagLastUpdate: number;
29
+ rvcTime: number | null;
30
+ rvcLastUpdate: number | null;
31
+ isVariableFpsVideo: boolean;
32
+ acceptableTimeShift: number;
33
+ lastSeekDueToShift: number | null;
34
+ playing: boolean;
35
+ playbackRate: number;
36
+ mediaTagBufferingOrStalled: boolean;
37
+ playerBuffering: boolean;
38
+ absoluteFrame: number;
39
+ onlyWarnForMediaSeekingError: boolean;
40
+ isPremounting: boolean;
41
+ isPostmounting: boolean;
42
+ pauseWhenBuffering: boolean;
43
+ };
44
+ export declare const getMediaSyncAction: (input: GetMediaSyncActionInput) => MediaSyncAction;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ // Pure decision logic for `useMediaPlayback`.
3
+ //
4
+ // Every frame, the media element's clock has to be reconciled with the
5
+ // timeline clock. This function takes a snapshot of the relevant state and
6
+ // decides *what* should happen (seek / play / buffer / nothing). The effect in
7
+ // `use-media-playback.ts` is responsible for actually executing the returned
8
+ // action. Keeping the decision separate makes the branch ordering testable
9
+ // without a DOM or a real media element.
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.getMediaSyncAction = void 0;
12
+ const getMediaSyncAction = (input) => {
13
+ const { duration, currentTime, paused, ended, desiredUnclampedTime, mediaTagTime, mediaTagLastUpdate, rvcTime, rvcLastUpdate, isVariableFpsVideo, acceptableTimeShift, lastSeekDueToShift, playing, playbackRate, mediaTagBufferingOrStalled, playerBuffering, absoluteFrame, onlyWarnForMediaSeekingError, isPremounting, isPostmounting, pauseWhenBuffering, } = input;
14
+ const shouldBeTime = !Number.isNaN(duration) && Number.isFinite(duration)
15
+ ? Math.min(duration, desiredUnclampedTime)
16
+ : desiredUnclampedTime;
17
+ const timeShiftMediaTag = Math.abs(shouldBeTime - mediaTagTime);
18
+ const timeShiftRvcTag = rvcTime ? Math.abs(shouldBeTime - rvcTime) : null;
19
+ const mostRecentTimeshift = rvcLastUpdate && rvcTime > mediaTagLastUpdate
20
+ ? timeShiftRvcTag
21
+ : timeShiftMediaTag;
22
+ const timeShift = timeShiftRvcTag && !isVariableFpsVideo
23
+ ? mostRecentTimeshift
24
+ : timeShiftMediaTag;
25
+ if (timeShift > acceptableTimeShift && lastSeekDueToShift !== shouldBeTime) {
26
+ // If scrubbing around, adjust timing
27
+ // or if time shift is bigger than 0.45sec
28
+ return {
29
+ type: 'seek-due-to-shift',
30
+ shouldBeTime,
31
+ why: `because time shift is too big. shouldBeTime = ${shouldBeTime}, isTime = ${mediaTagTime}, requestVideoCallbackTime = ${rvcTime}, timeShift = ${timeShift}${isVariableFpsVideo ? ', isVariableFpsVideo = true' : ''}, isPremounting = ${isPremounting}, isPostmounting = ${isPostmounting}, pauseWhenBuffering = ${pauseWhenBuffering}`,
32
+ bufferUntilFirstFrame: playing && playbackRate > 0,
33
+ playReason: playing && paused
34
+ ? 'player is playing but media tag is paused, and just seeked'
35
+ : null,
36
+ warnAboutNonSeekable: !onlyWarnForMediaSeekingError,
37
+ };
38
+ }
39
+ const seekThreshold = playing ? 0.15 : 0.01;
40
+ // Only perform a seek if the time is not already the same.
41
+ // Chrome rounds to 6 digits, so 0.033333333 -> 0.033333,
42
+ // therefore a threshold is allowed.
43
+ // Refer to the https://github.com/remotion-dev/video-buffering-example
44
+ // which is fixed by only seeking conditionally.
45
+ const makesSenseToSeek = Math.abs(currentTime - shouldBeTime) > seekThreshold;
46
+ const isSomethingElseBuffering = playerBuffering && !mediaTagBufferingOrStalled;
47
+ if (!playing || isSomethingElseBuffering) {
48
+ return {
49
+ type: 'seek-if-not-playing',
50
+ shouldBeTime,
51
+ why: makesSenseToSeek
52
+ ? `not playing or something else is buffering. time offset is over seek threshold (${seekThreshold})`
53
+ : null,
54
+ };
55
+ }
56
+ if (!playing || playerBuffering) {
57
+ return { type: 'none' };
58
+ }
59
+ // We now assured we are in playing state and not buffering
60
+ const pausedCondition = paused && !ended;
61
+ const firstFrameCondition = absoluteFrame === 0;
62
+ if (pausedCondition || firstFrameCondition) {
63
+ const reason = pausedCondition
64
+ ? 'media tag is paused'
65
+ : 'absolute frame is 0';
66
+ return {
67
+ type: 'play-and-seek',
68
+ shouldBeTime,
69
+ why: makesSenseToSeek
70
+ ? `is over timeshift threshold (threshold = ${seekThreshold}) and ${reason}`
71
+ : null,
72
+ playReason: `player is playing and ${reason}`,
73
+ bufferUntilFirstFrame: !isVariableFpsVideo && playbackRate > 0,
74
+ };
75
+ }
76
+ return { type: 'none' };
77
+ };
78
+ exports.getMediaSyncAction = getMediaSyncAction;
@@ -694,7 +694,7 @@ export declare const Internals: {
694
694
  listenForResume: (callback: () => void) => {
695
695
  remove: () => void;
696
696
  };
697
- buffering: import("react").MutableRefObject<boolean>;
697
+ buffering: import("react").RefObject<boolean>;
698
698
  } | null>;
699
699
  readonly getComponentsToAddStacksTo: () => unknown[];
700
700
  readonly CurrentScaleContext: import("react").Context<import("./use-current-scale.js").CurrentScaleContextType | null>;
@@ -823,7 +823,7 @@ export declare const Internals: {
823
823
  listenForResume: (callback: () => void) => {
824
824
  remove: () => void;
825
825
  };
826
- buffering: import("react").MutableRefObject<boolean>;
826
+ buffering: import("react").RefObject<boolean>;
827
827
  }) => boolean;
828
828
  readonly TimelinePosition: typeof TimelinePosition;
829
829
  readonly DelayRenderContextType: import("react").Context<import("./delay-render.js").DelayRenderScope | null>;
@@ -1,4 +1,4 @@
1
- import type { MutableRefObject } from 'react';
1
+ import type { RefObject } from 'react';
2
2
  import { type PlaybackRateContextValue, type TimelineContextValue } from './TimelineContext.js';
3
3
  export type PlayableMediaTag = {
4
4
  play: (reason: string) => void;
@@ -17,7 +17,7 @@ export declare const useTimelineSetFrame: () => (u: import("react").SetStateActi
17
17
  type PlayingReturnType = readonly [
18
18
  boolean,
19
19
  (u: React.SetStateAction<boolean>) => void,
20
- MutableRefObject<boolean>
20
+ RefObject<boolean>
21
21
  ];
22
22
  export declare const usePlayingState: () => PlayingReturnType;
23
23
  export {};
@@ -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 buffer_until_first_frame_js_1 = require("./buffer-until-first-frame.js");
7
7
  const buffering_js_1 = require("./buffering.js");
8
+ const get_media_sync_action_js_1 = require("./get-media-sync-action.js");
8
9
  const log_level_context_js_1 = require("./log-level-context.js");
9
10
  const log_js_1 = require("./log.js");
10
11
  const media_tag_current_time_timestamp_js_1 = require("./media-tag-current-time-timestamp.js");
@@ -144,7 +145,7 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
144
145
  }, [mediaRef, playbackRate, preservePitch]);
145
146
  (0, react_1.useEffect)(() => {
146
147
  var _a, _b;
147
- var _c;
148
+ var _c, _d;
148
149
  const tagName = mediaType === 'audio' ? '<Html5Audio>' : '<Html5Video>';
149
150
  if (!mediaRef.current) {
150
151
  throw new Error(`No ${mediaType} ref found`);
@@ -152,107 +153,94 @@ const useMediaPlayback = ({ mediaRef, src, mediaType, playbackRate: localPlaybac
152
153
  if (!src) {
153
154
  throw new Error(`No 'src' attribute was passed to the ${tagName} element.`);
154
155
  }
155
- const { duration } = mediaRef.current;
156
- const shouldBeTime = !Number.isNaN(duration) && Number.isFinite(duration)
157
- ? Math.min(duration, desiredUnclampedTime)
158
- : desiredUnclampedTime;
159
- const mediaTagTime = mediaTagCurrentTime.current.time;
160
- const rvcTime = (_c = (_a = rvcCurrentTime.current) === null || _a === void 0 ? void 0 : _a.time) !== null && _c !== void 0 ? _c : null;
161
- const isVariableFpsVideo = isVariableFpsVideoMap.current[src];
162
- const timeShiftMediaTag = Math.abs(shouldBeTime - mediaTagTime);
163
- const timeShiftRvcTag = rvcTime ? Math.abs(shouldBeTime - rvcTime) : null;
164
- const mostRecentTimeshift = ((_b = rvcCurrentTime.current) === null || _b === void 0 ? void 0 : _b.lastUpdate) &&
165
- rvcCurrentTime.current.time > mediaTagCurrentTime.current.lastUpdate
166
- ? timeShiftRvcTag
167
- : timeShiftMediaTag;
168
- const timeShift = timeShiftRvcTag && !isVariableFpsVideo
169
- ? mostRecentTimeshift
170
- : timeShiftMediaTag;
171
- if (timeShift > acceptableTimeShiftButLessThanDuration &&
172
- lastSeekDueToShift.current !== shouldBeTime) {
173
- // If scrubbing around, adjust timing
174
- // or if time shift is bigger than 0.45sec
156
+ const { current } = mediaRef;
157
+ const action = (0, get_media_sync_action_js_1.getMediaSyncAction)({
158
+ duration: current.duration,
159
+ currentTime: current.currentTime,
160
+ paused: current.paused,
161
+ ended: current.ended,
162
+ desiredUnclampedTime,
163
+ mediaTagTime: mediaTagCurrentTime.current.time,
164
+ mediaTagLastUpdate: mediaTagCurrentTime.current.lastUpdate,
165
+ rvcTime: (_c = (_a = rvcCurrentTime.current) === null || _a === void 0 ? void 0 : _a.time) !== null && _c !== void 0 ? _c : null,
166
+ rvcLastUpdate: (_d = (_b = rvcCurrentTime.current) === null || _b === void 0 ? void 0 : _b.lastUpdate) !== null && _d !== void 0 ? _d : null,
167
+ isVariableFpsVideo: Boolean(isVariableFpsVideoMap.current[src]),
168
+ acceptableTimeShift: acceptableTimeShiftButLessThanDuration,
169
+ lastSeekDueToShift: lastSeekDueToShift.current,
170
+ playing,
171
+ playbackRate,
172
+ mediaTagBufferingOrStalled: isMediaTagBuffering || isBuffering(),
173
+ playerBuffering: buffering.buffering.current,
174
+ absoluteFrame,
175
+ onlyWarnForMediaSeekingError,
176
+ isPremounting,
177
+ isPostmounting,
178
+ pauseWhenBuffering,
179
+ });
180
+ if (action.type === 'none') {
181
+ return;
182
+ }
183
+ if (action.type === 'seek-due-to-shift') {
175
184
  lastSeek.current = (0, seek_js_1.seek)({
176
- mediaRef: mediaRef.current,
177
- time: shouldBeTime,
185
+ mediaRef: current,
186
+ time: action.shouldBeTime,
178
187
  logLevel,
179
- why: `because time shift is too big. shouldBeTime = ${shouldBeTime}, isTime = ${mediaTagTime}, requestVideoCallbackTime = ${rvcTime}, timeShift = ${timeShift}${isVariableFpsVideo ? ', isVariableFpsVideo = true' : ''}, isPremounting = ${isPremounting}, isPostmounting = ${isPostmounting}, pauseWhenBuffering = ${pauseWhenBuffering}`,
188
+ why: action.why,
180
189
  mountTime,
181
190
  });
182
191
  lastSeekDueToShift.current = lastSeek.current;
183
- if (playing) {
184
- if (playbackRate > 0) {
185
- bufferUntilFirstFrame(shouldBeTime);
186
- }
187
- if (mediaRef.current.paused) {
188
- (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)({
189
- mediaRef,
190
- mediaType,
191
- onAutoPlayError,
192
- logLevel,
193
- mountTime,
194
- reason: 'player is playing but media tag is paused, and just seeked',
195
- isPlayer: env.isPlayer,
196
- });
197
- }
192
+ if (action.bufferUntilFirstFrame) {
193
+ bufferUntilFirstFrame(action.shouldBeTime);
198
194
  }
199
- if (!onlyWarnForMediaSeekingError) {
200
- (0, warn_about_non_seekable_media_js_1.warnAboutNonSeekableMedia)(mediaRef.current, onlyWarnForMediaSeekingError ? 'console-warning' : 'console-error');
201
- }
202
- return;
203
- }
204
- const seekThreshold = playing ? 0.15 : 0.01;
205
- // Only perform a seek if the time is not already the same.
206
- // Chrome rounds to 6 digits, so 0.033333333 -> 0.033333,
207
- // therefore a threshold is allowed.
208
- // Refer to the https://github.com/remotion-dev/video-buffering-example
209
- // which is fixed by only seeking conditionally.
210
- const makesSenseToSeek = Math.abs(mediaRef.current.currentTime - shouldBeTime) > seekThreshold;
211
- const isMediaTagBufferingOrStalled = isMediaTagBuffering || isBuffering();
212
- const isSomethingElseBuffering = buffering.buffering.current && !isMediaTagBufferingOrStalled;
213
- if (!playing || isSomethingElseBuffering) {
214
- if (makesSenseToSeek) {
215
- lastSeek.current = (0, seek_js_1.seek)({
216
- mediaRef: mediaRef.current,
217
- time: shouldBeTime,
195
+ if (action.playReason !== null) {
196
+ (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)({
197
+ mediaRef,
198
+ mediaType,
199
+ onAutoPlayError,
218
200
  logLevel,
219
- why: `not playing or something else is buffering. time offset is over seek threshold (${seekThreshold})`,
220
201
  mountTime,
202
+ reason: action.playReason,
203
+ isPlayer: env.isPlayer,
221
204
  });
222
205
  }
206
+ if (action.warnAboutNonSeekable) {
207
+ (0, warn_about_non_seekable_media_js_1.warnAboutNonSeekableMedia)(current, 'console-error');
208
+ }
223
209
  return;
224
210
  }
225
- if (!playing || buffering.buffering.current) {
226
- return;
227
- }
228
- // We now assured we are in playing state and not buffering
229
- const pausedCondition = mediaRef.current.paused && !mediaRef.current.ended;
230
- const firstFrameCondition = absoluteFrame === 0;
231
- if (pausedCondition || firstFrameCondition) {
232
- const reason = pausedCondition
233
- ? 'media tag is paused'
234
- : 'absolute frame is 0';
235
- if (makesSenseToSeek) {
211
+ if (action.type === 'seek-if-not-playing') {
212
+ if (action.why !== null) {
236
213
  lastSeek.current = (0, seek_js_1.seek)({
237
- mediaRef: mediaRef.current,
238
- time: shouldBeTime,
214
+ mediaRef: current,
215
+ time: action.shouldBeTime,
239
216
  logLevel,
240
- why: `is over timeshift threshold (threshold = ${seekThreshold}) and ${reason}`,
217
+ why: action.why,
241
218
  mountTime,
242
219
  });
243
220
  }
244
- (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)({
245
- mediaRef,
246
- mediaType,
247
- onAutoPlayError,
221
+ return;
222
+ }
223
+ // action.type === 'play-and-seek'
224
+ if (action.why !== null) {
225
+ lastSeek.current = (0, seek_js_1.seek)({
226
+ mediaRef: current,
227
+ time: action.shouldBeTime,
248
228
  logLevel,
229
+ why: action.why,
249
230
  mountTime,
250
- reason: `player is playing and ${reason}`,
251
- isPlayer: env.isPlayer,
252
231
  });
253
- if (!isVariableFpsVideo && playbackRate > 0) {
254
- bufferUntilFirstFrame(shouldBeTime);
255
- }
232
+ }
233
+ (0, play_and_handle_not_allowed_error_js_1.playAndHandleNotAllowedError)({
234
+ mediaRef,
235
+ mediaType,
236
+ onAutoPlayError,
237
+ logLevel,
238
+ mountTime,
239
+ reason: action.playReason,
240
+ isPlayer: env.isPlayer,
241
+ });
242
+ if (action.bufferUntilFirstFrame) {
243
+ bufferUntilFirstFrame(action.shouldBeTime);
256
244
  }
257
245
  }, [
258
246
  absoluteFrame,
@@ -2,7 +2,7 @@ import type { RefObject } from 'react';
2
2
  export declare const useRequestVideoCallbackTime: ({ mediaRef, mediaType, lastSeek, onVariableFpsVideoDetected, }: {
3
3
  mediaRef: RefObject<HTMLAudioElement | HTMLVideoElement | null>;
4
4
  mediaType: "audio" | "video";
5
- lastSeek: import("react").MutableRefObject<number | null>;
5
+ lastSeek: RefObject<number | null>;
6
6
  onVariableFpsVideoDetected: () => void;
7
7
  }) => RefObject<{
8
8
  time: number;
@@ -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.486";
6
+ export declare const VERSION = "4.0.487";
@@ -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.486';
10
+ exports.VERSION = '4.0.487';
@@ -39,7 +39,7 @@ export declare function useRemotionContexts(): {
39
39
  listenForResume: (callback: () => void) => {
40
40
  remove: () => void;
41
41
  };
42
- buffering: React.MutableRefObject<boolean>;
42
+ buffering: React.RefObject<boolean>;
43
43
  } | null;
44
44
  logLevelContext: import("./log-level-context.js").LoggingContextValue;
45
45
  };
@@ -1291,7 +1291,7 @@ var addSequenceStackTraces = (component) => {
1291
1291
  };
1292
1292
 
1293
1293
  // src/version.ts
1294
- var VERSION = "4.0.486";
1294
+ var VERSION = "4.0.487";
1295
1295
 
1296
1296
  // src/multiple-versions-warning.ts
1297
1297
  var checkMultipleRemotionVersions = () => {
@@ -7458,7 +7458,8 @@ var useBufferManager = (logLevel, mountTime) => {
7458
7458
  if (rendering) {
7459
7459
  return;
7460
7460
  }
7461
- if (blocks.length > 0) {
7461
+ if (blocks.length > 0 && !buffering.current) {
7462
+ buffering.current = true;
7462
7463
  onBufferingCallbacks.forEach((c2) => c2());
7463
7464
  playbackLogging({
7464
7465
  logLevel,
@@ -7473,7 +7474,8 @@ var useBufferManager = (logLevel, mountTime) => {
7473
7474
  if (rendering) {
7474
7475
  return;
7475
7476
  }
7476
- if (blocks.length === 0) {
7477
+ if (blocks.length === 0 && buffering.current) {
7478
+ buffering.current = false;
7477
7479
  onResumeCallbacks.forEach((c2) => c2());
7478
7480
  playbackLogging({
7479
7481
  logLevel,
@@ -7506,15 +7508,11 @@ var useIsPlayerBuffering = (bufferManager) => {
7506
7508
  const onResume = () => {
7507
7509
  setIsBuffering(false);
7508
7510
  };
7509
- bufferManager.listenForBuffering(onBuffer);
7510
- bufferManager.listenForResume(onResume);
7511
+ const buffer = bufferManager.listenForBuffering(onBuffer);
7512
+ const resume = bufferManager.listenForResume(onResume);
7511
7513
  return () => {
7512
- bufferManager.listenForBuffering(() => {
7513
- return;
7514
- });
7515
- bufferManager.listenForResume(() => {
7516
- return;
7517
- });
7514
+ buffer.remove();
7515
+ resume.remove();
7518
7516
  };
7519
7517
  }, [bufferManager]);
7520
7518
  return isBuffering;
@@ -7643,6 +7641,74 @@ var useBufferUntilFirstFrame = ({
7643
7641
  }, [bufferUntilFirstFrame]);
7644
7642
  };
7645
7643
 
7644
+ // src/get-media-sync-action.ts
7645
+ var getMediaSyncAction = (input) => {
7646
+ const {
7647
+ duration,
7648
+ currentTime,
7649
+ paused,
7650
+ ended,
7651
+ desiredUnclampedTime,
7652
+ mediaTagTime,
7653
+ mediaTagLastUpdate,
7654
+ rvcTime,
7655
+ rvcLastUpdate,
7656
+ isVariableFpsVideo,
7657
+ acceptableTimeShift,
7658
+ lastSeekDueToShift,
7659
+ playing,
7660
+ playbackRate,
7661
+ mediaTagBufferingOrStalled,
7662
+ playerBuffering,
7663
+ absoluteFrame,
7664
+ onlyWarnForMediaSeekingError,
7665
+ isPremounting,
7666
+ isPostmounting,
7667
+ pauseWhenBuffering
7668
+ } = input;
7669
+ const shouldBeTime = !Number.isNaN(duration) && Number.isFinite(duration) ? Math.min(duration, desiredUnclampedTime) : desiredUnclampedTime;
7670
+ const timeShiftMediaTag = Math.abs(shouldBeTime - mediaTagTime);
7671
+ const timeShiftRvcTag = rvcTime ? Math.abs(shouldBeTime - rvcTime) : null;
7672
+ const mostRecentTimeshift = rvcLastUpdate && rvcTime > mediaTagLastUpdate ? timeShiftRvcTag : timeShiftMediaTag;
7673
+ const timeShift = timeShiftRvcTag && !isVariableFpsVideo ? mostRecentTimeshift : timeShiftMediaTag;
7674
+ if (timeShift > acceptableTimeShift && lastSeekDueToShift !== shouldBeTime) {
7675
+ return {
7676
+ type: "seek-due-to-shift",
7677
+ shouldBeTime,
7678
+ why: `because time shift is too big. shouldBeTime = ${shouldBeTime}, isTime = ${mediaTagTime}, requestVideoCallbackTime = ${rvcTime}, timeShift = ${timeShift}${isVariableFpsVideo ? ", isVariableFpsVideo = true" : ""}, isPremounting = ${isPremounting}, isPostmounting = ${isPostmounting}, pauseWhenBuffering = ${pauseWhenBuffering}`,
7679
+ bufferUntilFirstFrame: playing && playbackRate > 0,
7680
+ playReason: playing && paused ? "player is playing but media tag is paused, and just seeked" : null,
7681
+ warnAboutNonSeekable: !onlyWarnForMediaSeekingError
7682
+ };
7683
+ }
7684
+ const seekThreshold = playing ? 0.15 : 0.01;
7685
+ const makesSenseToSeek = Math.abs(currentTime - shouldBeTime) > seekThreshold;
7686
+ const isSomethingElseBuffering = playerBuffering && !mediaTagBufferingOrStalled;
7687
+ if (!playing || isSomethingElseBuffering) {
7688
+ return {
7689
+ type: "seek-if-not-playing",
7690
+ shouldBeTime,
7691
+ why: makesSenseToSeek ? `not playing or something else is buffering. time offset is over seek threshold (${seekThreshold})` : null
7692
+ };
7693
+ }
7694
+ if (!playing || playerBuffering) {
7695
+ return { type: "none" };
7696
+ }
7697
+ const pausedCondition = paused && !ended;
7698
+ const firstFrameCondition = absoluteFrame === 0;
7699
+ if (pausedCondition || firstFrameCondition) {
7700
+ const reason = pausedCondition ? "media tag is paused" : "absolute frame is 0";
7701
+ return {
7702
+ type: "play-and-seek",
7703
+ shouldBeTime,
7704
+ why: makesSenseToSeek ? `is over timeshift threshold (threshold = ${seekThreshold}) and ${reason}` : null,
7705
+ playReason: `player is playing and ${reason}`,
7706
+ bufferUntilFirstFrame: !isVariableFpsVideo && playbackRate > 0
7707
+ };
7708
+ }
7709
+ return { type: "none" };
7710
+ };
7711
+
7646
7712
  // src/media-tag-current-time-timestamp.ts
7647
7713
  import React21 from "react";
7648
7714
  var useCurrentTimeOfMediaTagWithUpdateTimeStamp = (mediaRef) => {
@@ -8078,89 +8144,93 @@ var useMediaPlayback = ({
8078
8144
  if (!src) {
8079
8145
  throw new Error(`No 'src' attribute was passed to the ${tagName} element.`);
8080
8146
  }
8081
- const { duration } = mediaRef.current;
8082
- const shouldBeTime = !Number.isNaN(duration) && Number.isFinite(duration) ? Math.min(duration, desiredUnclampedTime) : desiredUnclampedTime;
8083
- const mediaTagTime = mediaTagCurrentTime.current.time;
8084
- const rvcTime = rvcCurrentTime.current?.time ?? null;
8085
- const isVariableFpsVideo = isVariableFpsVideoMap.current[src];
8086
- const timeShiftMediaTag = Math.abs(shouldBeTime - mediaTagTime);
8087
- const timeShiftRvcTag = rvcTime ? Math.abs(shouldBeTime - rvcTime) : null;
8088
- const mostRecentTimeshift = rvcCurrentTime.current?.lastUpdate && rvcCurrentTime.current.time > mediaTagCurrentTime.current.lastUpdate ? timeShiftRvcTag : timeShiftMediaTag;
8089
- const timeShift = timeShiftRvcTag && !isVariableFpsVideo ? mostRecentTimeshift : timeShiftMediaTag;
8090
- if (timeShift > acceptableTimeShiftButLessThanDuration && lastSeekDueToShift.current !== shouldBeTime) {
8147
+ const { current } = mediaRef;
8148
+ const action = getMediaSyncAction({
8149
+ duration: current.duration,
8150
+ currentTime: current.currentTime,
8151
+ paused: current.paused,
8152
+ ended: current.ended,
8153
+ desiredUnclampedTime,
8154
+ mediaTagTime: mediaTagCurrentTime.current.time,
8155
+ mediaTagLastUpdate: mediaTagCurrentTime.current.lastUpdate,
8156
+ rvcTime: rvcCurrentTime.current?.time ?? null,
8157
+ rvcLastUpdate: rvcCurrentTime.current?.lastUpdate ?? null,
8158
+ isVariableFpsVideo: Boolean(isVariableFpsVideoMap.current[src]),
8159
+ acceptableTimeShift: acceptableTimeShiftButLessThanDuration,
8160
+ lastSeekDueToShift: lastSeekDueToShift.current,
8161
+ playing,
8162
+ playbackRate,
8163
+ mediaTagBufferingOrStalled: isMediaTagBuffering || isBuffering(),
8164
+ playerBuffering: buffering.buffering.current,
8165
+ absoluteFrame,
8166
+ onlyWarnForMediaSeekingError,
8167
+ isPremounting,
8168
+ isPostmounting,
8169
+ pauseWhenBuffering
8170
+ });
8171
+ if (action.type === "none") {
8172
+ return;
8173
+ }
8174
+ if (action.type === "seek-due-to-shift") {
8091
8175
  lastSeek.current = seek({
8092
- mediaRef: mediaRef.current,
8093
- time: shouldBeTime,
8176
+ mediaRef: current,
8177
+ time: action.shouldBeTime,
8094
8178
  logLevel,
8095
- why: `because time shift is too big. shouldBeTime = ${shouldBeTime}, isTime = ${mediaTagTime}, requestVideoCallbackTime = ${rvcTime}, timeShift = ${timeShift}${isVariableFpsVideo ? ", isVariableFpsVideo = true" : ""}, isPremounting = ${isPremounting}, isPostmounting = ${isPostmounting}, pauseWhenBuffering = ${pauseWhenBuffering}`,
8179
+ why: action.why,
8096
8180
  mountTime
8097
8181
  });
8098
8182
  lastSeekDueToShift.current = lastSeek.current;
8099
- if (playing) {
8100
- if (playbackRate > 0) {
8101
- bufferUntilFirstFrame(shouldBeTime);
8102
- }
8103
- if (mediaRef.current.paused) {
8104
- playAndHandleNotAllowedError({
8105
- mediaRef,
8106
- mediaType,
8107
- onAutoPlayError,
8108
- logLevel,
8109
- mountTime,
8110
- reason: "player is playing but media tag is paused, and just seeked",
8111
- isPlayer: env.isPlayer
8112
- });
8113
- }
8183
+ if (action.bufferUntilFirstFrame) {
8184
+ bufferUntilFirstFrame(action.shouldBeTime);
8114
8185
  }
8115
- if (!onlyWarnForMediaSeekingError) {
8116
- warnAboutNonSeekableMedia(mediaRef.current, onlyWarnForMediaSeekingError ? "console-warning" : "console-error");
8117
- }
8118
- return;
8119
- }
8120
- const seekThreshold = playing ? 0.15 : 0.01;
8121
- const makesSenseToSeek = Math.abs(mediaRef.current.currentTime - shouldBeTime) > seekThreshold;
8122
- const isMediaTagBufferingOrStalled = isMediaTagBuffering || isBuffering();
8123
- const isSomethingElseBuffering = buffering.buffering.current && !isMediaTagBufferingOrStalled;
8124
- if (!playing || isSomethingElseBuffering) {
8125
- if (makesSenseToSeek) {
8126
- lastSeek.current = seek({
8127
- mediaRef: mediaRef.current,
8128
- time: shouldBeTime,
8186
+ if (action.playReason !== null) {
8187
+ playAndHandleNotAllowedError({
8188
+ mediaRef,
8189
+ mediaType,
8190
+ onAutoPlayError,
8129
8191
  logLevel,
8130
- why: `not playing or something else is buffering. time offset is over seek threshold (${seekThreshold})`,
8131
- mountTime
8192
+ mountTime,
8193
+ reason: action.playReason,
8194
+ isPlayer: env.isPlayer
8132
8195
  });
8133
8196
  }
8197
+ if (action.warnAboutNonSeekable) {
8198
+ warnAboutNonSeekableMedia(current, "console-error");
8199
+ }
8134
8200
  return;
8135
8201
  }
8136
- if (!playing || buffering.buffering.current) {
8137
- return;
8138
- }
8139
- const pausedCondition = mediaRef.current.paused && !mediaRef.current.ended;
8140
- const firstFrameCondition = absoluteFrame === 0;
8141
- if (pausedCondition || firstFrameCondition) {
8142
- const reason = pausedCondition ? "media tag is paused" : "absolute frame is 0";
8143
- if (makesSenseToSeek) {
8202
+ if (action.type === "seek-if-not-playing") {
8203
+ if (action.why !== null) {
8144
8204
  lastSeek.current = seek({
8145
- mediaRef: mediaRef.current,
8146
- time: shouldBeTime,
8205
+ mediaRef: current,
8206
+ time: action.shouldBeTime,
8147
8207
  logLevel,
8148
- why: `is over timeshift threshold (threshold = ${seekThreshold}) and ${reason}`,
8208
+ why: action.why,
8149
8209
  mountTime
8150
8210
  });
8151
8211
  }
8152
- playAndHandleNotAllowedError({
8153
- mediaRef,
8154
- mediaType,
8155
- onAutoPlayError,
8212
+ return;
8213
+ }
8214
+ if (action.why !== null) {
8215
+ lastSeek.current = seek({
8216
+ mediaRef: current,
8217
+ time: action.shouldBeTime,
8156
8218
  logLevel,
8157
- mountTime,
8158
- reason: `player is playing and ${reason}`,
8159
- isPlayer: env.isPlayer
8219
+ why: action.why,
8220
+ mountTime
8160
8221
  });
8161
- if (!isVariableFpsVideo && playbackRate > 0) {
8162
- bufferUntilFirstFrame(shouldBeTime);
8163
- }
8222
+ }
8223
+ playAndHandleNotAllowedError({
8224
+ mediaRef,
8225
+ mediaType,
8226
+ onAutoPlayError,
8227
+ logLevel,
8228
+ mountTime,
8229
+ reason: action.playReason,
8230
+ isPlayer: env.isPlayer
8231
+ });
8232
+ if (action.bufferUntilFirstFrame) {
8233
+ bufferUntilFirstFrame(action.shouldBeTime);
8164
8234
  }
8165
8235
  }, [
8166
8236
  absoluteFrame,
@@ -12009,6 +12079,7 @@ var Still = (props2) => {
12009
12079
  };
12010
12080
  return React42.createElement(Composition, newProps);
12011
12081
  };
12082
+ addSequenceStackTraces(Still);
12012
12083
  // src/video/html5-video.tsx
12013
12084
  import { forwardRef as forwardRef16, useCallback as useCallback25, useContext as useContext39 } from "react";
12014
12085
 
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "4.0.486";
2
+ var VERSION = "4.0.487";
3
3
  export {
4
4
  VERSION
5
5
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/core"
4
4
  },
5
5
  "name": "remotion",
6
- "version": "4.0.486",
6
+ "version": "4.0.487",
7
7
  "description": "Make videos programmatically",
8
8
  "main": "dist/cjs/index.js",
9
9
  "types": "dist/cjs/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  "react-dom": "19.2.3",
36
36
  "webpack": "5.105.0",
37
37
  "zod": "4.3.6",
38
- "@remotion/eslint-config-internal": "4.0.486",
38
+ "@remotion/eslint-config-internal": "4.0.487",
39
39
  "eslint": "9.19.0",
40
40
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
41
41
  },