@remotion/media 4.0.365 → 4.0.367

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.
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
- import { Html5Video, Internals, useBufferState, useCurrentFrame } from 'remotion';
3
+ import { Html5Video, Internals, useBufferState, useCurrentFrame, useVideoConfig, } from 'remotion';
4
+ import { getTimeInSeconds } from '../get-time-in-seconds';
4
5
  import { MediaPlayer } from '../media-player';
5
6
  import { useLoopDisplay } from '../show-in-timeline';
6
7
  import { useMediaInTimeline } from '../use-media-in-timeline';
7
8
  const { useUnsafeVideoConfig, Timeline, SharedAudioContext, useMediaMutedState, useMediaVolumeState, useFrameForVolumeProp, evaluateVolume, warnAboutTooHighVolume, usePreload, SequenceContext, SequenceVisibilityToggleContext, } = Internals;
8
- export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logLevel, className, muted, volume, loopVolumeCurveBehavior, onVideoFrame, showInTimeline, loop, name, trimAfter, trimBefore, stack, disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps, audioStreamIndex, debugOverlay, }) => {
9
+ const VideoForPreviewAssertedShowing = ({ src: unpreloadedSrc, style, playbackRate, logLevel, className, muted, volume, loopVolumeCurveBehavior, onVideoFrame, showInTimeline, loop, name, trimAfter, trimBefore, stack, disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps, audioStreamIndex, debugOverlay, }) => {
9
10
  const src = usePreload(unpreloadedSrc);
10
11
  const canvasRef = useRef(null);
11
12
  const videoConfig = useUnsafeVideoConfig();
@@ -30,6 +31,8 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
30
31
  });
31
32
  warnAboutTooHighVolume(userPreferredVolume);
32
33
  const parentSequence = useContext(SequenceContext);
34
+ const isPremounting = Boolean(parentSequence?.premounting);
35
+ const isPostmounting = Boolean(parentSequence?.postmounting);
33
36
  const loopDisplay = useLoopDisplay({
34
37
  loop,
35
38
  mediaDurationInSeconds,
@@ -86,6 +89,9 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
86
89
  audioStreamIndex,
87
90
  debugOverlay,
88
91
  bufferState: buffer,
92
+ isPremounting,
93
+ isPostmounting,
94
+ globalPlaybackRate,
89
95
  });
90
96
  mediaPlayerRef.current = player;
91
97
  player
@@ -162,6 +168,9 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
162
168
  audioStreamIndex,
163
169
  debugOverlay,
164
170
  buffer,
171
+ isPremounting,
172
+ isPostmounting,
173
+ globalPlaybackRate,
165
174
  ]);
166
175
  const classNameValue = useMemo(() => {
167
176
  return [Internals.OBJECTFIT_CONTAIN_CLASS_NAME, className]
@@ -183,7 +192,9 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
183
192
  const mediaPlayer = mediaPlayerRef.current;
184
193
  if (!mediaPlayer || !mediaPlayerReady)
185
194
  return;
186
- mediaPlayer.seekTo(currentTime);
195
+ mediaPlayer.seekTo(currentTime).catch(() => {
196
+ // Might be disposed
197
+ });
187
198
  Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[VideoForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
188
199
  }, [currentTime, logLevel, mediaPlayerReady]);
189
200
  const effectiveMuted = isSequenceHidden || muted || mediaMuted || userPreferredVolume <= 0;
@@ -207,14 +218,20 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
207
218
  }
208
219
  mediaPlayer.setDebugOverlay(debugOverlay);
209
220
  }, [debugOverlay, mediaPlayerReady]);
