@remotion/media 4.0.365 → 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 +91 -12
- package/dist/audio/audio-preview-iterator.d.ts +9 -3
- package/dist/audio/audio-preview-iterator.js +70 -42
- 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 +6 -14
- package/dist/debug-overlay/preview-overlay.js +13 -8
- package/dist/esm/index.mjs +866 -466
- package/dist/media-player.d.ts +26 -24
- package/dist/media-player.js +181 -286
- package/dist/nonce-manager.d.ts +9 -0
- package/dist/nonce-manager.js +13 -0
- package/dist/video/video-for-preview.js +83 -11
- package/dist/video-iterator-manager.d.ts +37 -0
- package/dist/video-iterator-manager.js +83 -0
- package/package.json +3 -3
|
@@ -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,
|
|
@@ -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(
|
|
217
|
-
}, [
|
|
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
|
|
265
|
+
if (!mediaPlayer || !mediaPlayerReady) {
|
|
235
266
|
return;
|
|
236
267
|
}
|
|
237
|
-
|
|
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
|
+
};
|
|
@@ -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",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"mediabunny": "1.24.2",
|
|
25
|
-
"remotion": "4.0.
|
|
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",
|