@remotion/media 4.0.364 → 4.0.366
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.
- package/dist/audio/audio-for-preview.js +99 -33
- package/dist/audio/audio-preview-iterator.d.ts +30 -7
- package/dist/audio/audio-preview-iterator.js +173 -20
- package/dist/audio-iterator-manager.d.ts +66 -0
- package/dist/audio-iterator-manager.js +181 -0
- package/dist/calculate-playbacktime.d.ts +5 -0
- package/dist/calculate-playbacktime.js +4 -0
- package/dist/debug-overlay/preview-overlay.d.ts +11 -5
- package/dist/debug-overlay/preview-overlay.js +37 -8
- package/dist/esm/index.mjs +1023 -550
- package/dist/media-player.d.ts +28 -34
- package/dist/media-player.js +187 -313
- package/dist/nonce-manager.d.ts +9 -0
- package/dist/nonce-manager.js +13 -0
- package/dist/video/video-for-preview.js +91 -40
- package/dist/video-iterator-manager.d.ts +37 -0
- package/dist/video-iterator-manager.js +83 -0
- package/package.json +4 -4
|
@@ -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
|
-
|
|
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,
|
|
@@ -60,6 +63,11 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
|
|
|
60
63
|
const currentTimeRef = useRef(currentTime);
|
|
61
64
|
currentTimeRef.current = currentTime;
|
|
62
65
|
const preloadedSrc = usePreload(src);
|
|
66
|
+
const buffering = useContext(Internals.BufferingContextReact);
|
|
67
|
+
if (!buffering) {
|
|
68
|
+
throw new Error('useMediaPlayback must be used inside a <BufferingContext>');
|
|
69
|
+
}
|
|
70
|
+
const isPlayerBuffering = Internals.useIsPlayerBuffering(buffering);
|
|
63
71
|
useEffect(() => {
|
|
64
72
|
if (!canvasRef.current)
|
|
65
73
|
return;
|
|
@@ -81,6 +89,9 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
|
|
|
81
89
|
audioStreamIndex,
|
|
82
90
|
debugOverlay,
|
|
83
91
|
bufferState: buffer,
|
|
92
|
+
isPremounting,
|
|
93
|
+
isPostmounting,
|
|
94
|
+
globalPlaybackRate,
|
|
84
95
|
});
|
|
85
96
|
mediaPlayerRef.current = player;
|
|
86
97
|
player
|
|
@@ -157,6 +168,9 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
|
|
|
157
168
|
audioStreamIndex,
|
|
158
169
|
debugOverlay,
|
|
159
170
|
buffer,
|
|
171
|
+
isPremounting,
|
|
172
|
+
isPostmounting,
|
|
173
|
+
globalPlaybackRate,
|
|
160
174
|
]);
|
|
161
175
|
const classNameValue = useMemo(() => {
|
|
162
176
|
return [Internals.OBJECTFIT_CONTAIN_CLASS_NAME, className]
|
|
@@ -167,46 +181,22 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
|
|
|
167
181
|
const mediaPlayer = mediaPlayerRef.current;
|
|
168
182
|
if (!mediaPlayer)
|
|
169
183
|
return;
|
|
170
|
-
if (playing) {
|
|
171
|
-
mediaPlayer.play(
|
|
172
|
-
Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[VideoForPreview] Failed to play', error);
|
|
173
|
-
});
|
|
184
|
+
if (playing && !isPlayerBuffering) {
|
|
185
|
+
mediaPlayer.play(currentTimeRef.current);
|
|
174
186
|
}
|
|
175
187
|
else {
|
|
176
188
|
mediaPlayer.pause();
|
|
177
189
|
}
|
|
178
|
-
}, [playing, logLevel, mediaPlayerReady]);
|
|
190
|
+
}, [isPlayerBuffering, playing, logLevel, mediaPlayerReady]);
|
|
179
191
|
useLayoutEffect(() => {
|
|
180
192
|
const mediaPlayer = mediaPlayerRef.current;
|
|
181
193
|
if (!mediaPlayer || !mediaPlayerReady)
|
|
182
194
|
return;
|
|
183
|
-
mediaPlayer.seekTo(currentTime)
|
|
195
|
+
mediaPlayer.seekTo(currentTime).catch(() => {
|
|
196
|
+
// Might be disposed
|
|
197
|
+
});
|
|
184
198
|
Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[VideoForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
|
|
185
199
|
}, [currentTime, logLevel, mediaPlayerReady]);
|
|
186
|
-
useEffect(() => {
|
|
187
|
-
const mediaPlayer = mediaPlayerRef.current;
|
|
188
|
-
if (!mediaPlayer || !mediaPlayerReady)
|
|
189
|
-
return;
|
|
190
|
-
let currentBlock = null;
|
|
191
|
-
const unsubscribe = mediaPlayer.onBufferingChange((newBufferingState) => {
|
|
192
|
-
if (newBufferingState && !currentBlock) {
|
|
193
|
-
currentBlock = buffer.delayPlayback();
|
|
194
|
-
Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[VideoForPreview] MediaPlayer buffering - blocking Remotion playback');
|
|
195
|
-
}
|
|
196
|
-
else if (!newBufferingState && currentBlock) {
|
|
197
|
-
currentBlock.unblock();
|
|
198
|
-
currentBlock = null;
|
|
199
|
-
Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[VideoForPreview] MediaPlayer unbuffering - unblocking Remotion playback');
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
return () => {
|
|
203
|
-
unsubscribe();
|
|
204
|
-
if (currentBlock) {
|
|
205
|
-
currentBlock.unblock();
|
|
206
|
-
currentBlock = null;
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
}, [mediaPlayerReady, buffer, logLevel]);
|
|
210
200
|
const effectiveMuted = isSequenceHidden || muted || mediaMuted || userPreferredVolume <= 0;
|
|
211
201
|
useEffect(() => {
|
|
212
202
|
const mediaPlayer = mediaPlayerRef.current;
|
|
@@ -228,14 +218,20 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
|
|
|
228
218
|
}
|
|
229
219
|
mediaPlayer.setDebugOverlay(debugOverlay);
|
|
230
220
|
}, [debugOverlay, mediaPlayerReady]);
|
|
231
|
-
const effectivePlaybackRate = useMemo(() => playbackRate * globalPlaybackRate, [playbackRate, globalPlaybackRate]);
|
|
232
221
|
useEffect(() => {
|
|
233
222
|
const mediaPlayer = mediaPlayerRef.current;
|
|
234
223
|
if (!mediaPlayer || !mediaPlayerReady) {
|
|
235
224
|
return;
|
|
236
225
|
}
|
|
237
|
-
mediaPlayer.setPlaybackRate(
|
|
238
|
-
}, [
|
|
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]);
|
|
239
235
|
useEffect(() => {
|
|
240
236
|
const mediaPlayer = mediaPlayerRef.current;
|
|
241
237
|
if (!mediaPlayer || !mediaPlayerReady) {
|
|
@@ -243,6 +239,20 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
|
|
|
243
239
|
}
|
|
244
240
|
mediaPlayer.setLoop(loop);
|
|
245
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]);
|
|
246
256
|
useEffect(() => {
|
|
247
257
|
const mediaPlayer = mediaPlayerRef.current;
|
|
248
258
|
if (!mediaPlayer || !mediaPlayerReady) {
|
|
@@ -252,14 +262,25 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
|
|
|
252
262
|
}, [videoConfig.fps, mediaPlayerReady]);
|
|
253
263
|
useEffect(() => {
|
|
254
264
|
const mediaPlayer = mediaPlayerRef.current;
|
|
255
|
-
if (!mediaPlayer || !mediaPlayerReady
|
|
265
|
+
if (!mediaPlayer || !mediaPlayerReady) {
|
|
256
266
|
return;
|
|
257
267
|
}
|
|
258
|
-
|
|
259
|
-
return () => {
|
|
260
|
-
unsubscribe();
|
|
261
|
-
};
|
|
268
|
+
mediaPlayer.setVideoFrameCallback(onVideoFrame ?? null);
|
|
262
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]);
|
|
263
284
|
const actualStyle = useMemo(() => {
|
|
264
285
|
return {
|
|
265
286
|
...style,
|
|
@@ -273,3 +294,33 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
|
|
|
273
294
|
}
|
|
274
295
|
return (_jsx("canvas", { ref: canvasRef, width: videoConfig.width, height: videoConfig.height, style: actualStyle, className: classNameValue }));
|
|
275
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
|
+
};
|
|
@@ -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.
|
|
3
|
+
"version": "4.0.366",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"module": "dist/esm/index.mjs",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"make": "tsc -d && bun --env-file=../.env.bundle bundle.ts"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"mediabunny": "1.24.
|
|
25
|
-
"remotion": "4.0.
|
|
24
|
+
"mediabunny": "1.24.2",
|
|
25
|
+
"remotion": "4.0.366",
|
|
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.
|
|
33
|
+
"@remotion/eslint-config-internal": "4.0.366",
|
|
34
34
|
"@vitest/browser": "^3.2.4",
|
|
35
35
|
"eslint": "9.19.0",
|
|
36
36
|
"react": "19.0.0",
|