210
- const effectivePlaybackRate = useMemo(() => playbackRate * globalPlaybackRate, [playbackRate, globalPlaybackRate]);
211
221
  useEffect(() => {
212
222
  const mediaPlayer = mediaPlayerRef.current;
213
223
  if (!mediaPlayer || !mediaPlayerReady) {
214
224
  return;
215
225
  }
216
- mediaPlayer.setPlaybackRate(effectivePlaybackRate);
217
- }, [effectivePlaybackRate, mediaPlayerReady]);
226
+ mediaPlayer.setPlaybackRate(playbackRate);
227
+ }, [playbackRate, mediaPlayerReady]);
228
+ useEffect(() => {
229
+ const mediaPlayer = mediaPlayerRef.current;
230
+ if (!mediaPlayer || !mediaPlayerReady) {
231
+ return;
232
+ }
233
+ mediaPlayer.setGlobalPlaybackRate(globalPlaybackRate);
234
+ }, [globalPlaybackRate, mediaPlayerReady]);
218
235
  useEffect(() => {
219
236
  const mediaPlayer = mediaPlayerRef.current;
220
237
  if (!mediaPlayer || !mediaPlayerReady) {
@@ -222,6 +239,20 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
222
239
  }
223
240
  mediaPlayer.setLoop(loop);
224
241
  }, [loop, mediaPlayerReady]);
