@remotion/media 4.0.363 → 4.0.364

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,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { Html5Video, Internals, useBufferState, useCurrentFrame } from 'remotion';
4
+ import { MediaPlayer } from '../media-player';
4
5
  import { useLoopDisplay } from '../show-in-timeline';
5
6
  import { useMediaInTimeline } from '../use-media-in-timeline';
6
- import { MediaPlayer } from './media-player';
7
7
  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, }) => {
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
9
  const src = usePreload(unpreloadedSrc);
10
10
  const canvasRef = useRef(null);
11
11
  const videoConfig = useUnsafeVideoConfig();
@@ -56,9 +56,6 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
56
56
  if (!videoConfig) {
57
57
  throw new Error('No video config found');
58
58
  }
59
- if (!src) {
60
- throw new TypeError('No `src` was passed to <NewVideoForPreview>.');
61
- }
62
59
  const currentTime = frame / videoConfig.fps;
63
60
  const currentTimeRef = useRef(currentTime);
64
61
  currentTimeRef.current = currentTime;
@@ -82,11 +79,16 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
82
79
  fps: videoConfig.fps,
83
80
  playbackRate,
84
81
  audioStreamIndex,
82
+ debugOverlay,
83
+ bufferState: buffer,
85
84
  });
86
85
  mediaPlayerRef.current = player;
87
86
  player
88
87
  .initialize(currentTimeRef.current)
89
88
  .then((result) => {
89
+ if (result.type === 'disposed') {
90
+ return;
91
+ }
90
92
  if (result.type === 'unknown-container-format') {
91
93
  if (disallowFallbackToOffthreadVideo) {
92
94
  throw new Error(`Unknown container format ${preloadedSrc}, and 'disallowFallbackToOffthreadVideo' was set.`);
@@ -125,17 +127,17 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
125
127
  }
126
128
  })
127
129
  .catch((error) => {
128
- Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewVideoForPreview] Failed to initialize MediaPlayer', error);
130
+ Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[VideoForPreview] Failed to initialize MediaPlayer', error);
129
131
  setShouldFallbackToNativeVideo(true);
130
132
  });
131
133
  }
132
134
  catch (error) {
133
- Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewVideoForPreview] MediaPlayer initialization failed', error);
135
+ Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[VideoForPreview] MediaPlayer initialization failed', error);
134
136
  setShouldFallbackToNativeVideo(true);
135
137
  }
136
138
  return () => {
137
139
  if (mediaPlayerRef.current) {
138
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[NewVideoForPreview] Disposing MediaPlayer`);
140
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[VideoForPreview] Disposing MediaPlayer`);
139
141
  mediaPlayerRef.current.dispose();
140
142
  mediaPlayerRef.current = null;
141
143
  }
@@ -153,6 +155,8 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
153
155
  playbackRate,
154
156
  disallowFallbackToOffthreadVideo,
155
157
  audioStreamIndex,
158
+ debugOverlay,
159
+ buffer,
156
160
  ]);
157
161
  const classNameValue = useMemo(() => {
158
162
  return [Internals.OBJECTFIT_CONTAIN_CLASS_NAME, className]
@@ -165,19 +169,19 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
165
169
  return;
166
170
  if (playing) {
167
171
  mediaPlayer.play().catch((error) => {
168
- Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewVideoForPreview] Failed to play', error);
172
+ Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[VideoForPreview] Failed to play', error);
169
173
  });
170
174
  }
171
175
  else {
172
176
  mediaPlayer.pause();
173
177
  }
174
178
  }, [playing, logLevel, mediaPlayerReady]);
