@remotion/media 4.0.356 → 4.0.357
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.d.ts +30 -0
- package/dist/audio/audio-for-preview.js +213 -0
- package/dist/audio/audio-for-rendering.js +32 -15
- package/dist/audio/audio.js +7 -49
- package/dist/audio/props.d.ts +8 -14
- package/dist/audio-extraction/audio-cache.d.ts +1 -1
- package/dist/audio-extraction/audio-cache.js +5 -1
- package/dist/audio-extraction/audio-iterator.d.ts +4 -1
- package/dist/audio-extraction/audio-iterator.js +22 -10
- package/dist/audio-extraction/audio-manager.d.ts +8 -37
- package/dist/audio-extraction/audio-manager.js +35 -8
- package/dist/audio-extraction/extract-audio.d.ts +9 -2
- package/dist/audio-extraction/extract-audio.js +28 -15
- package/dist/caches.d.ts +9 -44
- package/dist/convert-audiodata/apply-tonefrequency.js +0 -1
- package/dist/convert-audiodata/combine-audiodata.js +2 -23
- package/dist/convert-audiodata/convert-audiodata.d.ts +1 -5
- package/dist/convert-audiodata/convert-audiodata.js +16 -24
- package/dist/convert-audiodata/wsola.js +1 -1
- package/dist/esm/index.mjs +2681 -2162
- package/dist/extract-frame-and-audio.d.ts +6 -7
- package/dist/extract-frame-and-audio.js +28 -19
- package/dist/get-sink-weak.d.ts +1 -1
- package/dist/get-sink-weak.js +3 -11
- package/dist/get-sink.d.ts +13 -0
- package/dist/get-sink.js +15 -0
- package/dist/get-time-in-seconds.d.ts +10 -0
- package/dist/get-time-in-seconds.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/is-network-error.d.ts +6 -0
- package/dist/is-network-error.js +17 -0
- package/dist/render-timestamp-range.d.ts +1 -0
- package/dist/render-timestamp-range.js +9 -0
- package/dist/video/media-player.d.ts +28 -7
- package/dist/video/media-player.js +123 -58
- package/dist/video/props.d.ts +1 -0
- package/dist/video/resolve-playback-time.d.ts +8 -0
- package/dist/video/resolve-playback-time.js +22 -0
- package/dist/video/video-for-preview.d.ts +8 -0
- package/dist/video/video-for-preview.js +113 -90
- package/dist/video/video-for-rendering.d.ts +3 -0
- package/dist/video/video-for-rendering.js +58 -25
- package/dist/video/video.js +6 -10
- package/dist/video-extraction/extract-frame-via-broadcast-channel.d.ts +18 -6
- package/dist/video-extraction/extract-frame-via-broadcast-channel.js +21 -7
- package/dist/video-extraction/extract-frame.d.ts +20 -2
- package/dist/video-extraction/extract-frame.js +40 -9
- package/dist/video-extraction/get-frames-since-keyframe.d.ts +5 -3
- package/dist/video-extraction/get-frames-since-keyframe.js +7 -4
- package/dist/video-extraction/keyframe-bank.d.ts +3 -2
- package/dist/video-extraction/keyframe-bank.js +32 -12
- package/dist/video-extraction/keyframe-manager.d.ts +3 -8
- package/dist/video-extraction/keyframe-manager.js +25 -10
- package/package.json +4 -4
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { LogLevel, LoopVolumeCurveBehavior, VolumeProp } from 'remotion';
|
|
3
|
+
import type { FallbackHtml5AudioProps } from './props';
|
|
4
|
+
type InnerAudioProps = {
|
|
5
|
+
readonly loop?: boolean;
|
|
6
|
+
readonly src: string;
|
|
7
|
+
readonly logLevel?: LogLevel;
|
|
8
|
+
readonly muted?: boolean;
|
|
9
|
+
readonly name?: string | undefined;
|
|
10
|
+
readonly volume?: VolumeProp;
|
|
11
|
+
readonly loopVolumeCurveBehavior?: LoopVolumeCurveBehavior;
|
|
12
|
+
readonly playbackRate?: number;
|
|
13
|
+
readonly _remotionInternalNativeLoopPassed?: boolean;
|
|
14
|
+
readonly _remotionInternalStack?: string | null;
|
|
15
|
+
readonly shouldPreMountAudioTags?: boolean;
|
|
16
|
+
readonly onNativeError?: React.ReactEventHandler<HTMLAudioElement>;
|
|
17
|
+
readonly onDuration?: (src: string, durationInSeconds: number) => void;
|
|
18
|
+
readonly pauseWhenBuffering?: boolean;
|
|
19
|
+
readonly _remotionInternalNeedsDurationCalculation?: boolean;
|
|
20
|
+
readonly showInTimeline?: boolean;
|
|
21
|
+
readonly trimAfter?: number | undefined;
|
|
22
|
+
readonly trimBefore?: number | undefined;
|
|
23
|
+
readonly stack: string | null;
|
|
24
|
+
readonly disallowFallbackToHtml5Audio?: boolean;
|
|
25
|
+
readonly toneFrequency?: number;
|
|
26
|
+
readonly audioStreamIndex?: number;
|
|
27
|
+
readonly fallbackHtml5AudioProps?: FallbackHtml5AudioProps;
|
|
28
|
+
};
|
|
29
|
+
export declare const AudioForPreview: React.FC<InnerAudioProps>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Internals, Audio as RemotionAudio, useBufferState, useCurrentFrame, } from 'remotion';
|
|
4
|
+
import { MediaPlayer } from '../video/media-player';
|
|
5
|
+
const { useUnsafeVideoConfig, Timeline, SharedAudioContext, useMediaMutedState, useMediaVolumeState, useFrameForVolumeProp, evaluateVolume, warnAboutTooHighVolume, usePreload, useMediaInTimeline, SequenceContext, } = Internals;
|
|
6
|
+
const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVolumeCurveBehavior, loop, trimAfter, trimBefore, name, showInTimeline, stack, disallowFallbackToHtml5Audio, toneFrequency, audioStreamIndex, fallbackHtml5AudioProps, }) => {
|
|
7
|
+
const videoConfig = useUnsafeVideoConfig();
|
|
8
|
+
const frame = useCurrentFrame();
|
|
9
|
+
const mediaPlayerRef = useRef(null);
|
|
10
|
+
const [mediaPlayerReady, setMediaPlayerReady] = useState(false);
|
|
11
|
+
const [shouldFallbackToNativeAudio, setShouldFallbackToNativeAudio] = useState(false);
|
|
12
|
+
const [playing] = Timeline.usePlayingState();
|
|
13
|
+
const timelineContext = useContext(Timeline.TimelineContext);
|
|
14
|
+
const globalPlaybackRate = timelineContext.playbackRate;
|
|
15
|
+
const sharedAudioContext = useContext(SharedAudioContext);
|
|
16
|
+
const buffer = useBufferState();
|
|
17
|
+
const delayHandleRef = useRef(null);
|
|
18
|
+
const [mediaMuted] = useMediaMutedState();
|
|
19
|
+
const [mediaVolume] = useMediaVolumeState();
|
|
20
|
+
const volumePropFrame = useFrameForVolumeProp(loopVolumeCurveBehavior ?? 'repeat');
|
|
21
|
+
const userPreferredVolume = evaluateVolume({
|
|
22
|
+
frame: volumePropFrame,
|
|
23
|
+
volume,
|
|
24
|
+
mediaVolume,
|
|
25
|
+
});
|
|
26
|
+
warnAboutTooHighVolume(userPreferredVolume);
|
|
27
|
+
if (!videoConfig) {
|
|
28
|
+
throw new Error('No video config found');
|
|
29
|
+
}
|
|
30
|
+
if (!src) {
|
|
31
|
+
throw new TypeError('No `src` was passed to <NewAudioForPreview>.');
|
|
32
|
+
}
|
|
33
|
+
const currentTime = frame / videoConfig.fps;
|
|
34
|
+
const currentTimeRef = useRef(currentTime);
|
|
35
|
+
currentTimeRef.current = currentTime;
|
|
36
|
+
const preloadedSrc = usePreload(src);
|
|
37
|
+
const [timelineId] = useState(() => String(Math.random()));
|
|
38
|
+
const parentSequence = useContext(SequenceContext);
|
|
39
|
+
useMediaInTimeline({
|
|
40
|
+
volume,
|
|
41
|
+
mediaVolume,
|
|
42
|
+
mediaType: 'audio',
|
|
43
|
+
src,
|
|
44
|
+
playbackRate,
|
|
45
|
+
displayName: name ?? null,
|
|
46
|
+
id: timelineId,
|
|
47
|
+
stack,
|
|
48
|
+
showInTimeline,
|
|
49
|
+
premountDisplay: parentSequence?.premountDisplay ?? null,
|
|
50
|
+
postmountDisplay: parentSequence?.postmountDisplay ?? null,
|
|
51
|
+
});
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!sharedAudioContext)
|
|
54
|
+
return;
|
|
55
|
+
if (!sharedAudioContext.audioContext)
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
const player = new MediaPlayer({
|
|
59
|
+
src: preloadedSrc,
|
|
60
|
+
logLevel,
|
|
61
|
+
sharedAudioContext: sharedAudioContext.audioContext,
|
|
62
|
+
loop,
|
|
63
|
+
trimAfterSeconds: trimAfter ? trimAfter / videoConfig.fps : undefined,
|
|
64
|
+
trimBeforeSeconds: trimBefore
|
|
65
|
+
? trimBefore / videoConfig.fps
|
|
66
|
+
: undefined,
|
|
67
|
+
canvas: null,
|
|
68
|
+
playbackRate,
|
|
69
|
+
audioStreamIndex: audioStreamIndex ?? 0,
|
|
70
|
+
});
|
|
71
|
+
mediaPlayerRef.current = player;
|
|
72
|
+
player
|
|
73
|
+
.initialize(currentTimeRef.current)
|
|
74
|
+
.then((result) => {
|
|
75
|
+
if (result.type === 'unknown-container-format') {
|
|
76
|
+
if (disallowFallbackToHtml5Audio) {
|
|
77
|
+
throw new Error(`Unknown container format ${preloadedSrc}, and 'disallowFallbackToHtml5Audio' was set.`);
|
|
78
|
+
}
|
|
79
|
+
Internals.Log.warn({ logLevel, tag: '@remotion/media' }, `Unknown container format for ${preloadedSrc} (Supported formats: https://www.remotion.dev/docs/mediabunny/formats), falling back to <Audio>`);
|
|
80
|
+
setShouldFallbackToNativeAudio(true);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (result.type === 'network-error') {
|
|
84
|
+
if (disallowFallbackToHtml5Audio) {
|
|
85
|
+
throw new Error(`Network error fetching ${preloadedSrc}, and 'disallowFallbackToHtml5Audio' was set.`);
|
|
86
|
+
}
|
|
87
|
+
Internals.Log.warn({ logLevel, tag: '@remotion/media' }, `Network error fetching ${preloadedSrc}, falling back to <Audio>`);
|
|
88
|
+
setShouldFallbackToNativeAudio(true);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (result.type === 'cannot-decode') {
|
|
92
|
+
if (disallowFallbackToHtml5Audio) {
|
|
93
|
+
throw new Error(`Cannot decode ${preloadedSrc}, and 'disallowFallbackToHtml5Audio' was set.`);
|
|
94
|
+
}
|
|
95
|
+
Internals.Log.warn({ logLevel, tag: '@remotion/media' }, `Cannot decode ${preloadedSrc}, falling back to <Audio>`);
|
|
96
|
+
setShouldFallbackToNativeAudio(true);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (result.type === 'no-tracks') {
|
|
100
|
+
if (disallowFallbackToHtml5Audio) {
|
|
101
|
+
throw new Error(`No video or audio tracks found for ${preloadedSrc}, and 'disallowFallbackToHtml5Audio' was set.`);
|
|
102
|
+
}
|
|
103
|
+
Internals.Log.warn({ logLevel, tag: '@remotion/media' }, `No video or audio tracks found for ${preloadedSrc}, falling back to <Audio>`);
|
|
104
|
+
setShouldFallbackToNativeAudio(true);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (result.type === 'success') {
|
|
108
|
+
setMediaPlayerReady(true);
|
|
109
|
+
Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[NewAudioForPreview] MediaPlayer initialized successfully`);
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
.catch((error) => {
|
|
113
|
+
Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] Failed to initialize MediaPlayer', error);
|
|
114
|
+
setShouldFallbackToNativeAudio(true);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] MediaPlayer initialization failed', error);
|
|
119
|
+
setShouldFallbackToNativeAudio(true);
|
|
120
|
+
}
|
|
121
|
+
return () => {
|
|
122
|
+
if (delayHandleRef.current) {
|
|
123
|
+
delayHandleRef.current.unblock();
|
|
124
|
+
delayHandleRef.current = null;
|
|
125
|
+
}
|
|
126
|
+
if (mediaPlayerRef.current) {
|
|
127
|
+
Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[NewAudioForPreview] Disposing MediaPlayer`);
|
|
128
|
+
mediaPlayerRef.current.dispose();
|
|
129
|
+
mediaPlayerRef.current = null;
|
|
130
|
+
}
|
|
131
|
+
setMediaPlayerReady(false);
|
|
132
|
+
setShouldFallbackToNativeAudio(false);
|
|
133
|
+
};
|
|
134
|
+
}, [
|
|
135
|
+
preloadedSrc,
|
|
136
|
+
logLevel,
|
|
137
|
+
sharedAudioContext,
|
|
138
|
+
currentTimeRef,
|
|
139
|
+
loop,
|
|
140
|
+
trimAfter,
|
|
141
|
+
trimBefore,
|
|
142
|
+
playbackRate,
|
|
143
|
+
videoConfig.fps,
|
|
144
|
+
audioStreamIndex,
|
|
145
|
+
disallowFallbackToHtml5Audio,
|
|
146
|
+
]);
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const audioPlayer = mediaPlayerRef.current;
|
|
149
|
+
if (!audioPlayer)
|
|
150
|
+
return;
|
|
151
|
+
if (playing) {
|
|
152
|
+
audioPlayer.play().catch((error) => {
|
|
153
|
+
Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] Failed to play', error);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
audioPlayer.pause();
|
|
158
|
+
}
|
|
159
|
+
}, [playing, logLevel, mediaPlayerReady]);
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
const audioPlayer = mediaPlayerRef.current;
|
|
162
|
+
if (!audioPlayer || !mediaPlayerReady)
|
|
163
|
+
return;
|
|
164
|
+
audioPlayer.seekTo(currentTime);
|
|
165
|
+
Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[NewAudioForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
|
|
166
|
+
}, [currentTime, logLevel, mediaPlayerReady]);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
const audioPlayer = mediaPlayerRef.current;
|
|
169
|
+
if (!audioPlayer || !mediaPlayerReady)
|
|
170
|
+
return;
|
|
171
|
+
audioPlayer.onBufferingChange((newBufferingState) => {
|
|
172
|
+
if (newBufferingState && !delayHandleRef.current) {
|
|
173
|
+
delayHandleRef.current = buffer.delayPlayback();
|
|
174
|
+
Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] MediaPlayer buffering - blocking Remotion playback');
|
|
175
|
+
}
|
|
176
|
+
else if (!newBufferingState && delayHandleRef.current) {
|
|
177
|
+
delayHandleRef.current.unblock();
|
|
178
|
+
delayHandleRef.current = null;
|
|
179
|
+
Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] MediaPlayer unbuffering - unblocking Remotion playback');
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}, [mediaPlayerReady, buffer, logLevel]);
|
|
183
|
+
const effectiveMuted = muted || mediaMuted || userPreferredVolume <= 0;
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
const audioPlayer = mediaPlayerRef.current;
|
|
186
|
+
if (!audioPlayer || !mediaPlayerReady)
|
|
187
|
+
return;
|
|
188
|
+
audioPlayer.setMuted(effectiveMuted);
|
|
189
|
+
}, [effectiveMuted, mediaPlayerReady]);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const audioPlayer = mediaPlayerRef.current;
|
|
192
|
+
if (!audioPlayer || !mediaPlayerReady) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
audioPlayer.setVolume(userPreferredVolume);
|
|
196
|
+
}, [userPreferredVolume, mediaPlayerReady, logLevel]);
|
|
197
|
+
const effectivePlaybackRate = useMemo(() => playbackRate * globalPlaybackRate, [playbackRate, globalPlaybackRate]);
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
const audioPlayer = mediaPlayerRef.current;
|
|
200
|
+
if (!audioPlayer || !mediaPlayerReady) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
audioPlayer.setPlaybackRate(effectivePlaybackRate);
|
|
204
|
+
}, [effectivePlaybackRate, mediaPlayerReady, logLevel]);
|
|
205
|
+
if (shouldFallbackToNativeAudio && !disallowFallbackToHtml5Audio) {
|
|
206
|
+
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, ...fallbackHtml5AudioProps }));
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
};
|
|
210
|
+
export const AudioForPreview = ({ loop, src, logLevel, muted, name, volume, loopVolumeCurveBehavior, playbackRate, trimAfter, trimBefore, showInTimeline, stack, disallowFallbackToHtml5Audio, toneFrequency, audioStreamIndex, fallbackHtml5AudioProps, }) => {
|
|
211
|
+
const preloadedSrc = usePreload(src);
|
|
212
|
+
return (_jsx(NewAudioForPreview, { audioStreamIndex: audioStreamIndex ?? 0, src: preloadedSrc, playbackRate: playbackRate ?? 1, logLevel: logLevel ?? window.remotion_logLevel, muted: muted ?? false, volume: volume ?? 1, loopVolumeCurveBehavior: loopVolumeCurveBehavior ?? 'repeat', loop: loop ?? false, trimAfter: trimAfter, trimBefore: trimBefore, name: name, showInTimeline: showInTimeline ?? true, stack: stack, disallowFallbackToHtml5Audio: disallowFallbackToHtml5Audio ?? false, toneFrequency: toneFrequency, fallbackHtml5AudioProps: fallbackHtml5AudioProps }));
|
|
213
|
+
};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useContext, useLayoutEffect, useState } from 'react';
|
|
3
|
-
import { Audio, cancelRender, Internals, useCurrentFrame, useDelayRender, useRemotionEnvironment, } from 'remotion';
|
|
2
|
+
import { useContext, useLayoutEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Audio, cancelRender, Internals, random, useCurrentFrame, useDelayRender, useRemotionEnvironment, } from 'remotion';
|
|
4
4
|
import { applyVolume } from '../convert-audiodata/apply-volume';
|
|
5
|
+
import { TARGET_SAMPLE_RATE } from '../convert-audiodata/resample-audiodata';
|
|
5
6
|
import { frameForVolumeProp } from '../looped-frame';
|
|
6
7
|
import { extractFrameViaBroadcastChannel } from '../video-extraction/extract-frame-via-broadcast-channel';
|
|
7
|
-
export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted, loopVolumeCurveBehavior, delayRenderRetries, delayRenderTimeoutInMilliseconds, logLevel = window.remotion_logLevel, loop, fallbackHtml5AudioProps, audioStreamIndex, showInTimeline, style, name, disallowFallbackToHtml5Audio, }) => {
|
|
8
|
+
export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted, loopVolumeCurveBehavior, delayRenderRetries, delayRenderTimeoutInMilliseconds, logLevel = window.remotion_logLevel, loop, fallbackHtml5AudioProps, audioStreamIndex, showInTimeline, style, name, disallowFallbackToHtml5Audio, toneFrequency, trimAfter, trimBefore, }) => {
|
|
8
9
|
const frame = useCurrentFrame();
|
|
9
10
|
const absoluteFrame = Internals.useTimelinePosition();
|
|
10
11
|
const videoConfig = Internals.useUnsafeVideoConfig();
|
|
11
12
|
const { registerRenderAsset, unregisterRenderAsset } = useContext(Internals.RenderAssetManager);
|
|
12
13
|
const startsAt = Internals.useMediaStartsAt();
|
|
13
14
|
const environment = useRemotionEnvironment();
|
|
14
|
-
const [id] = useState(() => `${Math.random()}`.replace('0.', ''));
|
|
15
15
|
if (!videoConfig) {
|
|
16
16
|
throw new Error('No video config found');
|
|
17
17
|
}
|
|
@@ -21,10 +21,21 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
|
|
|
21
21
|
const { fps } = videoConfig;
|
|
22
22
|
const { delayRender, continueRender } = useDelayRender();
|
|
23
23
|
const [replaceWithHtml5Audio, setReplaceWithHtml5Audio] = useState(false);
|
|
24
|
+
const sequenceContext = useContext(Internals.SequenceContext);
|
|
25
|
+
// Generate a string that's as unique as possible for this asset
|
|
26
|
+
// but at the same time the same on all threads
|
|
27
|
+
const id = useMemo(() => `media-video-${random(src)}-${sequenceContext?.cumulatedFrom}-${sequenceContext?.relativeFrom}-${sequenceContext?.durationInFrames}`, [
|
|
28
|
+
src,
|
|
29
|
+
sequenceContext?.cumulatedFrom,
|
|
30
|
+
sequenceContext?.relativeFrom,
|
|
31
|
+
sequenceContext?.durationInFrames,
|
|
32
|
+
]);
|
|
24
33
|
useLayoutEffect(() => {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
34
|
+
const timestamp = frame / fps;
|
|
35
|
+
const durationInSeconds = 1 / fps;
|
|
36
|
+
if (replaceWithHtml5Audio) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
28
39
|
const newHandle = delayRender(`Extracting audio for frame ${frame}`, {
|
|
29
40
|
retries: delayRenderRetries ?? undefined,
|
|
30
41
|
timeoutInMilliseconds: delayRenderTimeoutInMilliseconds ?? undefined,
|
|
@@ -43,15 +54,18 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
|
|
|
43
54
|
timeInSeconds: timestamp,
|
|
44
55
|
durationInSeconds,
|
|
45
56
|
playbackRate: playbackRate ?? 1,
|
|
46
|
-
logLevel
|
|
57
|
+
logLevel,
|
|
47
58
|
includeAudio: shouldRenderAudio,
|
|
48
59
|
includeVideo: false,
|
|
49
60
|
isClientSideRendering: environment.isClientSideRendering,
|
|
50
61
|
loop: loop ?? false,
|
|
51
62
|
audioStreamIndex: audioStreamIndex ?? 0,
|
|
63
|
+
trimAfter,
|
|
64
|
+
trimBefore,
|
|
65
|
+
fps,
|
|
52
66
|
})
|
|
53
67
|
.then((result) => {
|
|
54
|
-
if (result === 'unknown-container-format') {
|
|
68
|
+
if (result.type === 'unknown-container-format') {
|
|
55
69
|
if (disallowFallbackToHtml5Audio) {
|
|
56
70
|
cancelRender(new Error(`Unknown container format ${src}, and 'disallowFallbackToHtml5Audio' was set. Failing the render.`));
|
|
57
71
|
}
|
|
@@ -59,7 +73,7 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
|
|
|
59
73
|
setReplaceWithHtml5Audio(true);
|
|
60
74
|
return;
|
|
61
75
|
}
|
|
62
|
-
if (result === 'cannot-decode') {
|
|
76
|
+
if (result.type === 'cannot-decode') {
|
|
63
77
|
if (disallowFallbackToHtml5Audio) {
|
|
64
78
|
cancelRender(new Error(`Cannot decode ${src}, and 'disallowFallbackToHtml5Audio' was set. Failing the render.`));
|
|
65
79
|
}
|
|
@@ -67,7 +81,7 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
|
|
|
67
81
|
setReplaceWithHtml5Audio(true);
|
|
68
82
|
return;
|
|
69
83
|
}
|
|
70
|
-
if (result === 'network-error') {
|
|
84
|
+
if (result.type === 'network-error') {
|
|
71
85
|
if (disallowFallbackToHtml5Audio) {
|
|
72
86
|
cancelRender(new Error(`Cannot decode ${src}, and 'disallowFallbackToHtml5Audio' was set. Failing the render.`));
|
|
73
87
|
}
|
|
@@ -96,11 +110,10 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
|
|
|
96
110
|
type: 'inline-audio',
|
|
97
111
|
id,
|
|
98
112
|
audio: Array.from(audio.data),
|
|
99
|
-
sampleRate: audio.sampleRate,
|
|
100
|
-
numberOfChannels: audio.numberOfChannels,
|
|
101
113
|
frame: absoluteFrame,
|
|
102
114
|
timestamp: audio.timestamp,
|
|
103
|
-
duration: (audio.numberOfFrames /
|
|
115
|
+
duration: (audio.numberOfFrames / TARGET_SAMPLE_RATE) * 1000000,
|
|
116
|
+
toneFrequency: toneFrequency ?? 1,
|
|
104
117
|
});
|
|
105
118
|
}
|
|
106
119
|
continueRender(newHandle);
|
|
@@ -134,10 +147,14 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
|
|
|
134
147
|
unregisterRenderAsset,
|
|
135
148
|
volumeProp,
|
|
136
149
|
audioStreamIndex,
|
|
150
|
+
toneFrequency,
|
|
151
|
+
trimAfter,
|
|
152
|
+
trimBefore,
|
|
153
|
+
replaceWithHtml5Audio,
|
|
137
154
|
]);
|
|
138
155
|
if (replaceWithHtml5Audio) {
|
|
139
156
|
// TODO: Loop and other props
|
|
140
|
-
return (_jsx(Audio, { 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:
|
|
157
|
+
return (_jsx(Audio, { 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 }));
|
|
141
158
|
}
|
|
142
159
|
return null;
|
|
143
160
|
};
|
package/dist/audio/audio.js
CHANGED
|
@@ -1,63 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { SharedAudioContext } from '../../../core/src/audio/shared-audio-tags';
|
|
2
|
+
import { Internals, useRemotionEnvironment } from 'remotion';
|
|
3
|
+
import { AudioForPreview } from './audio-for-preview';
|
|
5
4
|
import { AudioForRendering } from './audio-for-rendering';
|
|
6
|
-
const {
|
|
7
|
-
// dummy function for now because onError is not supported
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
9
|
-
const onRemotionError = (_e) => { };
|
|
5
|
+
const { validateMediaProps } = Internals;
|
|
10
6
|
export const Audio = (props) => {
|
|
11
|
-
const audioContext = useContext(SharedAudioContext);
|
|
12
7
|
// Should only destruct `trimBefore` and `trimAfter` from props,
|
|
13
8
|
// rest gets drilled down
|
|
14
|
-
const {
|
|
9
|
+
const { name, stack, showInTimeline, ...otherProps } = props;
|
|
15
10
|
const environment = useRemotionEnvironment();
|
|
16
|
-
const onDuration = useCallback(() => undefined, []);
|
|
17
11
|
if (typeof props.src !== 'string') {
|
|
18
12
|
throw new TypeError(`The \`<Audio>\` tag requires a string for \`src\`, but got ${JSON.stringify(props.src)} instead.`);
|
|
19
13
|
}
|
|
20
|
-
validateMediaTrimProps({
|
|
21
|
-
startFrom: undefined,
|
|
22
|
-
endAt: undefined,
|
|
23
|
-
trimBefore,
|
|
24
|
-
trimAfter,
|
|
25
|
-
});
|
|
26
|
-
const { trimBeforeValue, trimAfterValue } = resolveTrimProps({
|
|
27
|
-
startFrom: undefined,
|
|
28
|
-
endAt: undefined,
|
|
29
|
-
trimBefore,
|
|
30
|
-
trimAfter,
|
|
31
|
-
});
|
|
32
|
-
const onError = useCallback((e) => {
|
|
33
|
-
// eslint-disable-next-line no-console
|
|
34
|
-
console.log(e.currentTarget.error);
|
|
35
|
-
// If there is no `loop` property, we don't need to get the duration
|
|
36
|
-
// and this does not need to be a fatal error
|
|
37
|
-
const errMessage = `Could not play audio: ${e.currentTarget.error}. See https://remotion.dev/docs/media-playback-error for help.`;
|
|
38
|
-
if (loop) {
|
|
39
|
-
if (onRemotionError) {
|
|
40
|
-
onRemotionError(new Error(errMessage));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
cancelRender(new Error(errMessage));
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
onRemotionError?.(new Error(errMessage));
|
|
47
|
-
// eslint-disable-next-line no-console
|
|
48
|
-
console.warn(errMessage);
|
|
49
|
-
}
|
|
50
|
-
}, [loop]);
|
|
51
|
-
if (typeof trimBeforeValue !== 'undefined' ||
|
|
52
|
-
typeof trimAfterValue !== 'undefined') {
|
|
53
|
-
return (_jsx(Sequence, { layout: "none", from: 0 - (trimBeforeValue ?? 0), showInTimeline: false, durationInFrames: trimAfterValue, name: name, children: _jsx(Audio, { pauseWhenBuffering: pauseWhenBuffering ?? false, ...otherProps }) }));
|
|
54
|
-
}
|
|
55
14
|
validateMediaProps({ playbackRate: props.playbackRate, volume: props.volume }, 'Audio');
|
|
56
15
|
if (environment.isRendering) {
|
|
57
16
|
return _jsx(AudioForRendering, { ...otherProps });
|
|
58
17
|
}
|
|
59
|
-
|
|
60
|
-
return (_jsx(AudioForPreview, { _remotionInternalNativeLoopPassed: props._remotionInternalNativeLoopPassed ?? false, _remotionInternalStack: stack ?? null, shouldPreMountAudioTags: audioContext !== null && audioContext.numberOfAudioTags > 0, ...propsForPreview, onNativeError: onError, onDuration: onDuration,
|
|
61
|
-
// Proposal: Make this default to true in v5
|
|
62
|
-
pauseWhenBuffering: pauseWhenBuffering ?? false, _remotionInternalNeedsDurationCalculation: Boolean(loop), showInTimeline: showInTimeline ?? true }));
|
|
18
|
+
return _jsx(AudioForPreview, { name: name, ...otherProps, stack: stack ?? null });
|
|
63
19
|
};
|
|
20
|
+
// TODO: Doesn't work
|
|
21
|
+
Internals.addSequenceStackTraces(Audio);
|
package/dist/audio/props.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { LogLevel, LoopVolumeCurveBehavior, VolumeProp } from 'remotion';
|
|
2
2
|
export type FallbackHtml5AudioProps = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
3
|
+
onError?: (err: Error) => void;
|
|
4
|
+
useWebAudioApi?: boolean;
|
|
5
|
+
acceptableTimeShiftInSeconds?: number;
|
|
6
|
+
pauseWhenBuffering?: boolean;
|
|
8
7
|
};
|
|
9
8
|
export type AudioProps = {
|
|
10
9
|
src: string;
|
|
@@ -13,12 +12,9 @@ export type AudioProps = {
|
|
|
13
12
|
volume?: VolumeProp;
|
|
14
13
|
loopVolumeCurveBehavior?: LoopVolumeCurveBehavior;
|
|
15
14
|
name?: string;
|
|
16
|
-
pauseWhenBuffering?: boolean;
|
|
17
15
|
showInTimeline?: boolean;
|
|
18
16
|
playbackRate?: number;
|
|
19
17
|
muted?: boolean;
|
|
20
|
-
delayRenderRetries?: number;
|
|
21
|
-
delayRenderTimeoutInMilliseconds?: number;
|
|
22
18
|
style?: React.CSSProperties;
|
|
23
19
|
/**
|
|
24
20
|
* @deprecated For internal use only
|
|
@@ -28,11 +24,9 @@ export type AudioProps = {
|
|
|
28
24
|
loop?: boolean;
|
|
29
25
|
audioStreamIndex?: number;
|
|
30
26
|
_remotionInternalNativeLoopPassed?: boolean;
|
|
31
|
-
fallbackHtml5AudioProps?:
|
|
32
|
-
onError?: (err: Error) => void;
|
|
33
|
-
useWebAudioApi?: boolean;
|
|
34
|
-
toneFrequency?: number;
|
|
35
|
-
acceptableTimeShiftInSeconds?: number;
|
|
36
|
-
};
|
|
27
|
+
fallbackHtml5AudioProps?: FallbackHtml5AudioProps;
|
|
37
28
|
disallowFallbackToHtml5Audio?: boolean;
|
|
29
|
+
toneFrequency?: number;
|
|
30
|
+
delayRenderRetries?: number;
|
|
31
|
+
delayRenderTimeoutInMilliseconds?: number;
|
|
38
32
|
};
|
|
@@ -5,7 +5,7 @@ export declare const makeAudioCache: () => {
|
|
|
5
5
|
deleteAll: () => void;
|
|
6
6
|
getSamples: (timestamp: number, durationInSeconds: number) => AudioSample[];
|
|
7
7
|
getOldestTimestamp: () => number;
|
|
8
|
-
getNewestTimestamp: () => number;
|
|
8
|
+
getNewestTimestamp: () => number | null;
|
|
9
9
|
getOpenTimestamps: () => number[];
|
|
10
10
|
};
|
|
11
11
|
export type AudioCache = ReturnType<typeof makeAudioCache>;
|
|
@@ -6,7 +6,7 @@ export const makeAudioCache = () => {
|
|
|
6
6
|
samples[sample.timestamp] = sample;
|
|
7
7
|
};
|
|
8
8
|
const clearBeforeThreshold = (threshold) => {
|
|
9
|
-
for (const timestamp of timestamps) {
|
|
9
|
+
for (const timestamp of timestamps.slice()) {
|
|
10
10
|
const endTimestamp = timestamp + samples[timestamp].duration;
|
|
11
11
|
if (endTimestamp < threshold) {
|
|
12
12
|
const isLast = timestamp === timestamps[timestamps.length - 1];
|
|
@@ -21,6 +21,7 @@ export const makeAudioCache = () => {
|
|
|
21
21
|
};
|
|
22
22
|
const deleteAll = () => {
|
|
23
23
|
for (const timestamp of timestamps) {
|
|
24
|
+
samples[timestamp].close();
|
|
24
25
|
delete samples[timestamp];
|
|
25
26
|
}
|
|
26
27
|
timestamps.length = 0;
|
|
@@ -47,6 +48,9 @@ export const makeAudioCache = () => {
|
|
|
47
48
|
return timestamps[0];
|
|
48
49
|
};
|
|
49
50
|
const getNewestTimestamp = () => {
|
|
51
|
+
if (timestamps.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
50
54
|
const sample = samples[timestamps[timestamps.length - 1]];
|
|
51
55
|
return sample.timestamp + sample.duration;
|
|
52
56
|
};
|
|
@@ -19,7 +19,10 @@ export declare const makeAudioIterator: ({ audioSampleSink, isMatroska, startTim
|
|
|
19
19
|
size: number;
|
|
20
20
|
};
|
|
21
21
|
getLastUsed: () => number;
|
|
22
|
-
prepareForDeletion: () =>
|
|
22
|
+
prepareForDeletion: () => void;
|
|
23
23
|
startTimestamp: number;
|
|
24
|
+
clearBeforeThreshold: (threshold: number) => void;
|
|
25
|
+
getOldestTimestamp: () => number;
|
|
26
|
+
getNewestTimestamp: () => number | null;
|
|
24
27
|
};
|
|
25
28
|
export type AudioSampleIterator = ReturnType<typeof makeAudioIterator>;
|
|
@@ -30,7 +30,7 @@ export const makeAudioIterator = ({ audioSampleSink, isMatroska, startTimestamp,
|
|
|
30
30
|
lastUsed = Date.now();
|
|
31
31
|
const { value: sample, done } = await sampleIterator.next();
|
|
32
32
|
if (done) {
|
|
33
|
-
fullDuration = cache.getNewestTimestamp()
|
|
33
|
+
fullDuration = cache.getNewestTimestamp();
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
36
|
const realTimestamp = actualMatroskaTimestamps.getRealTimestamp(sample.timestamp);
|
|
@@ -51,6 +51,12 @@ export const makeAudioIterator = ({ audioSampleSink, isMatroska, startTimestamp,
|
|
|
51
51
|
return [];
|
|
52
52
|
}
|
|
53
53
|
const samples = cache.getSamples(timestamp, durationInSeconds);
|
|
54
|
+
const newestTimestamp = cache.getNewestTimestamp();
|
|
55
|
+
if (newestTimestamp !== null) {
|
|
56
|
+
if (newestTimestamp >= timestamp + durationInSeconds - 0.0000000001) {
|
|
57
|
+
return samples;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
54
60
|
while (true) {
|
|
55
61
|
const sample = await getNextSample();
|
|
56
62
|
// Clear all samples before the timestamp
|
|
@@ -73,10 +79,12 @@ export const makeAudioIterator = ({ audioSampleSink, isMatroska, startTimestamp,
|
|
|
73
79
|
return samples;
|
|
74
80
|
};
|
|
75
81
|
const logOpenFrames = () => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
.
|
|
82
|
+
const openTimestamps = cache.getOpenTimestamps();
|
|
83
|
+
if (openTimestamps.length > 0) {
|
|
84
|
+
const first = openTimestamps[0];
|
|
85
|
+
const last = openTimestamps[openTimestamps.length - 1];
|
|
86
|
+
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, 'Open audio samples for src', src, `${first.toFixed(3)}...${last.toFixed(3)}`);
|
|
87
|
+
}
|
|
80
88
|
};
|
|
81
89
|
const getCacheStats = () => {
|
|
82
90
|
return {
|
|
@@ -91,12 +99,13 @@ export const makeAudioIterator = ({ audioSampleSink, isMatroska, startTimestamp,
|
|
|
91
99
|
}
|
|
92
100
|
return (oldestTimestamp < timestamp && Math.abs(oldestTimestamp - timestamp) < 10);
|
|
93
101
|
};
|
|
94
|
-
const prepareForDeletion =
|
|
102
|
+
const prepareForDeletion = () => {
|
|
95
103
|
cache.deleteAll();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
sampleIterator.return().then((value) => {
|
|
105
|
+
if (value.value) {
|
|
106
|
+
value.value.close();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
100
109
|
fullDuration = null;
|
|
101
110
|
};
|
|
102
111
|
let op = Promise.resolve([]);
|
|
@@ -116,5 +125,8 @@ export const makeAudioIterator = ({ audioSampleSink, isMatroska, startTimestamp,
|
|
|
116
125
|
getLastUsed: () => lastUsed,
|
|
117
126
|
prepareForDeletion,
|
|
118
127
|
startTimestamp,
|
|
128
|
+
clearBeforeThreshold: cache.clearBeforeThreshold,
|
|
129
|
+
getOldestTimestamp: cache.getOldestTimestamp,
|
|
130
|
+
getNewestTimestamp: cache.getNewestTimestamp,
|
|
119
131
|
};
|
|
120
132
|
};
|
|
@@ -1,28 +1,8 @@
|
|
|
1
1
|
import type { AudioSampleSink } from 'mediabunny';
|
|
2
|
-
import type
|
|
2
|
+
import { type LogLevel } from 'remotion';
|
|
3
3
|
import type { RememberActualMatroskaTimestamps } from '../video-extraction/remember-actual-matroska-timestamps';
|
|
4
|
+
import type { AudioSampleIterator } from './audio-iterator';
|
|
4
5
|
export declare const makeAudioManager: () => {
|
|
5
|
-
makeIterator: ({ timeInSeconds, src, audioSampleSink, isMatroska, actualMatroskaTimestamps, logLevel, }: {
|
|
6
|
-
timeInSeconds: number;
|
|
7
|
-
src: string;
|
|
8
|
-
audioSampleSink: AudioSampleSink;
|
|
9
|
-
isMatroska: boolean;
|
|
10
|
-
actualMatroskaTimestamps: RememberActualMatroskaTimestamps;
|
|
11
|
-
logLevel: LogLevel;
|
|
12
|
-
}) => {
|
|
13
|
-
src: string;
|
|
14
|
-
getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
|
|
15
|
-
waitForCompletion: () => Promise<boolean>;
|
|
16
|
-
canSatisfyRequestedTime: (timestamp: number) => boolean;
|
|
17
|
-
logOpenFrames: () => void;
|
|
18
|
-
getCacheStats: () => {
|
|
19
|
-
count: number;
|
|
20
|
-
size: number;
|
|
21
|
-
};
|
|
22
|
-
getLastUsed: () => number;
|
|
23
|
-
prepareForDeletion: () => Promise<void>;
|
|
24
|
-
startTimestamp: number;
|
|
25
|
-
};
|
|
26
6
|
getIterator: ({ src, timeInSeconds, audioSampleSink, isMatroska, actualMatroskaTimestamps, logLevel, }: {
|
|
27
7
|
src: string;
|
|
28
8
|
timeInSeconds: number;
|
|
@@ -30,20 +10,7 @@ export declare const makeAudioManager: () => {
|
|
|
30
10
|
isMatroska: boolean;
|
|
31
11
|
actualMatroskaTimestamps: RememberActualMatroskaTimestamps;
|
|
32
12
|
logLevel: LogLevel;
|
|
33
|
-
}) => Promise<
|
|
34
|
-
src: string;
|
|
35
|
-
getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
|
|
36
|
-
waitForCompletion: () => Promise<boolean>;
|
|
37
|
-
canSatisfyRequestedTime: (timestamp: number) => boolean;
|
|
38
|
-
logOpenFrames: () => void;
|
|
39
|
-
getCacheStats: () => {
|
|
40
|
-
count: number;
|
|
41
|
-
size: number;
|
|
42
|
-
};
|
|
43
|
-
getLastUsed: () => number;
|
|
44
|
-
prepareForDeletion: () => Promise<void>;
|
|
45
|
-
startTimestamp: number;
|
|
46
|
-
}>;
|
|
13
|
+
}) => Promise<AudioSampleIterator>;
|
|
47
14
|
getCacheStats: () => {
|
|
48
15
|
count: number;
|
|
49
16
|
totalSize: number;
|
|
@@ -59,8 +26,12 @@ export declare const makeAudioManager: () => {
|
|
|
59
26
|
size: number;
|
|
60
27
|
};
|
|
61
28
|
getLastUsed: () => number;
|
|
62
|
-
prepareForDeletion: () =>
|
|
29
|
+
prepareForDeletion: () => void;
|
|
63
30
|
startTimestamp: number;
|
|
31
|
+
clearBeforeThreshold: (threshold: number) => void;
|
|
32
|
+
getOldestTimestamp: () => number;
|
|
33
|
+
getNewestTimestamp: () => number | null;
|
|
64
34
|
} | null;
|
|
65
35
|
logOpenFrames: () => void;
|
|
36
|
+
deleteDuplicateIterators: (logLevel: LogLevel) => void;
|
|
66
37
|
};
|