242
+ useEffect(() => {
243
+ const mediaPlayer = mediaPlayerRef.current;
244
+ if (!mediaPlayer || !mediaPlayerReady) {
245
+ return;
246
+ }
247
+ mediaPlayer.setIsPremounting(isPremounting);
248
+ }, [isPremounting, mediaPlayerReady]);
249
+ useEffect(() => {
250
+ const mediaPlayer = mediaPlayerRef.current;
251
+ if (!mediaPlayer || !mediaPlayerReady) {
252
+ return;
253
+ }
254
+ mediaPlayer.setIsPostmounting(isPostmounting);
255
+ }, [isPostmounting, mediaPlayerReady]);
225
256
  useEffect(() => {
226
257
  const mediaPlayer = mediaPlayerRef.current;
227
258
  if (!mediaPlayer || !mediaPlayerReady) {
@@ -231,14 +262,25 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
231
262
  }, [videoConfig.fps, mediaPlayerReady]);
232
263
  useEffect(() => {
233
264
  const mediaPlayer = mediaPlayerRef.current;
234
- if (!mediaPlayer || !mediaPlayerReady || !onVideoFrame) {
265
+ if (!mediaPlayer || !mediaPlayerReady) {
235
266
  return;
236
267
  }
237
- const unsubscribe = mediaPlayer.onVideoFrame(onVideoFrame);
238
- return () => {
239
- unsubscribe();
240
- };
268
+ mediaPlayer.setVideoFrameCallback(onVideoFrame ?? null);
241
269
  }, [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(() => {
278
+ const mediaPlayer = mediaPlayerRef.current;
279
+ if (!mediaPlayer || !mediaPlayerReady) {
280
+ return;
281
+ }
282
+ mediaPlayer.setTrimAfter(trimAfter);
283
+ }, [trimAfter, mediaPlayerReady]);
242
284
  const actualStyle = useMemo(() => {
243
285
  return {
244
286
  ...style,
@@ -252,3 +294,33 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
252
294
  }
253
295
  return (_jsx("canvas", { ref: canvasRef, width: videoConfig.width, height: videoConfig.height, style: actualStyle, className: classNameValue }));
254
296
  };
297
+ export const VideoForPreview = (props) => {
298
+ const frame = useCurrentFrame();
299
+ const videoConfig = useVideoConfig();
300
+ const currentTime = frame / videoConfig.fps;
301
+ const showShow = useMemo(() => {
302
+ return (getTimeInSeconds({
303
+ unloopedTimeInSeconds: currentTime,
304
+ playbackRate: props.playbackRate,
305
+ loop: props.loop,
306
+ trimBefore: props.trimBefore,
307
+ trimAfter: props.trimAfter,
308
+ mediaDurationInSeconds: Infinity,
309
+ fps: videoConfig.fps,
310
+ ifNoMediaDuration: 'infinity',
311
+ src: props.src,
312
+ }) !== null);
313
+ }, [
314
+ currentTime,
315
+ props.loop,
316
+ props.playbackRate,
317
+ props.src,
318
+ props.trimAfter,
319
+ props.trimBefore,
320
+ videoConfig.fps,
321
+ ]);
322
+ if (!showShow) {
323
+ return null;
324
+ }
325
+ return _jsx(VideoForPreviewAssertedShowing, { ...props });
326
+ };
@@ -28,6 +28,8 @@ const InnerVideo = ({ src, audioStreamIndex, className, delayRenderRetries, dela
28
28
  };
29
29
  export const Video = ({ src, audioStreamIndex, className, delayRenderRetries, delayRenderTimeoutInMilliseconds, disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps, logLevel, loop, loopVolumeCurveBehavior, muted, name, onVideoFrame, playbackRate, showInTimeline, style, trimAfter, trimBefore, volume, stack, toneFrequency, debugOverlay, }) => {
30
30
  return (_jsx(InnerVideo, { audioStreamIndex: audioStreamIndex ?? 0, className: className, delayRenderRetries: delayRenderRetries ?? null, delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds ?? null, disallowFallbackToOffthreadVideo: disallowFallbackToOffthreadVideo ?? false, fallbackOffthreadVideoProps: fallbackOffthreadVideoProps ?? {}, logLevel: logLevel ??
31
- (typeof window !== 'undefined' ? window.remotion_logLevel : 'info'), loop: loop ?? false, loopVolumeCurveBehavior: loopVolumeCurveBehavior ?? 'repeat', muted: muted ?? false, name: name, onVideoFrame: onVideoFrame, playbackRate: playbackRate ?? 1, showInTimeline: showInTimeline ?? true, src: src, style: style ?? {}, trimAfter: trimAfter, trimBefore: trimBefore, volume: volume ?? 1, toneFrequency: toneFrequency ?? 1, stack: stack, debugOverlay: debugOverlay ?? false }));
31
+ (typeof window !== 'undefined'
32
+ ? (window.remotion_logLevel ?? 'info')
33
+ : 'info'), loop: loop ?? false, loopVolumeCurveBehavior: loopVolumeCurveBehavior ?? 'repeat', muted: muted ?? false, name: name, onVideoFrame: onVideoFrame, playbackRate: playbackRate ?? 1, showInTimeline: showInTimeline ?? true, src: src, style: style ?? {}, trimAfter: trimAfter, trimBefore: trimBefore, volume: volume ?? 1, toneFrequency: toneFrequency ?? 1, stack: stack, debugOverlay: debugOverlay ?? false }));
32
34
  };
33
35
  Internals.addSequenceStackTraces(Video);
@@ -0,0 +1,37 @@
1
+ import type { InputVideoTrack, WrappedCanvas } from 'mediabunny';
2
+ import type { LogLevel } from 'remotion';
3
+ import type { Nonce } from './nonce-manager';
4
+ export declare const videoIteratorManager: ({ delayPlaybackHandleIfNotPremounting, canvas, context, drawDebugOverlay, logLevel, getOnVideoFrameCallback, videoTrack, }: {
5
+ videoTrack: InputVideoTrack;
6
+ delayPlaybackHandleIfNotPremounting: () => {
7
+ unblock: () => void;
8
+ };
9
+ context: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D;
10
+ canvas: OffscreenCanvas | HTMLCanvasElement;
11
+ getOnVideoFrameCallback: () => null | ((frame: CanvasImageSource) => void);
12
+ logLevel: LogLevel;
13
+ drawDebugOverlay: () => void;
14
+ }) => {
15
+ startVideoIterator: (timeToSeek: number, nonce: Nonce) => Promise<void>;
16
+ getVideoIteratorsCreated: () => number;
17
+ seek: ({ newTime, nonce }: {
18
+ newTime: number;
19
+ nonce: Nonce;
20
+ }) => Promise<void>;
21
+ destroy: () => void;
22
+ getVideoFrameIterator: () => {
23
+ destroy: () => void;
24
+ getNext: () => Promise<IteratorResult<WrappedCanvas, void>>;
25
+ isDestroyed: () => boolean;
26
+ tryToSatisfySeek: (time: number) => Promise<{
27
+ type: "not-satisfied";
28
+ reason: string;
29
+ } | {
30
+ type: "satisfied";
31
+ frame: WrappedCanvas;
32
+ }>;
33
+ } | null;
34
+ drawFrame: (frame: WrappedCanvas) => void;
35
+ getFramesRendered: () => number;
36
+ };
37
+ export type VideoIteratorManager = ReturnType<typeof videoIteratorManager>;
@@ -0,0 +1,83 @@
1
+ import { CanvasSink } from 'mediabunny';
2
+ import { Internals } from 'remotion';
3
+ import { createVideoIterator, } from './video/video-preview-iterator';
4
+ export const videoIteratorManager = ({ delayPlaybackHandleIfNotPremounting, canvas, context, drawDebugOverlay, logLevel, getOnVideoFrameCallback, videoTrack, }) => {
5
+ let videoIteratorsCreated = 0;
6
+ let videoFrameIterator = null;
7
+ let framesRendered = 0;
8
+ canvas.width = videoTrack.displayWidth;
9
+ canvas.height = videoTrack.displayHeight;
10
+ const canvasSink = new CanvasSink(videoTrack, {
11
+ poolSize: 2,
12
+ fit: 'contain',
13
+ alpha: true,
14
+ });
15
+ const drawFrame = (frame) => {
16
+ context.clearRect(0, 0, canvas.width, canvas.height);
17
+ context.drawImage(frame.canvas, 0, 0);
18
+ framesRendered++;
19
+ drawDebugOverlay();
20
+ const callback = getOnVideoFrameCallback();
21
+ if (callback) {
22
+ callback(canvas);
23
+ }
24
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[MediaPlayer] Drew frame ${frame.timestamp.toFixed(3)}s`);
25
+ };
26
+ const startVideoIterator = async (timeToSeek, nonce) => {
27
+ videoFrameIterator?.destroy();
28
+ const iterator = createVideoIterator(timeToSeek, canvasSink);
29
+ videoIteratorsCreated++;
30
+ videoFrameIterator = iterator;
31
+ const delayHandle = delayPlaybackHandleIfNotPremounting();
32
+ const frameResult = await iterator.getNext();
33
+ delayHandle.unblock();
34
+ if (iterator.isDestroyed()) {
35
+ return;
36
+ }
37
+ if (nonce.isStale()) {
38
+ return;
39
+ }
40
+ if (videoFrameIterator.isDestroyed()) {
41
+ return;
42
+ }
43
+ if (!frameResult.value) {
44
+ // media ended
45
+ return;
46
+ }
47
+ drawFrame(frameResult.value);
48
+ };
49
+ const seek = async ({ newTime, nonce }) => {
50
+ if (!videoFrameIterator) {
51
+ return;
52
+ }
53
+ // Should return immediately, so it's okay to not use Promise.all here
54
+ const videoSatisfyResult = await videoFrameIterator.tryToSatisfySeek(newTime);
55
+ // Doing this before the staleness check, because
56
+ // frame might be better than what we currently have
57
+ // TODO: check if this is actually true
58
+ if (videoSatisfyResult.type === 'satisfied') {
59
+ drawFrame(videoSatisfyResult.frame);
60
+ return;
61
+ }
62
+ if (nonce.isStale()) {
63
+ return;
64
+ }
65
+ // Intentionally not awaited, letting audio start as well
66
+ startVideoIterator(newTime, nonce).catch(() => {
67
+ // Ignore errors, might be stale or disposed
68
+ });
69
+ };
70
+ return {
71
+ startVideoIterator,
72
+ getVideoIteratorsCreated: () => videoIteratorsCreated,
73
+ seek,
74
+ destroy: () => {
75
+ videoFrameIterator?.destroy();
76
+ context.clearRect(0, 0, canvas.width, canvas.height);
77
+ videoFrameIterator = null;
78
+ },
79
+ getVideoFrameIterator: () => videoFrameIterator,
80
+ drawFrame,
81
+ getFramesRendered: () => framesRendered,
82
+ };
83
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.365",
3
+ "version": "4.0.367",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "mediabunny": "1.24.2",
25
- "remotion": "4.0.365",
25
+ "remotion": "4.0.367",
26
26
  "webdriverio": "9.19.2"
27
27
  },
28
28
  "peerDependencies": {
@@ -30,7 +30,7 @@
30
30
  "react-dom": ">=16.8.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@remotion/eslint-config-internal": "4.0.365",
33
+ "@remotion/eslint-config-internal": "4.0.367",
34
34
  "@vitest/browser": "^3.2.4",
35
35
  "eslint": "9.19.0",
36
36
  "react": "19.0.0",