175
- useEffect(() => {
179
+ useLayoutEffect(() => {
176
180
  const mediaPlayer = mediaPlayerRef.current;
177
181
  if (!mediaPlayer || !mediaPlayerReady)
178
182
  return;
179
183
  mediaPlayer.seekTo(currentTime);
180
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[NewVideoForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
184
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[VideoForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
181
185
  }, [currentTime, logLevel, mediaPlayerReady]);
182
186
  useEffect(() => {
183
187
  const mediaPlayer = mediaPlayerRef.current;
@@ -187,12 +191,12 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
187
191
  const unsubscribe = mediaPlayer.onBufferingChange((newBufferingState) => {
188
192
  if (newBufferingState && !currentBlock) {
189
193
  currentBlock = buffer.delayPlayback();
190
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[NewVideoForPreview] MediaPlayer buffering - blocking Remotion playback');
194
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[VideoForPreview] MediaPlayer buffering - blocking Remotion playback');
191
195
  }
192
196
  else if (!newBufferingState && currentBlock) {
193
197
  currentBlock.unblock();
194
198
  currentBlock = null;
195
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[NewVideoForPreview] MediaPlayer unbuffering - unblocking Remotion playback');
199
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[VideoForPreview] MediaPlayer unbuffering - unblocking Remotion playback');
196
200
  }
197
201
  });
198
202
  return () => {
@@ -217,6 +221,13 @@ export const VideoForPreview = ({ src: unpreloadedSrc, style, playbackRate, logL
217
221
  }
218
222
  mediaPlayer.setVolume(userPreferredVolume);
219
223
  }, [userPreferredVolume, mediaPlayerReady]);
224
+ useEffect(() => {
225
+ const mediaPlayer = mediaPlayerRef.current;
226
+ if (!mediaPlayer || !mediaPlayerReady) {
227
+ return;
228
+ }
229
+ mediaPlayer.setDebugOverlay(debugOverlay);
230
+ }, [debugOverlay, mediaPlayerReady]);
220
231
  const effectivePlaybackRate = useMemo(() => playbackRate * globalPlaybackRate, [playbackRate, globalPlaybackRate]);
221
232
  useEffect(() => {
222
233
  const mediaPlayer = mediaPlayerRef.current;
@@ -0,0 +1,14 @@
1
+ import type { CanvasSink, WrappedCanvas } from 'mediabunny';
2
+ export declare const createVideoIterator: (timeToSeek: number, videoSink: CanvasSink) => {
3
+ destroy: () => void;
4
+ getNext: () => Promise<IteratorResult<WrappedCanvas, void>>;
5
+ isDestroyed: () => boolean;
6
+ tryToSatisfySeek: (time: number) => Promise<{
7
+ type: "not-satisfied";
8
+ reason: string;
9
+ } | {
10
+ type: "satisfied";
11
+ frame: WrappedCanvas;
12
+ }>;
13
+ };
14
+ export type VideoIterator = ReturnType<typeof createVideoIterator>;
@@ -0,0 +1,122 @@
1
+ import { roundTo4Digits } from '../helpers/round-to-4-digits';
2
+ export const createVideoIterator = (timeToSeek, videoSink) => {
3
+ let destroyed = false;
4
+ const iterator = videoSink.canvases(timeToSeek);
5
+ let lastReturnedFrame = null;
6
+ let iteratorEnded = false;
7
+ const getNextOrNullIfNotAvailable = async () => {
8
+ const next = iterator.next();
9
+ const result = await Promise.race([
10
+ next,
11
+ new Promise((resolve) => {
12
+ Promise.resolve().then(() => resolve());
13
+ }),
14
+ ]);
15
+ if (!result) {
16
+ return {
17
+ type: 'need-to-wait-for-it',
18
+ waitPromise: async () => {
19
+ const res = await next;
20
+ if (res.value) {
21
+ lastReturnedFrame = res.value;
22
+ }
23
+ else {
24
+ iteratorEnded = true;
25
+ }
26
+ return res.value;
27
+ },
28
+ };
29
+ }
30
+ if (result.value) {
31
+ lastReturnedFrame = result.value;
32
+ }
33
+ else {
34
+ iteratorEnded = true;
35
+ }
36
+ return {
37
+ type: 'got-frame-or-end',
38
+ frame: result.value ?? null,
39
+ };
40
+ };
41
+ const destroy = () => {
42
+ destroyed = true;
43
+ lastReturnedFrame = null;
44
+ iterator.return().catch(() => undefined);
45
+ };
46
+ const tryToSatisfySeek = async (time) => {
47
+ if (lastReturnedFrame) {
48
+ const frameTimestamp = roundTo4Digits(lastReturnedFrame.timestamp);
49
+ if (roundTo4Digits(time) < frameTimestamp) {
50
+ return {
51
+ type: 'not-satisfied',
52
+ reason: `iterator is too far, most recently returned ${frameTimestamp}`,
53
+ };
54
+ }
55
+ const frameEndTimestamp = roundTo4Digits(lastReturnedFrame.timestamp + lastReturnedFrame.duration);
56
+ const timestamp = roundTo4Digits(time);
57
+ if (frameTimestamp <= timestamp && frameEndTimestamp > timestamp) {
58
+ return {
59
+ type: 'satisfied',
60
+ frame: lastReturnedFrame,
61
+ };
62
+ }
63
+ }
64
+ if (iteratorEnded) {
65
+ if (lastReturnedFrame) {
66
+ return {
67
+ type: 'satisfied',
68
+ frame: lastReturnedFrame,
69
+ };
70
+ }
71
+ return {
72
+ type: 'not-satisfied',
73
+ reason: 'iterator ended',
74
+ };
75
+ }
76
+ while (true) {
77
+ const frame = await getNextOrNullIfNotAvailable();
78
+ if (frame.type === 'need-to-wait-for-it') {
79
+ return {
80
+ type: 'not-satisfied',
81
+ reason: 'iterator did not have frame ready',
82
+ };
83
+ }
84
+ if (frame.type === 'got-frame-or-end') {
85
+ if (frame.frame === null) {
86
+ iteratorEnded = true;
87
+ if (lastReturnedFrame) {
88
+ return {
89
+ type: 'satisfied',
90
+ frame: lastReturnedFrame,
91
+ };
92
+ }
93
+ return {
94
+ type: 'not-satisfied',
95
+ reason: 'iterator ended and did not have frame ready',
96
+ };
97
+ }
98
+ const frameTimestamp = roundTo4Digits(frame.frame.timestamp);
99
+ const frameEndTimestamp = roundTo4Digits(frame.frame.timestamp + frame.frame.duration);
100
+ const timestamp = roundTo4Digits(time);
101
+ if (frameTimestamp <= timestamp && frameEndTimestamp > timestamp) {
102
+ return {
103
+ type: 'satisfied',
104
+ frame: frame.frame,
105
+ };
106
+ }
107
+ continue;
108
+ }
109
+ throw new Error('Unreachable');
110
+ }
111
+ };
112
+ return {
113
+ destroy,
114
+ getNext: () => {
115
+ return iterator.next();
116
+ },
117
+ isDestroyed: () => {
118
+ return destroyed;
119
+ },
120
+ tryToSatisfySeek,
121
+ };
122
+ };
@@ -3,7 +3,7 @@ import { Internals, useRemotionEnvironment } from 'remotion';
3
3
  import { VideoForPreview } from './video-for-preview';
4
4
  import { VideoForRendering } from './video-for-rendering';
5
5
  const { validateMediaTrimProps, resolveTrimProps, validateMediaProps } = Internals;
6
- const InnerVideo = ({ src, audioStreamIndex, className, delayRenderRetries, delayRenderTimeoutInMilliseconds, disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps, logLevel, loop, loopVolumeCurveBehavior, muted, name, onVideoFrame, playbackRate, style, trimAfter, trimBefore, volume, stack, toneFrequency, showInTimeline, }) => {
6
+ const InnerVideo = ({ src, audioStreamIndex, className, delayRenderRetries, delayRenderTimeoutInMilliseconds, disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps, logLevel, loop, loopVolumeCurveBehavior, muted, name, onVideoFrame, playbackRate, style, trimAfter, trimBefore, volume, stack, toneFrequency, showInTimeline, debugOverlay, }) => {
7
7
  const environment = useRemotionEnvironment();
8
8
  if (typeof src !== 'string') {
9
9
  throw new TypeError(`The \`<Video>\` tag requires a string for \`src\`, but got ${JSON.stringify(src)} instead.`);
@@ -24,10 +24,10 @@ const InnerVideo = ({ src, audioStreamIndex, className, delayRenderRetries, dela
24
24
  if (environment.isRendering) {
25
25
  return (_jsx(VideoForRendering, { audioStreamIndex: audioStreamIndex ?? 0, className: className, delayRenderRetries: delayRenderRetries ?? null, delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds ?? null, disallowFallbackToOffthreadVideo: disallowFallbackToOffthreadVideo ?? false, name: name, fallbackOffthreadVideoProps: fallbackOffthreadVideoProps, logLevel: logLevel, loop: loop, loopVolumeCurveBehavior: loopVolumeCurveBehavior, muted: muted, onVideoFrame: onVideoFrame, playbackRate: playbackRate, src: src, stack: stack, style: style, volume: volume, toneFrequency: toneFrequency, trimAfterValue: trimAfterValue, trimBeforeValue: trimBeforeValue }));
26
26
  }
27
- return (_jsx(VideoForPreview, { audioStreamIndex: audioStreamIndex ?? 0, className: className, name: name, logLevel: logLevel, loop: loop, loopVolumeCurveBehavior: loopVolumeCurveBehavior, muted: muted, onVideoFrame: onVideoFrame, playbackRate: playbackRate, src: src, style: style, volume: volume, showInTimeline: showInTimeline, trimAfter: trimAfterValue, trimBefore: trimBeforeValue, stack: stack ?? null, disallowFallbackToOffthreadVideo: disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps: fallbackOffthreadVideoProps }));
27
+ return (_jsx(VideoForPreview, { audioStreamIndex: audioStreamIndex ?? 0, className: className, name: name, logLevel: logLevel, loop: loop, loopVolumeCurveBehavior: loopVolumeCurveBehavior, muted: muted, onVideoFrame: onVideoFrame, playbackRate: playbackRate, src: src, style: style, volume: volume, showInTimeline: showInTimeline, trimAfter: trimAfterValue, trimBefore: trimBeforeValue, stack: stack ?? null, disallowFallbackToOffthreadVideo: disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps: fallbackOffthreadVideoProps, debugOverlay: debugOverlay ?? false }));
28
28
  };
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, }) => {
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 }));
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 }));
32
32
  };
33
33
  Internals.addSequenceStackTraces(Video);
@@ -1,10 +1,7 @@
1
1
  import { Internals } from 'remotion';
2
2
  import { SAFE_BACK_WINDOW_IN_SECONDS } from '../caches';
3
+ import { roundTo4Digits } from '../helpers/round-to-4-digits';
3
4
  import { renderTimestampRange } from '../render-timestamp-range';
4
- // Round to only 4 digits, because WebM has a timescale of 1_000, e.g. framer.webm
5
- const roundTo4Digits = (timestamp) => {
6
- return Math.round(timestamp * 1000) / 1000;
7
- };
8
5
  export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSeconds, sampleIterator, logLevel: parentLogLevel, src, }) => {
9
6
  Internals.Log.verbose({ logLevel: parentLogLevel, tag: '@remotion/media' }, `Creating keyframe bank from ${startTimestampInSeconds}sec to ${endTimestampInSeconds}sec`);
10
7
  const frames = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.363",
3
+ "version": "4.0.364",
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.23.0",
25
- "remotion": "4.0.363",
24
+ "mediabunny": "1.24.1",
25
+ "remotion": "4.0.364",
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.363",
33
+ "@remotion/eslint-config-internal": "4.0.364",
34
34
  "@vitest/browser": "^3.2.4",
35
35
  "eslint": "9.19.0",
36
36
  "react": "19.0.0",