@remotion/media 4.0.382 → 4.0.384

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.
@@ -10,6 +10,8 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
10
10
  const videoConfig = useUnsafeVideoConfig();
11
11
  const frame = useCurrentFrame();
12
12
  const mediaPlayerRef = useRef(null);
13
+ const initialTrimBeforeRef = useRef(trimBefore);
14
+ const initialTrimAfterRef = useRef(trimAfter);
13
15
  const [mediaPlayerReady, setMediaPlayerReady] = useState(false);
14
16
  const [shouldFallbackToNativeAudio, setShouldFallbackToNativeAudio] = useState(false);
15
17
  const [playing] = Timeline.usePlayingState();
@@ -62,11 +64,11 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
62
64
  trimAfter,
63
65
  trimBefore,
64
66
  });
65
- const buffering = useContext(Internals.BufferingContextReact);
66
- if (!buffering) {
67
+ const bufferingContext = useContext(Internals.BufferingContextReact);
68
+ if (!bufferingContext) {
67
69
  throw new Error('useMediaPlayback must be used inside a <BufferingContext>');
68
70
  }
69
- const isPlayerBuffering = Internals.useIsPlayerBuffering(buffering);
71
+ const isPlayerBuffering = Internals.useIsPlayerBuffering(bufferingContext);
70
72
  useEffect(() => {
71
73
  if (!sharedAudioContext)
72
74
  return;
@@ -78,8 +80,8 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
78
80
  logLevel,
79
81
  sharedAudioContext: sharedAudioContext.audioContext,
80
82
  loop,
81
- trimAfter,
82
- trimBefore,
83
+ trimAfter: initialTrimAfterRef.current,
84
+ trimBefore: initialTrimBeforeRef.current,
83
85
  fps: videoConfig.fps,
84
86
  canvas: null,
85
87
  playbackRate,
@@ -159,8 +161,6 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
159
161
  sharedAudioContext,
160
162
  currentTimeRef,
161
163
  loop,
162
- trimAfter,
163
- trimBefore,
164
164
  playbackRate,
165
165
  videoConfig.fps,
166
166
  audioStreamIndex,
@@ -170,7 +170,7 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
170
170
  isPostmounting,
171
171
  globalPlaybackRate,
172
172
  ]);
173
- useEffect(() => {
173
+ useLayoutEffect(() => {
174
174
  const audioPlayer = mediaPlayerRef.current;
175
175
  if (!audioPlayer)
176
176
  return;
@@ -182,16 +182,21 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
182
182
  }
183
183
  }, [isPlayerBuffering, logLevel, playing]);
184
184
  useLayoutEffect(() => {
185
- const audioPlayer = mediaPlayerRef.current;
186
- if (!audioPlayer || !mediaPlayerReady)
185
+ const mediaPlayer = mediaPlayerRef.current;
186
+ if (!mediaPlayer || !mediaPlayerReady) {
187
187
  return;
188
- audioPlayer.seekTo(currentTime).catch(() => {
189
- // Might be disposed
190
- });
191
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[AudioForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
192
- }, [currentTime, logLevel, mediaPlayerReady]);
188
+ }
189
+ mediaPlayer.setTrimBefore(trimBefore, currentTimeRef.current);
190
+ }, [trimBefore, mediaPlayerReady]);
191
+ useLayoutEffect(() => {
192
+ const mediaPlayer = mediaPlayerRef.current;
193
+ if (!mediaPlayer || !mediaPlayerReady) {
194
+ return;
195
+ }
196
+ mediaPlayer.setTrimAfter(trimAfter, currentTimeRef.current);
197
+ }, [trimAfter, mediaPlayerReady]);
193
198
  const effectiveMuted = muted || mediaMuted || userPreferredVolume <= 0;
194
- useEffect(() => {
199
+ useLayoutEffect(() => {
195
200
  const audioPlayer = mediaPlayerRef.current;
196
201
  if (!audioPlayer || !mediaPlayerReady)
197
202
  return;
@@ -211,55 +216,50 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
211
216
  }
212
217
  audioPlayer.setPlaybackRate(playbackRate);
213
218
  }, [playbackRate, mediaPlayerReady]);
214
- useEffect(() => {
219
+ useLayoutEffect(() => {
215
220
  const audioPlayer = mediaPlayerRef.current;
216
221
  if (!audioPlayer || !mediaPlayerReady) {
217
222
  return;
218
223
  }
219
224
  audioPlayer.setGlobalPlaybackRate(globalPlaybackRate);
220
225
  }, [globalPlaybackRate, mediaPlayerReady]);
221
- useEffect(() => {
226
+ useLayoutEffect(() => {
222
227
  const audioPlayer = mediaPlayerRef.current;
223
228
  if (!audioPlayer || !mediaPlayerReady) {
224
229
  return;
225
230
  }
226
231
  audioPlayer.setFps(videoConfig.fps);
227
232
  }, [videoConfig.fps, mediaPlayerReady]);
228
- useEffect(() => {
229
- const mediaPlayer = mediaPlayerRef.current;
230
- if (!mediaPlayer || !mediaPlayerReady) {
231
- return;
232
- }
233
- mediaPlayer.setTrimBefore(trimBefore);
234
- }, [trimBefore, mediaPlayerReady]);
235
- useEffect(() => {
236
- const mediaPlayer = mediaPlayerRef.current;
237
- if (!mediaPlayer || !mediaPlayerReady) {
238
- return;
239
- }
240
- mediaPlayer.setTrimAfter(trimAfter);
241
- }, [trimAfter, mediaPlayerReady]);
242
- useEffect(() => {
233
+ useLayoutEffect(() => {
243
234
  const mediaPlayer = mediaPlayerRef.current;
244
235
  if (!mediaPlayer || !mediaPlayerReady) {
245
236
  return;
246
237
  }
247
238
  mediaPlayer.setLoop(loop);
248
239
  }, [loop, mediaPlayerReady]);
249
- useEffect(() => {
240
+ useLayoutEffect(() => {
250
241
  const mediaPlayer = mediaPlayerRef.current;
251
242
  if (!mediaPlayer || !mediaPlayerReady) {
252
243
  return;
253
244
  }
254
245
  mediaPlayer.setIsPremounting(isPremounting);
255
246
  }, [isPremounting, mediaPlayerReady]);
256
- useEffect(() => {
247
+ useLayoutEffect(() => {
257
248
  const mediaPlayer = mediaPlayerRef.current;
258
249
  if (!mediaPlayer || !mediaPlayerReady) {
259
250
  return;
260
251
  }
261
252
  mediaPlayer.setIsPostmounting(isPostmounting);
262
253
  }, [isPostmounting, mediaPlayerReady]);
254
+ useLayoutEffect(() => {
255
+ const audioPlayer = mediaPlayerRef.current;
256
+ if (!audioPlayer || !mediaPlayerReady)
257
+ return;
258
+ audioPlayer.seekTo(currentTime).catch(() => {
259
+ // Might be disposed
260
+ });
261
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[AudioForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
262
+ }, [currentTime, logLevel, mediaPlayerReady]);
263
263
  if (shouldFallbackToNativeAudio && !disallowFallbackToHtml5Audio) {
264
264
  return (_jsx(RemotionAudio, { src: src, muted: muted, volume: volume, startFrom: trimBefore, endAt: trimAfter, playbackRate: playbackRate, loopVolumeCurveBehavior: loopVolumeCurveBehavior, name: name, loop: loop, showInTimeline: showInTimeline, stack: stack ?? undefined, toneFrequency: toneFrequency, audioStreamIndex: audioStreamIndex, pauseWhenBuffering: fallbackHtml5AudioProps?.pauseWhenBuffering, crossOrigin: fallbackHtml5AudioProps?.crossOrigin, ...fallbackHtml5AudioProps }));
265
265
  }
@@ -6,7 +6,7 @@ import { applyVolume } from '../convert-audiodata/apply-volume';
6
6
  import { TARGET_SAMPLE_RATE } from '../convert-audiodata/resample-audiodata';
7
7
  import { frameForVolumeProp } from '../looped-frame';
8
8
  import { extractFrameViaBroadcastChannel } from '../video-extraction/extract-frame-via-broadcast-channel';
9
- export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted, loopVolumeCurveBehavior, delayRenderRetries, delayRenderTimeoutInMilliseconds, logLevel, loop, fallbackHtml5AudioProps, audioStreamIndex, showInTimeline, style, name, disallowFallbackToHtml5Audio, toneFrequency, trimAfter, trimBefore, }) => {
9
+ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted, loopVolumeCurveBehavior, delayRenderRetries, delayRenderTimeoutInMilliseconds, logLevel = window.remotion_logLevel ?? 'info', loop, fallbackHtml5AudioProps, audioStreamIndex, showInTimeline, style, name, disallowFallbackToHtml5Audio, toneFrequency, trimAfter, trimBefore, }) => {
10
10
  const frame = useCurrentFrame();
11
11
  const absoluteFrame = Internals.useTimelinePosition();
12
12
  const videoConfig = Internals.useUnsafeVideoConfig();
@@ -25,13 +25,14 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
25
25
  const sequenceContext = useContext(Internals.SequenceContext);
26
26
  // Generate a string that's as unique as possible for this asset
27
27
  // but at the same time the same on all threads
28
- const id = useMemo(() => `media-video-${random(src)}-${sequenceContext?.cumulatedFrom}-${sequenceContext?.relativeFrom}-${sequenceContext?.durationInFrames}`, [
28
+ const id = useMemo(() => `media-audio-${random(src)}-${sequenceContext?.cumulatedFrom}-${sequenceContext?.relativeFrom}-${sequenceContext?.durationInFrames}`, [
29
29
  src,
30
30
  sequenceContext?.cumulatedFrom,
31
31
  sequenceContext?.relativeFrom,
32
32
  sequenceContext?.durationInFrames,
33
33
  ]);
34
34
  const maxCacheSize = useMaxMediaCacheSize(logLevel ?? window.remotion_logLevel);
35
+ const audioEnabled = Internals.useAudioEnabled();
35
36
  useLayoutEffect(() => {
36
37
  const timestamp = frame / fps;
37
38
  const durationInSeconds = 1 / fps;
@@ -43,7 +44,7 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
43
44
  timeoutInMilliseconds: delayRenderTimeoutInMilliseconds ?? undefined,
44
45
  });
45
46
  const shouldRenderAudio = (() => {
46
- if (!window.remotion_audioEnabled) {
47
+ if (!audioEnabled) {
47
48
  return false;
48
49
  }
49
50
  if (muted) {
@@ -124,7 +125,9 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
124
125
  registerRenderAsset({
125
126
  type: 'inline-audio',
126
127
  id,
127
- audio: Array.from(audio.data),
128
+ audio: environment.isClientSideRendering
129
+ ? audio.data
130
+ : Array.from(audio.data),
128
131
  frame: absoluteFrame,
129
132
  timestamp: audio.timestamp,
130
133
  duration: (audio.numberOfFrames / TARGET_SAMPLE_RATE) * 1000000,
@@ -167,6 +170,7 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
167
170
  trimBefore,
168
171
  replaceWithHtml5Audio,
169
172
  maxCacheSize,
173
+ audioEnabled,
170
174
  ]);
171
175
  if (replaceWithHtml5Audio) {
172
176
  return (_jsx(Html5Audio, { src: src, playbackRate: playbackRate, muted: muted, loop: loop, volume: volumeProp, delayRenderRetries: delayRenderRetries, delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds, style: style, loopVolumeCurveBehavior: loopVolumeCurveBehavior, audioStreamIndex: audioStreamIndex, useWebAudioApi: fallbackHtml5AudioProps?.useWebAudioApi, onError: fallbackHtml5AudioProps?.onError, toneFrequency: toneFrequency, acceptableTimeShiftInSeconds: fallbackHtml5AudioProps?.acceptableTimeShiftInSeconds, name: name, showInTimeline: showInTimeline }));
@@ -4,6 +4,7 @@ import { convertAudioData, fixFloatingPoint, } from '../convert-audiodata/conver
4
4
  import { TARGET_NUMBER_OF_CHANNELS, TARGET_SAMPLE_RATE, } from '../convert-audiodata/resample-audiodata';
5
5
  import { getSink } from '../get-sink';
6
6
  import { getTimeInSeconds } from '../get-time-in-seconds';
7
+ import { isNetworkError, isUnsupportedConfigurationError, } from '../is-type-of-error';
7
8
  const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds: durationNotYetApplyingPlaybackRate, logLevel, loop, playbackRate, audioStreamIndex, trimBefore, trimAfter, fps, maxCacheSize, }) => {
8
9
  const { getAudio, actualMatroskaTimestamps, isMatroska, getDuration } = await getSink(src, logLevel);
9
10
  let mediaDurationInSeconds = null;
@@ -47,70 +48,82 @@ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
47
48
  maxCacheSize,
48
49
  });
49
50
  const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
50
- const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
51
- audioManager.logOpenFrames();
52
- const audioDataArray = [];
53
- for (let i = 0; i < samples.length; i++) {
54
- const sample = samples[i];
55
- // Less than 1 sample would be included - we did not need it after all!
56
- if (Math.abs(sample.timestamp - (timeInSeconds + durationInSeconds)) *
57
- sample.sampleRate <
58
- 1) {
59
- continue;
60
- }
61
- // Less than 1 sample would be included - we did not need it after all!
62
- if (sample.timestamp + sample.duration <= timeInSeconds) {
63
- continue;
64
- }
65
- const isFirstSample = i === 0;
66
- const isLastSample = i === samples.length - 1;
67
- const audioDataRaw = sample.toAudioData();
68
- // amount of samples to shave from start and end
69
- let trimStartInSeconds = 0;
70
- let trimEndInSeconds = 0;
71
- let leadingSilence = null;
72
- if (isFirstSample) {
73
- trimStartInSeconds = fixFloatingPoint(timeInSeconds - sample.timestamp);
74
- if (trimStartInSeconds < 0) {
75
- const silenceFrames = Math.ceil(fixFloatingPoint(-trimStartInSeconds * TARGET_SAMPLE_RATE));
76
- leadingSilence = {
77
- data: new Int16Array(silenceFrames * TARGET_NUMBER_OF_CHANNELS),
78
- numberOfFrames: silenceFrames,
79
- timestamp: timeInSeconds * 1000000,
80
- durationInMicroSeconds: (silenceFrames / TARGET_SAMPLE_RATE) * 1000000,
81
- };
82
- trimStartInSeconds = 0;
51
+ try {
52
+ const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
53
+ audioManager.logOpenFrames();
54
+ const audioDataArray = [];
55
+ for (let i = 0; i < samples.length; i++) {
56
+ const sample = samples[i];
57
+ // Less than 1 sample would be included - we did not need it after all!
58
+ if (Math.abs(sample.timestamp - (timeInSeconds + durationInSeconds)) *
59
+ sample.sampleRate <
60
+ 1) {
61
+ continue;
62
+ }
63
+ // Less than 1 sample would be included - we did not need it after all!
64
+ if (sample.timestamp + sample.duration <= timeInSeconds) {
65
+ continue;
66
+ }
67
+ const isFirstSample = i === 0;
68
+ const isLastSample = i === samples.length - 1;
69
+ const audioDataRaw = sample.toAudioData();
70
+ // amount of samples to shave from start and end
71
+ let trimStartInSeconds = 0;
72
+ let trimEndInSeconds = 0;
73
+ let leadingSilence = null;
74
+ if (isFirstSample) {
75
+ trimStartInSeconds = fixFloatingPoint(timeInSeconds - sample.timestamp);
76
+ if (trimStartInSeconds < 0) {
77
+ const silenceFrames = Math.ceil(fixFloatingPoint(-trimStartInSeconds * TARGET_SAMPLE_RATE));
78
+ leadingSilence = {
79
+ data: new Int16Array(silenceFrames * TARGET_NUMBER_OF_CHANNELS),
80
+ numberOfFrames: silenceFrames,
81
+ timestamp: timeInSeconds * 1000000,
82
+ durationInMicroSeconds: (silenceFrames / TARGET_SAMPLE_RATE) * 1000000,
83
+ };
84
+ trimStartInSeconds = 0;
85
+ }
86
+ }
87
+ if (isLastSample) {
88
+ trimEndInSeconds =
89
+ // clamp to 0 in case the audio ends early
90
+ Math.max(0, sample.timestamp +
91
+ sample.duration -
92
+ (timeInSeconds + durationInSeconds));
83
93
  }
94
+ const audioData = convertAudioData({
95
+ audioData: audioDataRaw,
96
+ trimStartInSeconds,
97
+ trimEndInSeconds,
98
+ playbackRate,
99
+ audioDataTimestamp: sample.timestamp,
100
+ isLast: isLastSample,
101
+ });
102
+ audioDataRaw.close();
103
+ if (audioData.numberOfFrames === 0) {
104
+ continue;
105
+ }
106
+ if (leadingSilence) {
107
+ audioDataArray.push(leadingSilence);
108
+ }
109
+ audioDataArray.push(audioData);
84
110
  }
85
- if (isLastSample) {
86
- trimEndInSeconds =
87
- // clamp to 0 in case the audio ends early
88
- Math.max(0, sample.timestamp +
89
- sample.duration -
90
- (timeInSeconds + durationInSeconds));
111
+ if (audioDataArray.length === 0) {
112
+ return { data: null, durationInSeconds: mediaDurationInSeconds };
91
113
  }
92
- const audioData = convertAudioData({
93
- audioData: audioDataRaw,
94
- trimStartInSeconds,
95
- trimEndInSeconds,
96
- playbackRate,
97
- audioDataTimestamp: sample.timestamp,
98
- isLast: isLastSample,
99
- });
100
- audioDataRaw.close();
101
- if (audioData.numberOfFrames === 0) {
102
- continue;
114
+ const combined = combineAudioDataAndClosePrevious(audioDataArray);
115
+ return { data: combined, durationInSeconds: mediaDurationInSeconds };
116
+ }
117
+ catch (err) {
118
+ const error = err;
119
+ if (isNetworkError(error)) {
120
+ return 'network-error';
103
121
  }
104
- if (leadingSilence) {
105
- audioDataArray.push(leadingSilence);
122
+ if (isUnsupportedConfigurationError(error)) {
123
+ return 'cannot-decode';
106
124
  }
107
- audioDataArray.push(audioData);
108
- }
109
- if (audioDataArray.length === 0) {
110
- return { data: null, durationInSeconds: mediaDurationInSeconds };
125
+ throw err;
111
126
  }
112
- const combined = combineAudioDataAndClosePrevious(audioDataArray);
113
- return { data: combined, durationInSeconds: mediaDurationInSeconds };
114
127
  };
115
128
  let queue = Promise.resolve(undefined);
116
129
  export const extractAudio = (params) => {
@@ -46,7 +46,7 @@ export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfN
46
46
  moveQueuedChunksToPauseQueue: () => void;
47
47
  getNumberOfChunksAfterResuming: () => number;
48
48
  } | null;
49
- destroy: () => void;
49
+ destroyIterator: () => void;
50
50
  seek: ({ newTime, nonce, fps, playbackRate, getIsPlaying, scheduleAudioNode, bufferState, }: {
51
51
  newTime: number;
52
52
  nonce: Nonce;
@@ -102,7 +102,8 @@ export const audioIteratorManager = ({ audioTrack, delayPlaybackHandleIfNotPremo
102
102
  });
103
103
  return;
104
104
  }
105
- const currentTimeIsAlreadyQueued = isAlreadyQueued(newTime, audioBufferIterator.getQueuedPeriod());
105
+ const queuedPeriod = audioBufferIterator.getQueuedPeriod();
106
+ const currentTimeIsAlreadyQueued = isAlreadyQueued(newTime, queuedPeriod);
106
107
  if (!currentTimeIsAlreadyQueued) {
107
108
  const audioSatisfyResult = await audioBufferIterator.tryToSatisfySeek(newTime, null, (buffer) => {
108
109
  if (!nonce.isStale()) {
@@ -192,7 +193,7 @@ export const audioIteratorManager = ({ audioTrack, delayPlaybackHandleIfNotPremo
192
193
  resumeScheduledAudioChunks,
193
194
  pausePlayback,
194
195
  getAudioBufferIterator: () => audioBufferIterator,
195
- destroy: () => {
196
+ destroyIterator: () => {
196
197
  audioBufferIterator?.destroy();
197
198
  audioBufferIterator = null;
198
199
  },
@@ -377,7 +377,8 @@ var audioIteratorManager = ({
377
377
  });
378
378
  return;
379
379
  }
380
- const currentTimeIsAlreadyQueued = isAlreadyQueued(newTime, audioBufferIterator.getQueuedPeriod());
380
+ const queuedPeriod = audioBufferIterator.getQueuedPeriod();
381
+ const currentTimeIsAlreadyQueued = isAlreadyQueued(newTime, queuedPeriod);
381
382
  if (!currentTimeIsAlreadyQueued) {
382
383
  const audioSatisfyResult = await audioBufferIterator.tryToSatisfySeek(newTime, null, (buffer) => {
383
384
  if (!nonce.isStale()) {
@@ -465,7 +466,7 @@ var audioIteratorManager = ({
465
466
  resumeScheduledAudioChunks,
466
467
  pausePlayback,
467
468
  getAudioBufferIterator: () => audioBufferIterator,
468
- destroy: () => {
469
+ destroyIterator: () => {
469
470
  audioBufferIterator?.destroy();
470
471
  audioBufferIterator = null;
471
472
  },
@@ -538,13 +539,16 @@ var drawPreviewOverlay = ({
538
539
  }
539
540
  };
540
541
 
541
- // src/is-network-error.ts
542
+ // src/is-type-of-error.ts
542
543
  function isNetworkError(error) {
543
544
  if (error.message.includes("Failed to fetch") || error.message.includes("Load failed") || error.message.includes("NetworkError when attempting to fetch resource")) {
544
545
  return true;
545
546
  }
546
547
  return false;
547
548
  }
549
+ function isUnsupportedConfigurationError(error) {
550
+ return error.message.includes("Unsupported configuration");
551
+ }
548
552
 
549
553
  // src/nonce-manager.ts
550
554
  var makeNonceManager = () => {
@@ -1048,11 +1052,33 @@ class MediaPlayer {
1048
1052
  }
1049
1053
  this.audioIteratorManager.setVolume(volume);
1050
1054
  }
1051
- setTrimBefore(trimBefore) {
1055
+ updateAfterTrimChange(unloopedTimeInSeconds) {
1056
+ if (!this.audioIteratorManager && !this.videoIteratorManager) {
1057
+ return;
1058
+ }
1059
+ const newMediaTime = getTimeInSeconds({
1060
+ unloopedTimeInSeconds,
1061
+ playbackRate: this.playbackRate,
1062
+ loop: this.loop,
1063
+ trimBefore: this.trimBefore,
1064
+ trimAfter: this.trimAfter,
1065
+ mediaDurationInSeconds: this.totalDuration ?? null,
1066
+ fps: this.fps,
1067
+ ifNoMediaDuration: "infinity",
1068
+ src: this.src
1069
+ });
1070
+ if (newMediaTime !== null) {
1071
+ this.setPlaybackTime(newMediaTime, this.playbackRate * this.globalPlaybackRate);
1072
+ }
1073
+ this.audioIteratorManager?.destroyIterator();
1074
+ }
1075
+ setTrimBefore(trimBefore, unloopedTimeInSeconds) {
1052
1076
  this.trimBefore = trimBefore;
1077
+ this.updateAfterTrimChange(unloopedTimeInSeconds);
1053
1078
  }
1054
- setTrimAfter(trimAfter) {
1079
+ setTrimAfter(trimAfter, unloopedTimeInSeconds) {
1055
1080
  this.trimAfter = trimAfter;
1081
+ this.updateAfterTrimChange(unloopedTimeInSeconds);
1056
1082
  }
1057
1083
  setDebugOverlay(debugOverlay) {
1058
1084
  this.debugOverlay = debugOverlay;
@@ -1102,7 +1128,7 @@ class MediaPlayer {
1102
1128
  }
1103
1129
  this.nonceManager.createAsyncOperation();
1104
1130
  this.videoIteratorManager?.destroy();
1105
- this.audioIteratorManager?.destroy();
1131
+ this.audioIteratorManager?.destroyIterator();
1106
1132
  this.input.dispose();
1107
1133
  }
1108
1134
  scheduleAudioNode = (node, mediaTimestamp) => {
@@ -1344,6 +1370,8 @@ var AudioForPreviewAssertedShowing = ({
1344
1370
  const videoConfig = useUnsafeVideoConfig();
1345
1371
  const frame = useCurrentFrame2();
1346
1372
  const mediaPlayerRef = useRef(null);
1373
+ const initialTrimBeforeRef = useRef(trimBefore);
1374
+ const initialTrimAfterRef = useRef(trimAfter);
1347
1375
  const [mediaPlayerReady, setMediaPlayerReady] = useState2(false);
1348
1376
  const [shouldFallbackToNativeAudio, setShouldFallbackToNativeAudio] = useState2(false);
1349
1377
  const [playing] = Timeline.usePlayingState();
@@ -1396,11 +1424,11 @@ var AudioForPreviewAssertedShowing = ({
1396
1424
  trimAfter,
1397
1425
  trimBefore
1398
1426
  });
1399
- const buffering = useContext2(Internals6.BufferingContextReact);
1400
- if (!buffering) {
1427
+ const bufferingContext = useContext2(Internals6.BufferingContextReact);
1428
+ if (!bufferingContext) {
1401
1429
  throw new Error("useMediaPlayback must be used inside a <BufferingContext>");
1402
1430
  }
1403
- const isPlayerBuffering = Internals6.useIsPlayerBuffering(buffering);
1431
+ const isPlayerBuffering = Internals6.useIsPlayerBuffering(bufferingContext);
1404
1432
  useEffect2(() => {
1405
1433
  if (!sharedAudioContext)
1406
1434
  return;
@@ -1412,8 +1440,8 @@ var AudioForPreviewAssertedShowing = ({
1412
1440
  logLevel,
1413
1441
  sharedAudioContext: sharedAudioContext.audioContext,
1414
1442
  loop,
1415
- trimAfter,
1416
- trimBefore,
1443
+ trimAfter: initialTrimAfterRef.current,
1444
+ trimBefore: initialTrimBeforeRef.current,
1417
1445
  fps: videoConfig.fps,
1418
1446
  canvas: null,
1419
1447
  playbackRate,
@@ -1489,8 +1517,6 @@ var AudioForPreviewAssertedShowing = ({
1489
1517
  sharedAudioContext,
1490
1518
  currentTimeRef,
1491
1519
  loop,
1492
- trimAfter,
1493
- trimBefore,
1494
1520
  playbackRate,
1495
1521
  videoConfig.fps,
1496
1522
  audioStreamIndex,
@@ -1500,7 +1526,7 @@ var AudioForPreviewAssertedShowing = ({
1500
1526
  isPostmounting,
1501
1527
  globalPlaybackRate
1502
1528
  ]);
1503
- useEffect2(() => {
1529
+ useLayoutEffect(() => {
1504
1530
  const audioPlayer = mediaPlayerRef.current;
1505
1531
  if (!audioPlayer)
1506
1532
  return;
@@ -1511,14 +1537,21 @@ var AudioForPreviewAssertedShowing = ({
1511
1537
  }
1512
1538
  }, [isPlayerBuffering, logLevel, playing]);
1513
1539
  useLayoutEffect(() => {
1514
- const audioPlayer = mediaPlayerRef.current;
1515
- if (!audioPlayer || !mediaPlayerReady)
1540
+ const mediaPlayer = mediaPlayerRef.current;
1541
+ if (!mediaPlayer || !mediaPlayerReady) {
1516
1542
  return;
1517
- audioPlayer.seekTo(currentTime).catch(() => {});
1518
- Internals6.Log.trace({ logLevel, tag: "@remotion/media" }, `[AudioForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
1519
- }, [currentTime, logLevel, mediaPlayerReady]);
1543
+ }
1544
+ mediaPlayer.setTrimBefore(trimBefore, currentTimeRef.current);
1545
+ }, [trimBefore, mediaPlayerReady]);
1546
+ useLayoutEffect(() => {
1547
+ const mediaPlayer = mediaPlayerRef.current;
1548
+ if (!mediaPlayer || !mediaPlayerReady) {
1549
+ return;
1550
+ }
1551
+ mediaPlayer.setTrimAfter(trimAfter, currentTimeRef.current);
1552
+ }, [trimAfter, mediaPlayerReady]);
1520
1553
  const effectiveMuted = muted || mediaMuted || userPreferredVolume <= 0;
1521
- useEffect2(() => {
1554
+ useLayoutEffect(() => {
1522
1555
  const audioPlayer = mediaPlayerRef.current;
1523
1556
  if (!audioPlayer || !mediaPlayerReady)
1524
1557
  return;
@@ -1538,55 +1571,48 @@ var AudioForPreviewAssertedShowing = ({
1538
1571
  }
1539
1572
  audioPlayer.setPlaybackRate(playbackRate);
1540
1573
  }, [playbackRate, mediaPlayerReady]);
1541
- useEffect2(() => {
1574
+ useLayoutEffect(() => {
1542
1575
  const audioPlayer = mediaPlayerRef.current;
1543
1576
  if (!audioPlayer || !mediaPlayerReady) {
1544
1577
  return;
1545
1578
  }
1546
1579
  audioPlayer.setGlobalPlaybackRate(globalPlaybackRate);
1547
1580
  }, [globalPlaybackRate, mediaPlayerReady]);
1548
- useEffect2(() => {
1581
+ useLayoutEffect(() => {
1549
1582
  const audioPlayer = mediaPlayerRef.current;
1550
1583
  if (!audioPlayer || !mediaPlayerReady) {
1551
1584
  return;
1552
1585
  }
1553
1586
  audioPlayer.setFps(videoConfig.fps);
1554
1587
  }, [videoConfig.fps, mediaPlayerReady]);
1555
- useEffect2(() => {
1556
- const mediaPlayer = mediaPlayerRef.current;
1557
- if (!mediaPlayer || !mediaPlayerReady) {
1558
- return;
1559
- }
1560
- mediaPlayer.setTrimBefore(trimBefore);
1561
- }, [trimBefore, mediaPlayerReady]);
1562
- useEffect2(() => {
1563
- const mediaPlayer = mediaPlayerRef.current;
1564
- if (!mediaPlayer || !mediaPlayerReady) {
1565
- return;
1566
- }
1567
- mediaPlayer.setTrimAfter(trimAfter);
1568
- }, [trimAfter, mediaPlayerReady]);
1569
- useEffect2(() => {
1588
+ useLayoutEffect(() => {
1570
1589
  const mediaPlayer = mediaPlayerRef.current;
1571
1590
  if (!mediaPlayer || !mediaPlayerReady) {
1572
1591
  return;
1573
1592
  }
1574
1593
  mediaPlayer.setLoop(loop);
1575
1594
  }, [loop, mediaPlayerReady]);
1576
- useEffect2(() => {
1595
+ useLayoutEffect(() => {
1577
1596
  const mediaPlayer = mediaPlayerRef.current;
1578
1597
  if (!mediaPlayer || !mediaPlayerReady) {
1579
1598
  return;
1580
1599
  }
1581
1600
  mediaPlayer.setIsPremounting(isPremounting);
1582
1601
  }, [isPremounting, mediaPlayerReady]);
1583
- useEffect2(() => {
1602
+ useLayoutEffect(() => {
1584
1603
  const mediaPlayer = mediaPlayerRef.current;
1585
1604
  if (!mediaPlayer || !mediaPlayerReady) {
1586
1605
  return;
1587
1606
  }
1588
1607
  mediaPlayer.setIsPostmounting(isPostmounting);
1589
1608
  }, [isPostmounting, mediaPlayerReady]);
1609
+ useLayoutEffect(() => {
1610
+ const audioPlayer = mediaPlayerRef.current;
1611
+ if (!audioPlayer || !mediaPlayerReady)
1612
+ return;
1613
+ audioPlayer.seekTo(currentTime).catch(() => {});
1614
+ Internals6.Log.trace({ logLevel, tag: "@remotion/media" }, `[AudioForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
1615
+ }, [currentTime, logLevel, mediaPlayerReady]);
1590
1616
  if (shouldFallbackToNativeAudio && !disallowFallbackToHtml5Audio) {
1591
1617
  return /* @__PURE__ */ jsx(RemotionAudio, {
1592
1618
  src,
@@ -2902,61 +2928,72 @@ var extractAudioInternal = async ({
2902
2928
  maxCacheSize
2903
2929
  });
2904
2930
  const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
2905
- const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
2906
- audioManager.logOpenFrames();
2907
- const audioDataArray = [];
2908
- for (let i = 0;i < samples.length; i++) {
2909
- const sample = samples[i];
2910
- if (Math.abs(sample.timestamp - (timeInSeconds + durationInSeconds)) * sample.sampleRate < 1) {
2911
- continue;
2912
- }
2913
- if (sample.timestamp + sample.duration <= timeInSeconds) {
2914
- continue;
2915
- }
2916
- const isFirstSample = i === 0;
2917
- const isLastSample = i === samples.length - 1;
2918
- const audioDataRaw = sample.toAudioData();
2919
- let trimStartInSeconds = 0;
2920
- let trimEndInSeconds = 0;
2921
- let leadingSilence = null;
2922
- if (isFirstSample) {
2923
- trimStartInSeconds = fixFloatingPoint2(timeInSeconds - sample.timestamp);
2924
- if (trimStartInSeconds < 0) {
2925
- const silenceFrames = Math.ceil(fixFloatingPoint2(-trimStartInSeconds * TARGET_SAMPLE_RATE));
2926
- leadingSilence = {
2927
- data: new Int16Array(silenceFrames * TARGET_NUMBER_OF_CHANNELS),
2928
- numberOfFrames: silenceFrames,
2929
- timestamp: timeInSeconds * 1e6,
2930
- durationInMicroSeconds: silenceFrames / TARGET_SAMPLE_RATE * 1e6
2931
- };
2932
- trimStartInSeconds = 0;
2931
+ try {
2932
+ const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
2933
+ audioManager.logOpenFrames();
2934
+ const audioDataArray = [];
2935
+ for (let i = 0;i < samples.length; i++) {
2936
+ const sample = samples[i];
2937
+ if (Math.abs(sample.timestamp - (timeInSeconds + durationInSeconds)) * sample.sampleRate < 1) {
2938
+ continue;
2939
+ }
2940
+ if (sample.timestamp + sample.duration <= timeInSeconds) {
2941
+ continue;
2942
+ }
2943
+ const isFirstSample = i === 0;
2944
+ const isLastSample = i === samples.length - 1;
2945
+ const audioDataRaw = sample.toAudioData();
2946
+ let trimStartInSeconds = 0;
2947
+ let trimEndInSeconds = 0;
2948
+ let leadingSilence = null;
2949
+ if (isFirstSample) {
2950
+ trimStartInSeconds = fixFloatingPoint2(timeInSeconds - sample.timestamp);
2951
+ if (trimStartInSeconds < 0) {
2952
+ const silenceFrames = Math.ceil(fixFloatingPoint2(-trimStartInSeconds * TARGET_SAMPLE_RATE));
2953
+ leadingSilence = {
2954
+ data: new Int16Array(silenceFrames * TARGET_NUMBER_OF_CHANNELS),
2955
+ numberOfFrames: silenceFrames,
2956
+ timestamp: timeInSeconds * 1e6,
2957
+ durationInMicroSeconds: silenceFrames / TARGET_SAMPLE_RATE * 1e6
2958
+ };
2959
+ trimStartInSeconds = 0;
2960
+ }
2961
+ }
2962
+ if (isLastSample) {
2963
+ trimEndInSeconds = Math.max(0, sample.timestamp + sample.duration - (timeInSeconds + durationInSeconds));
2964
+ }
2965
+ const audioData = convertAudioData({
2966
+ audioData: audioDataRaw,
2967
+ trimStartInSeconds,
2968
+ trimEndInSeconds,
2969
+ playbackRate,
2970
+ audioDataTimestamp: sample.timestamp,
2971
+ isLast: isLastSample
2972
+ });
2973
+ audioDataRaw.close();
2974
+ if (audioData.numberOfFrames === 0) {
2975
+ continue;
2976
+ }
2977
+ if (leadingSilence) {
2978
+ audioDataArray.push(leadingSilence);
2933
2979
  }
2980
+ audioDataArray.push(audioData);
2934
2981
  }
2935
- if (isLastSample) {
2936
- trimEndInSeconds = Math.max(0, sample.timestamp + sample.duration - (timeInSeconds + durationInSeconds));
2982
+ if (audioDataArray.length === 0) {
2983
+ return { data: null, durationInSeconds: mediaDurationInSeconds };
2937
2984
  }
2938
- const audioData = convertAudioData({
2939
- audioData: audioDataRaw,
2940
- trimStartInSeconds,
2941
- trimEndInSeconds,
2942
- playbackRate,
2943
- audioDataTimestamp: sample.timestamp,
2944
- isLast: isLastSample
2945
- });
2946
- audioDataRaw.close();
2947
- if (audioData.numberOfFrames === 0) {
2948
- continue;
2985
+ const combined = combineAudioDataAndClosePrevious(audioDataArray);
2986
+ return { data: combined, durationInSeconds: mediaDurationInSeconds };
2987
+ } catch (err) {
2988
+ const error = err;
2989
+ if (isNetworkError(error)) {
2990
+ return "network-error";
2949
2991
  }
2950
- if (leadingSilence) {
2951
- audioDataArray.push(leadingSilence);
2992
+ if (isUnsupportedConfigurationError(error)) {
2993
+ return "cannot-decode";
2952
2994
  }
2953
- audioDataArray.push(audioData);
2954
- }
2955
- if (audioDataArray.length === 0) {
2956
- return { data: null, durationInSeconds: mediaDurationInSeconds };
2995
+ throw err;
2957
2996
  }
2958
- const combined = combineAudioDataAndClosePrevious(audioDataArray);
2959
- return { data: combined, durationInSeconds: mediaDurationInSeconds };
2960
2997
  };
2961
2998
  var queue = Promise.resolve(undefined);
2962
2999
  var extractAudio = (params) => {
@@ -3394,7 +3431,7 @@ var AudioForRendering = ({
3394
3431
  loopVolumeCurveBehavior,
3395
3432
  delayRenderRetries,
3396
3433
  delayRenderTimeoutInMilliseconds,
3397
- logLevel,
3434
+ logLevel = window.remotion_logLevel ?? "info",
3398
3435
  loop,
3399
3436
  fallbackHtml5AudioProps,
3400
3437
  audioStreamIndex,
@@ -3422,13 +3459,14 @@ var AudioForRendering = ({
3422
3459
  const { delayRender, continueRender } = useDelayRender();
3423
3460
  const [replaceWithHtml5Audio, setReplaceWithHtml5Audio] = useState3(false);
3424
3461
  const sequenceContext = useContext3(Internals13.SequenceContext);
3425
- const id = useMemo3(() => `media-video-${random(src)}-${sequenceContext?.cumulatedFrom}-${sequenceContext?.relativeFrom}-${sequenceContext?.durationInFrames}`, [
3462
+ const id = useMemo3(() => `media-audio-${random(src)}-${sequenceContext?.cumulatedFrom}-${sequenceContext?.relativeFrom}-${sequenceContext?.durationInFrames}`, [
3426
3463
  src,
3427
3464
  sequenceContext?.cumulatedFrom,
3428
3465
  sequenceContext?.relativeFrom,
3429
3466
  sequenceContext?.durationInFrames
3430
3467
  ]);
3431
3468
  const maxCacheSize = useMaxMediaCacheSize(logLevel ?? window.remotion_logLevel);
3469
+ const audioEnabled = Internals13.useAudioEnabled();
3432
3470
  useLayoutEffect2(() => {
3433
3471
  const timestamp = frame / fps;
3434
3472
  const durationInSeconds = 1 / fps;
@@ -3440,7 +3478,7 @@ var AudioForRendering = ({
3440
3478
  timeoutInMilliseconds: delayRenderTimeoutInMilliseconds ?? undefined
3441
3479
  });
3442
3480
  const shouldRenderAudio = (() => {
3443
- if (!window.remotion_audioEnabled) {
3481
+ if (!audioEnabled) {
3444
3482
  return false;
3445
3483
  }
3446
3484
  if (muted) {
@@ -3520,7 +3558,7 @@ var AudioForRendering = ({
3520
3558
  registerRenderAsset({
3521
3559
  type: "inline-audio",
3522
3560
  id,
3523
- audio: Array.from(audio.data),
3561
+ audio: environment.isClientSideRendering ? audio.data : Array.from(audio.data),
3524
3562
  frame: absoluteFrame,
3525
3563
  timestamp: audio.timestamp,
3526
3564
  duration: audio.numberOfFrames / TARGET_SAMPLE_RATE * 1e6,
@@ -3561,7 +3599,8 @@ var AudioForRendering = ({
3561
3599
  trimAfter,
3562
3600
  trimBefore,
3563
3601
  replaceWithHtml5Audio,
3564
- maxCacheSize
3602
+ maxCacheSize,
3603
+ audioEnabled
3565
3604
  ]);
3566
3605
  if (replaceWithHtml5Audio) {
3567
3606
  return /* @__PURE__ */ jsx2(Html5Audio, {
@@ -3668,6 +3707,8 @@ var VideoForPreviewAssertedShowing = ({
3668
3707
  const videoConfig = useUnsafeVideoConfig2();
3669
3708
  const frame = useCurrentFrame4();
3670
3709
  const mediaPlayerRef = useRef2(null);
3710
+ const initialTrimBeforeRef = useRef2(trimBefore);
3711
+ const initialTrimAfterRef = useRef2(trimAfter);
3671
3712
  const [mediaPlayerReady, setMediaPlayerReady] = useState4(false);
3672
3713
  const [shouldFallbackToNativeVideo, setShouldFallbackToNativeVideo] = useState4(false);
3673
3714
  const [playing] = Timeline2.usePlayingState();
@@ -3738,8 +3779,8 @@ var VideoForPreviewAssertedShowing = ({
3738
3779
  logLevel,
3739
3780
  sharedAudioContext: sharedAudioContext.audioContext,
3740
3781
  loop,
3741
- trimAfter,
3742
- trimBefore,
3782
+ trimAfter: initialTrimAfterRef.current,
3783
+ trimBefore: initialTrimBeforeRef.current,
3743
3784
  fps: videoConfig.fps,
3744
3785
  playbackRate,
3745
3786
  audioStreamIndex,
@@ -3812,8 +3853,6 @@ var VideoForPreviewAssertedShowing = ({
3812
3853
  logLevel,
3813
3854
  sharedAudioContext,
3814
3855
  loop,
3815
- trimAfter,
3816
- trimBefore,
3817
3856
  videoConfig.fps,
3818
3857
  playbackRate,
3819
3858
  disallowFallbackToOffthreadVideo,
@@ -3837,97 +3876,97 @@ var VideoForPreviewAssertedShowing = ({
3837
3876
  mediaPlayer.pause();
3838
3877
  }
3839
3878
  }, [isPlayerBuffering, playing, logLevel, mediaPlayerReady]);
3840
- useLayoutEffect3(() => {
3879
+ useEffect3(() => {
3841
3880
  const mediaPlayer = mediaPlayerRef.current;
3842
- if (!mediaPlayer || !mediaPlayerReady)
3881
+ if (!mediaPlayer || !mediaPlayerReady) {
3843
3882
  return;
3844
- mediaPlayer.seekTo(currentTime).catch(() => {});
3845
- Internals15.Log.trace({ logLevel, tag: "@remotion/media" }, `[VideoForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
3846
- }, [currentTime, logLevel, mediaPlayerReady]);
3847
- const effectiveMuted = isSequenceHidden || muted || mediaMuted || userPreferredVolume <= 0;
3883
+ }
3884
+ mediaPlayer.setTrimBefore(trimBefore, currentTimeRef.current);
3885
+ }, [trimBefore, mediaPlayerReady]);
3848
3886
  useEffect3(() => {
3887
+ const mediaPlayer = mediaPlayerRef.current;
3888
+ if (!mediaPlayer || !mediaPlayerReady) {
3889
+ return;
3890
+ }
3891
+ mediaPlayer.setTrimAfter(trimAfter, currentTimeRef.current);
3892
+ }, [trimAfter, mediaPlayerReady]);
3893
+ const effectiveMuted = isSequenceHidden || muted || mediaMuted || userPreferredVolume <= 0;
3894
+ useLayoutEffect3(() => {
3849
3895
  const mediaPlayer = mediaPlayerRef.current;
3850
3896
  if (!mediaPlayer || !mediaPlayerReady)
3851
3897
  return;
3852
3898
  mediaPlayer.setMuted(effectiveMuted);
3853
3899
  }, [effectiveMuted, mediaPlayerReady]);
3854
- useEffect3(() => {
3900
+ useLayoutEffect3(() => {
3855
3901
  const mediaPlayer = mediaPlayerRef.current;
3856
3902
  if (!mediaPlayer || !mediaPlayerReady) {
3857
3903
  return;
3858
3904
  }
3859
3905
  mediaPlayer.setVolume(userPreferredVolume);
3860
3906
  }, [userPreferredVolume, mediaPlayerReady]);
3861
- useEffect3(() => {
3907
+ useLayoutEffect3(() => {
3862
3908
  const mediaPlayer = mediaPlayerRef.current;
3863
3909
  if (!mediaPlayer || !mediaPlayerReady) {
3864
3910
  return;
3865
3911
  }
3866
3912
  mediaPlayer.setDebugOverlay(debugOverlay);
3867
3913
  }, [debugOverlay, mediaPlayerReady]);
3868
- useEffect3(() => {
3914
+ useLayoutEffect3(() => {
3869
3915
  const mediaPlayer = mediaPlayerRef.current;
3870
3916
  if (!mediaPlayer || !mediaPlayerReady) {
3871
3917
  return;
3872
3918
  }
3873
3919
  mediaPlayer.setPlaybackRate(playbackRate);
3874
3920
  }, [playbackRate, mediaPlayerReady]);
3875
- useEffect3(() => {
3921
+ useLayoutEffect3(() => {
3876
3922
  const mediaPlayer = mediaPlayerRef.current;
3877
3923
  if (!mediaPlayer || !mediaPlayerReady) {
3878
3924
  return;
3879
3925
  }
3880
3926
  mediaPlayer.setGlobalPlaybackRate(globalPlaybackRate);
3881
3927
  }, [globalPlaybackRate, mediaPlayerReady]);
3882
- useEffect3(() => {
3928
+ useLayoutEffect3(() => {
3883
3929
  const mediaPlayer = mediaPlayerRef.current;
3884
3930
  if (!mediaPlayer || !mediaPlayerReady) {
3885
3931
  return;
3886
3932
  }
3887
3933
  mediaPlayer.setLoop(loop);
3888
3934
  }, [loop, mediaPlayerReady]);
3889
- useEffect3(() => {
3935
+ useLayoutEffect3(() => {
3890
3936
  const mediaPlayer = mediaPlayerRef.current;
3891
3937
  if (!mediaPlayer || !mediaPlayerReady) {
3892
3938
  return;
3893
3939
  }
3894
3940
  mediaPlayer.setIsPremounting(isPremounting);
3895
3941
  }, [isPremounting, mediaPlayerReady]);
3896
- useEffect3(() => {
3942
+ useLayoutEffect3(() => {
3897
3943
  const mediaPlayer = mediaPlayerRef.current;
3898
3944
  if (!mediaPlayer || !mediaPlayerReady) {
3899
3945
  return;
3900
3946
  }
3901
3947
  mediaPlayer.setIsPostmounting(isPostmounting);
3902
3948
  }, [isPostmounting, mediaPlayerReady]);
3903
- useEffect3(() => {
3949
+ useLayoutEffect3(() => {
3904
3950
  const mediaPlayer = mediaPlayerRef.current;
3905
3951
  if (!mediaPlayer || !mediaPlayerReady) {
3906
3952
  return;
3907
3953
  }
3908
3954
  mediaPlayer.setFps(videoConfig.fps);
3909
3955
  }, [videoConfig.fps, mediaPlayerReady]);
3910
- useEffect3(() => {
3956
+ useLayoutEffect3(() => {
3911
3957
  const mediaPlayer = mediaPlayerRef.current;
3912
3958
  if (!mediaPlayer || !mediaPlayerReady) {
3913
3959
  return;
3914
3960
  }
3915
3961
  mediaPlayer.setVideoFrameCallback(onVideoFrame ?? null);
3916
3962
  }, [onVideoFrame, mediaPlayerReady]);
3917
- useEffect3(() => {
3918
- const mediaPlayer = mediaPlayerRef.current;
3919
- if (!mediaPlayer || !mediaPlayerReady) {
3920
- return;
3921
- }
3922
- mediaPlayer.setTrimBefore(trimBefore);
3923
- }, [trimBefore, mediaPlayerReady]);
3924
- useEffect3(() => {
3963
+ useLayoutEffect3(() => {
3925
3964
  const mediaPlayer = mediaPlayerRef.current;
3926
- if (!mediaPlayer || !mediaPlayerReady) {
3965
+ if (!mediaPlayer || !mediaPlayerReady)
3927
3966
  return;
3928
- }
3929
- mediaPlayer.setTrimAfter(trimAfter);
3930
- }, [trimAfter, mediaPlayerReady]);
3967
+ mediaPlayer.seekTo(currentTime).catch(() => {});
3968
+ Internals15.Log.trace({ logLevel, tag: "@remotion/media" }, `[VideoForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
3969
+ }, [currentTime, logLevel, mediaPlayerReady]);
3931
3970
  const actualStyle = useMemo4(() => {
3932
3971
  return {
3933
3972
  ...style,
@@ -4115,7 +4154,7 @@ var VideoForRendering = ({
4115
4154
  cancelRender3(new Error(`Cannot decode ${src}, and 'disallowFallbackToOffthreadVideo' was set. Failing the render.`));
4116
4155
  }
4117
4156
  if (window.remotion_isMainTab) {
4118
- Internals16.Log.info({ logLevel, tag: "@remotion/media" }, `Cannot decode ${src}, falling back to <OffthreadVideo>`);
4157
+ Internals16.Log.warn({ logLevel, tag: "@remotion/media" }, `Cannot decode ${src}, falling back to <OffthreadVideo>`);
4119
4158
  }
4120
4159
  setReplaceWithOffthreadVideo({
4121
4160
  durationInSeconds: result.durationInSeconds
@@ -4189,7 +4228,7 @@ var VideoForRendering = ({
4189
4228
  registerRenderAsset({
4190
4229
  type: "inline-audio",
4191
4230
  id,
4192
- audio: Array.from(audio.data),
4231
+ audio: environment.isClientSideRendering ? audio.data : Array.from(audio.data),
4193
4232
  frame: absoluteFrame,
4194
4233
  timestamp: audio.timestamp,
4195
4234
  duration: audio.numberOfFrames / TARGET_SAMPLE_RATE * 1e6,
@@ -1,5 +1,5 @@
1
1
  import { extractAudio } from './audio-extraction/extract-audio';
2
- import { isNetworkError } from './is-network-error';
2
+ import { isNetworkError } from './is-type-of-error';
3
3
  import { extractFrame } from './video-extraction/extract-frame';
4
4
  import { rotateFrame } from './video-extraction/rotate-frame';
5
5
  export const extractFrameAndAudio = async ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, loop, audioStreamIndex, trimAfter, trimBefore, fps, maxCacheSize, }) => {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Utility to check if error is network error
3
+ * @param error
4
+ * @returns
5
+ */
6
+ export declare function isNetworkError(error: Error): boolean;
7
+ export declare function isUnsupportedConfigurationError(error: Error): boolean;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Utility to check if error is network error
3
+ * @param error
4
+ * @returns
5
+ */
6
+ export function isNetworkError(error) {
7
+ if (
8
+ // Chrome
9
+ error.message.includes('Failed to fetch') ||
10
+ // Safari
11
+ error.message.includes('Load failed') ||
12
+ // Firefox
13
+ error.message.includes('NetworkError when attempting to fetch resource')) {
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+ export function isUnsupportedConfigurationError(error) {
19
+ return error.message.includes('Unsupported configuration');
20
+ }
@@ -70,8 +70,9 @@ export declare class MediaPlayer {
70
70
  pause(): void;
71
71
  setMuted(muted: boolean): void;
72
72
  setVolume(volume: number): void;
73
- setTrimBefore(trimBefore: number | undefined): void;
74
- setTrimAfter(trimAfter: number | undefined): void;
73
+ private updateAfterTrimChange;
74
+ setTrimBefore(trimBefore: number | undefined, unloopedTimeInSeconds: number): void;
75
+ setTrimAfter(trimAfter: number | undefined, unloopedTimeInSeconds: number): void;
75
76
  setDebugOverlay(debugOverlay: boolean): void;
76
77
  private updateAfterPlaybackRateChange;
77
78
  setPlaybackRate(rate: number): void;
@@ -4,7 +4,7 @@ import { audioIteratorManager, } from './audio-iterator-manager';
4
4
  import { calculatePlaybackTime } from './calculate-playbacktime';
5
5
  import { drawPreviewOverlay } from './debug-overlay/preview-overlay';
6
6
  import { getTimeInSeconds } from './get-time-in-seconds';
7
- import { isNetworkError } from './is-network-error';
7
+ import { isNetworkError } from './is-type-of-error';
8
8
  import { makeNonceManager } from './nonce-manager';
9
9
  import { videoIteratorManager } from './video-iterator-manager';
10
10
  export class MediaPlayer {
@@ -284,11 +284,35 @@ export class MediaPlayer {
284
284
  }
285
285
  this.audioIteratorManager.setVolume(volume);
286
286
  }
287
- setTrimBefore(trimBefore) {
287
+ updateAfterTrimChange(unloopedTimeInSeconds) {
288
+ if (!this.audioIteratorManager && !this.videoIteratorManager) {
289
+ return;
290
+ }
291
+ const newMediaTime = getTimeInSeconds({
292
+ unloopedTimeInSeconds,
293
+ playbackRate: this.playbackRate,
294
+ loop: this.loop,
295
+ trimBefore: this.trimBefore,
296
+ trimAfter: this.trimAfter,
297
+ mediaDurationInSeconds: this.totalDuration ?? null,
298
+ fps: this.fps,
299
+ ifNoMediaDuration: 'infinity',
300
+ src: this.src,
301
+ });
302
+ if (newMediaTime !== null) {
303
+ this.setPlaybackTime(newMediaTime, this.playbackRate * this.globalPlaybackRate);
304
+ }
305
+ // audio iterator will be re-created on next play/seek
306
+ // video iterator doesn't need to be re-created
307
+ this.audioIteratorManager?.destroyIterator();
308
+ }
309
+ setTrimBefore(trimBefore, unloopedTimeInSeconds) {
288
310
  this.trimBefore = trimBefore;
311
+ this.updateAfterTrimChange(unloopedTimeInSeconds);
289
312
  }
290
- setTrimAfter(trimAfter) {
313
+ setTrimAfter(trimAfter, unloopedTimeInSeconds) {
291
314
  this.trimAfter = trimAfter;
315
+ this.updateAfterTrimChange(unloopedTimeInSeconds);
292
316
  }
293
317
  setDebugOverlay(debugOverlay) {
294
318
  this.debugOverlay = debugOverlay;
@@ -345,7 +369,7 @@ export class MediaPlayer {
345
369
  // Mark all async operations as stale
346
370
  this.nonceManager.createAsyncOperation();
347
371
  this.videoIteratorManager?.destroy();
348
- this.audioIteratorManager?.destroy();
372
+ this.audioIteratorManager?.destroyIterator();
349
373
  this.input.dispose();
350
374
  }
351
375
  getPlaybackTime() {
@@ -12,6 +12,8 @@ const VideoForPreviewAssertedShowing = ({ src: unpreloadedSrc, style, playbackRa
12
12
  const videoConfig = useUnsafeVideoConfig();
13
13
  const frame = useCurrentFrame();
14
14
  const mediaPlayerRef = useRef(null);
15
+ const initialTrimBeforeRef = useRef(trimBefore);
16
+ const initialTrimAfterRef = useRef(trimAfter);
15
17
  const [mediaPlayerReady, setMediaPlayerReady] = useState(false);
16
18
  const [shouldFallbackToNativeVideo, setShouldFallbackToNativeVideo] = useState(false);
17
19
  const [playing] = Timeline.usePlayingState();
@@ -82,8 +84,8 @@ const VideoForPreviewAssertedShowing = ({ src: unpreloadedSrc, style, playbackRa
82
84
  logLevel,
83
85
  sharedAudioContext: sharedAudioContext.audioContext,
84
86
  loop,
85
- trimAfter,
86
- trimBefore,
87
+ trimAfter: initialTrimAfterRef.current,
88
+ trimBefore: initialTrimBeforeRef.current,
87
89
  fps: videoConfig.fps,
88
90
  playbackRate,
89
91
  audioStreamIndex,
@@ -160,8 +162,6 @@ const VideoForPreviewAssertedShowing = ({ src: unpreloadedSrc, style, playbackRa
160
162
  logLevel,
161
163
  sharedAudioContext,
162
164
  loop,
163
- trimAfter,
164
- trimBefore,
165
165
  videoConfig.fps,
166
166
  playbackRate,
167
167
  disallowFallbackToOffthreadVideo,
@@ -188,99 +188,99 @@ const VideoForPreviewAssertedShowing = ({ src: unpreloadedSrc, style, playbackRa
188
188
  mediaPlayer.pause();
189
189
  }
190
190
  }, [isPlayerBuffering, playing, logLevel, mediaPlayerReady]);
191
- useLayoutEffect(() => {
191
+ useEffect(() => {
192
192
  const mediaPlayer = mediaPlayerRef.current;
193
- if (!mediaPlayer || !mediaPlayerReady)
193
+ if (!mediaPlayer || !mediaPlayerReady) {
194
194
  return;
195
- mediaPlayer.seekTo(currentTime).catch(() => {
196
- // Might be disposed
197
- });
198
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[VideoForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
199
- }, [currentTime, logLevel, mediaPlayerReady]);
200
- const effectiveMuted = isSequenceHidden || muted || mediaMuted || userPreferredVolume <= 0;
195
+ }
196
+ mediaPlayer.setTrimBefore(trimBefore, currentTimeRef.current);
197
+ }, [trimBefore, mediaPlayerReady]);
201
198
  useEffect(() => {
199
+ const mediaPlayer = mediaPlayerRef.current;
200
+ if (!mediaPlayer || !mediaPlayerReady) {
201
+ return;
202
+ }
203
+ mediaPlayer.setTrimAfter(trimAfter, currentTimeRef.current);
204
+ }, [trimAfter, mediaPlayerReady]);
205
+ const effectiveMuted = isSequenceHidden || muted || mediaMuted || userPreferredVolume <= 0;
206
+ useLayoutEffect(() => {
202
207
  const mediaPlayer = mediaPlayerRef.current;
203
208
  if (!mediaPlayer || !mediaPlayerReady)
204
209
  return;
205
210
  mediaPlayer.setMuted(effectiveMuted);
206
211
  }, [effectiveMuted, mediaPlayerReady]);
207
- useEffect(() => {
212
+ useLayoutEffect(() => {
208
213
  const mediaPlayer = mediaPlayerRef.current;
209
214
  if (!mediaPlayer || !mediaPlayerReady) {
210
215
  return;
211
216
  }
212
217
  mediaPlayer.setVolume(userPreferredVolume);
213
218
  }, [userPreferredVolume, mediaPlayerReady]);
214
- useEffect(() => {
219
+ useLayoutEffect(() => {
215
220
  const mediaPlayer = mediaPlayerRef.current;
216
221
  if (!mediaPlayer || !mediaPlayerReady) {
217
222
  return;
218
223
  }
219
224
  mediaPlayer.setDebugOverlay(debugOverlay);
220
225
  }, [debugOverlay, mediaPlayerReady]);
221
- useEffect(() => {
226
+ useLayoutEffect(() => {
222
227
  const mediaPlayer = mediaPlayerRef.current;
223
228
  if (!mediaPlayer || !mediaPlayerReady) {
224
229
  return;
225
230
  }
226
231
  mediaPlayer.setPlaybackRate(playbackRate);
227
232
  }, [playbackRate, mediaPlayerReady]);
228
- useEffect(() => {
233
+ useLayoutEffect(() => {
229
234
  const mediaPlayer = mediaPlayerRef.current;
230
235
  if (!mediaPlayer || !mediaPlayerReady) {
231
236
  return;
232
237
  }
233
238
  mediaPlayer.setGlobalPlaybackRate(globalPlaybackRate);
234
239
  }, [globalPlaybackRate, mediaPlayerReady]);
235
- useEffect(() => {
240
+ useLayoutEffect(() => {
236
241
  const mediaPlayer = mediaPlayerRef.current;
237
242
  if (!mediaPlayer || !mediaPlayerReady) {
238
243
  return;
239
244
  }
240
245
  mediaPlayer.setLoop(loop);
241
246
  }, [loop, mediaPlayerReady]);
242
- useEffect(() => {
247
+ useLayoutEffect(() => {
243
248
  const mediaPlayer = mediaPlayerRef.current;
244
249
  if (!mediaPlayer || !mediaPlayerReady) {
245
250
  return;
246
251
  }
247
252
  mediaPlayer.setIsPremounting(isPremounting);
248
253
  }, [isPremounting, mediaPlayerReady]);
249
- useEffect(() => {
254
+ useLayoutEffect(() => {
250
255
  const mediaPlayer = mediaPlayerRef.current;
251
256
  if (!mediaPlayer || !mediaPlayerReady) {
252
257
  return;
253
258
  }
254
259
  mediaPlayer.setIsPostmounting(isPostmounting);
255
260
  }, [isPostmounting, mediaPlayerReady]);
256
- useEffect(() => {
261
+ useLayoutEffect(() => {
257
262
  const mediaPlayer = mediaPlayerRef.current;
258
263
  if (!mediaPlayer || !mediaPlayerReady) {
259
264
  return;
260
265
  }
261
266
  mediaPlayer.setFps(videoConfig.fps);
262
267
  }, [videoConfig.fps, mediaPlayerReady]);
263
- useEffect(() => {
268
+ useLayoutEffect(() => {
264
269
  const mediaPlayer = mediaPlayerRef.current;
265
270
  if (!mediaPlayer || !mediaPlayerReady) {
266
271
  return;
267
272
  }
268
273
  mediaPlayer.setVideoFrameCallback(onVideoFrame ?? null);
269
274
  }, [onVideoFrame, mediaPlayerReady]);
270
- useEffect(() => {
271
- const mediaPlayer = mediaPlayerRef.current;
272
- if (!mediaPlayer || !mediaPlayerReady) {
273
- return;
274
- }
275
- mediaPlayer.setTrimBefore(trimBefore);
276
- }, [trimBefore, mediaPlayerReady]);
277
- useEffect(() => {
275
+ useLayoutEffect(() => {
278
276
  const mediaPlayer = mediaPlayerRef.current;
279
- if (!mediaPlayer || !mediaPlayerReady) {
277
+ if (!mediaPlayer || !mediaPlayerReady)
280
278
  return;
281
- }
282
- mediaPlayer.setTrimAfter(trimAfter);
283
- }, [trimAfter, mediaPlayerReady]);
279
+ mediaPlayer.seekTo(currentTime).catch(() => {
280
+ // Might be disposed
281
+ });
282
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[VideoForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
283
+ }, [currentTime, logLevel, mediaPlayerReady]);
284
284
  const actualStyle = useMemo(() => {
285
285
  return {
286
286
  ...style,
@@ -92,7 +92,7 @@ export const VideoForRendering = ({ volume: volumeProp, playbackRate, src, muted
92
92
  cancelRender(new Error(`Cannot decode ${src}, and 'disallowFallbackToOffthreadVideo' was set. Failing the render.`));
93
93
  }
94
94
  if (window.remotion_isMainTab) {
95
- Internals.Log.info({ logLevel, tag: '@remotion/media' }, `Cannot decode ${src}, falling back to <OffthreadVideo>`);
95
+ Internals.Log.warn({ logLevel, tag: '@remotion/media' }, `Cannot decode ${src}, falling back to <OffthreadVideo>`);
96
96
  }
97
97
  setReplaceWithOffthreadVideo({
98
98
  durationInSeconds: result.durationInSeconds,
@@ -165,7 +165,9 @@ export const VideoForRendering = ({ volume: volumeProp, playbackRate, src, muted
165
165
  registerRenderAsset({
166
166
  type: 'inline-audio',
167
167
  id,
168
- audio: Array.from(audio.data),
168
+ audio: environment.isClientSideRendering
169
+ ? audio.data
170
+ : Array.from(audio.data),
169
171
  frame: absoluteFrame,
170
172
  timestamp: audio.timestamp,
171
173
  duration: (audio.numberOfFrames / TARGET_SAMPLE_RATE) * 1000000,
@@ -1,5 +1,5 @@
1
1
  import { ALL_FORMATS, AudioSampleSink, EncodedPacketSink, Input, MATROSKA, UrlSource, VideoSampleSink, WEBM, } from 'mediabunny';
2
- import { isNetworkError } from '../is-network-error';
2
+ import { isNetworkError } from '../is-type-of-error';
3
3
  import { makeKeyframeBank } from './keyframe-bank';
4
4
  import { rememberActualMatroskaTimestamps } from './remember-actual-matroska-timestamps';
5
5
  const getRetryDelay = (() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.382",
3
+ "version": "4.0.384",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -21,15 +21,15 @@
21
21
  "make": "tsc -d && bun --env-file=../.env.bundle bundle.ts"
22
22
  },
23
23
  "dependencies": {
24
- "mediabunny": "1.25.3",
25
- "remotion": "4.0.382"
24
+ "mediabunny": "1.25.8",
25
+ "remotion": "4.0.384"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": ">=16.8.0",
29
29
  "react-dom": ">=16.8.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@remotion/eslint-config-internal": "4.0.382",
32
+ "@remotion/eslint-config-internal": "4.0.384",
33
33
  "@vitest/browser-webdriverio": "4.0.7",
34
34
  "eslint": "9.19.0",
35
35
  "react": "19.2.1",