@remotion/media 4.0.353 → 4.0.355

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,12 +1,14 @@
1
- import { useContext, useLayoutEffect, useMemo, useState } from 'react';
1
+ import { useContext, useLayoutEffect, useState } from 'react';
2
2
  import { cancelRender, Internals, useCurrentFrame, useDelayRender, useRemotionEnvironment, } from 'remotion';
3
+ import { applyVolume } from '../convert-audiodata/apply-volume';
4
+ import { frameForVolumeProp } from '../looped-frame';
3
5
  import { extractFrameViaBroadcastChannel } from '../video-extraction/extract-frame-via-broadcast-channel';
4
6
  export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted, loopVolumeCurveBehavior, delayRenderRetries, delayRenderTimeoutInMilliseconds, logLevel = window.remotion_logLevel, loop, }) => {
7
+ const frame = useCurrentFrame();
5
8
  const absoluteFrame = Internals.useTimelinePosition();
6
9
  const videoConfig = Internals.useUnsafeVideoConfig();
7
10
  const { registerRenderAsset, unregisterRenderAsset } = useContext(Internals.RenderAssetManager);
8
- const frame = useCurrentFrame();
9
- const volumePropsFrame = Internals.useFrameForVolumeProp(loopVolumeCurveBehavior ?? 'repeat');
11
+ const startsAt = Internals.useMediaStartsAt();
10
12
  const environment = useRemotionEnvironment();
11
13
  const [id] = useState(() => `${Math.random()}`.replace('0.', ''));
12
14
  if (!videoConfig) {
@@ -15,24 +17,6 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
15
17
  if (!src) {
16
18
  throw new TypeError('No `src` was passed to <Audio>.');
17
19
  }
18
- const volume = Internals.evaluateVolume({
19
- volume: volumeProp,
20
- frame: volumePropsFrame,
21
- mediaVolume: 1,
22
- });
23
- Internals.warnAboutTooHighVolume(volume);
24
- const shouldRenderAudio = useMemo(() => {
25
- if (!window.remotion_audioEnabled) {
26
- return false;
27
- }
28
- if (muted) {
29
- return false;
30
- }
31
- if (volume <= 0) {
32
- return false;
33
- }
34
- return true;
35
- }, [muted, volume]);
36
20
  const { fps } = videoConfig;
37
21
  const { delayRender, continueRender } = useDelayRender();
38
22
  useLayoutEffect(() => {
@@ -43,19 +27,43 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
43
27
  retries: delayRenderRetries ?? undefined,
44
28
  timeoutInMilliseconds: delayRenderTimeoutInMilliseconds ?? undefined,
45
29
  });
30
+ const shouldRenderAudio = (() => {
31
+ if (!window.remotion_audioEnabled) {
32
+ return false;
33
+ }
34
+ if (muted) {
35
+ return false;
36
+ }
37
+ return true;
38
+ })();
46
39
  extractFrameViaBroadcastChannel({
47
40
  src,
48
41
  timeInSeconds: timestamp,
49
42
  durationInSeconds,
43
+ playbackRate: playbackRate ?? 1,
50
44
  logLevel: logLevel ?? 'info',
51
45
  includeAudio: shouldRenderAudio,
52
46
  includeVideo: false,
53
47
  isClientSideRendering: environment.isClientSideRendering,
54
- volume,
55
48
  loop: loop ?? false,
56
49
  })
57
- .then(({ audio }) => {
58
- if (audio) {
50
+ .then(({ audio, durationInSeconds: assetDurationInSeconds }) => {
51
+ const volumePropsFrame = frameForVolumeProp({
52
+ behavior: loopVolumeCurveBehavior ?? 'repeat',
53
+ loop: loop ?? false,
54
+ assetDurationInSeconds: assetDurationInSeconds ?? 0,
55
+ fps,
56
+ frame,
57
+ startsAt,
58
+ });
59
+ const volume = Internals.evaluateVolume({
60
+ volume: volumeProp,
61
+ frame: volumePropsFrame,
62
+ mediaVolume: 1,
63
+ });
64
+ Internals.warnAboutTooHighVolume(volume);
65
+ if (audio && volume > 0) {
66
+ applyVolume(audio.data, volume);
59
67
  registerRenderAsset({
60
68
  type: 'inline-audio',
61
69
  id,
@@ -87,13 +95,15 @@ export const AudioForRendering = ({ volume: volumeProp, playbackRate, src, muted
87
95
  frame,
88
96
  id,
89
97
  logLevel,
98
+ loop,
99
+ loopVolumeCurveBehavior,
100
+ muted,
90
101
  playbackRate,
91
102
  registerRenderAsset,
92
- shouldRenderAudio,
93
103
  src,
104
+ startsAt,
94
105
  unregisterRenderAsset,
95
- volume,
96
- loop,
106
+ volumeProp,
97
107
  ]);
98
108
  return null;
99
109
  };
@@ -4,11 +4,14 @@ import { cancelRender, Internals, Sequence, useRemotionEnvironment, } from 'remo
4
4
  import { SharedAudioContext } from '../../../core/src/audio/shared-audio-tags';
5
5
  import { AudioForRendering } from './audio-for-rendering';
6
6
  const { validateMediaTrimProps, resolveTrimProps, validateMediaProps, AudioForPreview, } = Internals;
7
+ // dummy function for now because onError is not supported
8
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
9
+ const onRemotionError = (_e) => { };
7
10
  export const Audio = (props) => {
8
11
  const audioContext = useContext(SharedAudioContext);
9
12
  // Should only destruct `trimBefore` and `trimAfter` from props,
10
13
  // rest gets drilled down
11
- const { trimBefore, trimAfter, name, pauseWhenBuffering, stack, showInTimeline, onError: onRemotionError, loop, ...otherProps } = props;
14
+ const { trimBefore, trimAfter, name, pauseWhenBuffering, stack, showInTimeline, loop, ...otherProps } = props;
12
15
  const environment = useRemotionEnvironment();
13
16
  const onDuration = useCallback(() => undefined, []);
14
17
  if (typeof props.src !== 'string') {
@@ -44,7 +47,7 @@ export const Audio = (props) => {
44
47
  // eslint-disable-next-line no-console
45
48
  console.warn(errMessage);
46
49
  }
47
- }, [onRemotionError, loop]);
50
+ }, [loop]);
48
51
  if (typeof trimBeforeValue !== 'undefined' ||
49
52
  typeof trimAfterValue !== 'undefined') {
50
53
  return (_jsx(Sequence, { layout: "none", from: 0 - (trimBeforeValue ?? 0), showInTimeline: false, durationInFrames: trimAfterValue, name: name, children: _jsx(Audio, { pauseWhenBuffering: pauseWhenBuffering ?? false, ...otherProps }) }));
@@ -53,7 +56,7 @@ export const Audio = (props) => {
53
56
  if (environment.isRendering) {
54
57
  return _jsx(AudioForRendering, { ...otherProps });
55
58
  }
56
- const { onAutoPlayError, crossOrigin, delayRenderRetries, delayRenderTimeoutInMilliseconds, ...propsForPreview } = otherProps;
59
+ const { delayRenderRetries, delayRenderTimeoutInMilliseconds, ...propsForPreview } = otherProps;
57
60
  return (_jsx(AudioForPreview, { _remotionInternalNativeLoopPassed: props._remotionInternalNativeLoopPassed ?? false, _remotionInternalStack: stack ?? null, shouldPreMountAudioTags: audioContext !== null && audioContext.numberOfAudioTags > 0, ...propsForPreview, onNativeError: onError, onDuration: onDuration,
58
61
  // Proposal: Make this default to true in v5
59
62
  pauseWhenBuffering: pauseWhenBuffering ?? false, _remotionInternalNeedsDurationCalculation: Boolean(loop), showInTimeline: showInTimeline ?? true }));
@@ -8,16 +8,11 @@ export type AudioProps = {
8
8
  name?: string;
9
9
  pauseWhenBuffering?: boolean;
10
10
  showInTimeline?: boolean;
11
- onAutoPlayError?: null | (() => void);
12
11
  playbackRate?: number;
13
12
  muted?: boolean;
14
13
  delayRenderRetries?: number;
15
14
  delayRenderTimeoutInMilliseconds?: number;
16
- crossOrigin?: '' | 'anonymous' | 'use-credentials';
17
15
  style?: React.CSSProperties;
18
- onError?: (err: Error) => void;
19
- useWebAudioApi?: boolean;
20
- acceptableTimeShiftInSeconds?: number;
21
16
  /**
22
17
  * @deprecated For internal use only
23
18
  */
@@ -1,10 +1,13 @@
1
1
  import { type LogLevel } from 'remotion';
2
2
  import type { PcmS16AudioData } from '../convert-audiodata/convert-audiodata';
3
- export declare const extractAudio: ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds, volume, logLevel, loop, }: {
3
+ export declare const extractAudio: ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds, logLevel, loop, playbackRate, }: {
4
4
  src: string;
5
5
  timeInSeconds: number;
6
6
  durationInSeconds: number;
7
- volume: number;
8
7
  logLevel: LogLevel;
9
8
  loop: boolean;
10
- }) => Promise<PcmS16AudioData | null>;
9
+ playbackRate: number;
10
+ }) => Promise<{
11
+ data: PcmS16AudioData | null;
12
+ durationInSeconds: number | null;
13
+ }>;
@@ -2,18 +2,18 @@ import { audioManager } from '../caches';
2
2
  import { combineAudioDataAndClosePrevious } from '../convert-audiodata/combine-audiodata';
3
3
  import { convertAudioData } from '../convert-audiodata/convert-audiodata';
4
4
  import { TARGET_NUMBER_OF_CHANNELS, TARGET_SAMPLE_RATE, } from '../convert-audiodata/resample-audiodata';
5
- import { sinkPromises } from '../video-extraction/extract-frame';
6
- import { getSinks } from '../video-extraction/get-frames-since-keyframe';
7
- export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds, volume, logLevel, loop, }) => {
8
- if (!sinkPromises[src]) {
9
- sinkPromises[src] = getSinks(src);
5
+ import { getSinkWeak } from '../get-sink-weak';
6
+ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds, logLevel, loop, playbackRate, }) => {
7
+ const { audio, actualMatroskaTimestamps, isMatroska, getDuration } = await getSinkWeak(src, logLevel);
8
+ let duration = null;
9
+ if (loop) {
10
+ duration = await getDuration();
10
11
  }
11
- const { audio, actualMatroskaTimestamps, isMatroska, getDuration } = await sinkPromises[src];
12
12
  if (audio === null) {
13
- return null;
13
+ return { data: null, durationInSeconds: null };
14
14
  }
15
15
  const timeInSeconds = loop
16
- ? unloopedTimeInSeconds % (await getDuration())
16
+ ? unloopedTimeInSeconds % duration
17
17
  : unloopedTimeInSeconds;
18
18
  const sampleIterator = await audioManager.getIterator({
19
19
  src,
@@ -44,10 +44,15 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
44
44
  // amount of samples to shave from start and end
45
45
  let trimStartInSeconds = 0;
46
46
  let trimEndInSeconds = 0;
47
- // TODO: Apply playback rate
48
47
  // TODO: Apply tone frequency
49
48
  if (isFirstSample) {
50
49
  trimStartInSeconds = timeInSeconds - sample.timestamp;
50
+ if (trimStartInSeconds < 0 && trimStartInSeconds > -1e-10) {
51
+ trimStartInSeconds = 0;
52
+ }
53
+ if (trimStartInSeconds < 0) {
54
+ throw new Error(`trimStartInSeconds is negative: ${trimStartInSeconds}`);
55
+ }
51
56
  }
52
57
  if (isLastSample) {
53
58
  trimEndInSeconds =
@@ -62,7 +67,7 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
62
67
  trimStartInSeconds,
63
68
  trimEndInSeconds,
64
69
  targetNumberOfChannels: TARGET_NUMBER_OF_CHANNELS,
65
- volume,
70
+ playbackRate,
66
71
  });
67
72
  audioDataRaw.close();
68
73
  if (audioData.numberOfFrames === 0) {
@@ -71,8 +76,8 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
71
76
  audioDataArray.push(audioData);
72
77
  }
73
78
  if (audioDataArray.length === 0) {
74
- return null;
79
+ return { data: null, durationInSeconds: duration };
75
80
  }
76
81
  const combined = combineAudioDataAndClosePrevious(audioDataArray);
77
- return combined;
82
+ return { data: combined, durationInSeconds: duration };
78
83
  };
@@ -0,0 +1 @@
1
+ export declare const applyVolume: (array: Int16Array, volume: number) => void;
@@ -0,0 +1,17 @@
1
+ export const applyVolume = (array, volume) => {
2
+ if (volume === 1) {
3
+ return;
4
+ }
5
+ for (let i = 0; i < array.length; i++) {
6
+ const newValue = array[i] * volume;
7
+ if (newValue < -32768) {
8
+ array[i] = -32768;
9
+ }
10
+ else if (newValue > 32767) {
11
+ array[i] = 32767;
12
+ }
13
+ else {
14
+ array[i] = newValue;
15
+ }
16
+ }
17
+ };
@@ -4,7 +4,7 @@ export type ConvertAudioDataOptions = {
4
4
  trimStartInSeconds: number;
5
5
  trimEndInSeconds: number;
6
6
  targetNumberOfChannels: number;
7
- volume: number;
7
+ playbackRate: number;
8
8
  };
9
9
  export type PcmS16AudioData = {
10
10
  data: Int16Array;
@@ -13,4 +13,4 @@ export type PcmS16AudioData = {
13
13
  numberOfFrames: number;
14
14
  timestamp: number;
15
15
  };
16
- export declare const convertAudioData: ({ audioData, newSampleRate, trimStartInSeconds, trimEndInSeconds, targetNumberOfChannels, volume, }: ConvertAudioDataOptions) => PcmS16AudioData;
16
+ export declare const convertAudioData: ({ audioData, newSampleRate, trimStartInSeconds, trimEndInSeconds, targetNumberOfChannels, playbackRate, }: ConvertAudioDataOptions) => PcmS16AudioData;
@@ -1,12 +1,19 @@
1
1
  import { resampleAudioData } from './resample-audiodata';
2
2
  const FORMAT = 's16';
3
- export const convertAudioData = ({ audioData, newSampleRate, trimStartInSeconds, trimEndInSeconds, targetNumberOfChannels, volume, }) => {
3
+ const roundButRoundDownZeroPointFive = (value) => {
4
+ if (value % 1 <= 0.5) {
5
+ return Math.floor(value);
6
+ }
7
+ return Math.ceil(value);
8
+ };
9
+ export const convertAudioData = ({ audioData, newSampleRate, trimStartInSeconds, trimEndInSeconds, targetNumberOfChannels, playbackRate, }) => {
4
10
  const { numberOfChannels: srcNumberOfChannels, sampleRate: currentSampleRate, numberOfFrames, } = audioData;
5
11
  const ratio = currentSampleRate / newSampleRate;
6
- const frameOffset = Math.round(trimStartInSeconds * audioData.sampleRate);
7
- const frameCount = numberOfFrames -
8
- Math.round((trimEndInSeconds + trimStartInSeconds) * audioData.sampleRate);
9
- const newNumberOfFrames = Math.round(frameCount / ratio);
12
+ const frameOffset = roundButRoundDownZeroPointFive(trimStartInSeconds * audioData.sampleRate);
13
+ const unroundedFrameCount = numberOfFrames -
14
+ (trimEndInSeconds + trimStartInSeconds) * audioData.sampleRate;
15
+ const frameCount = Math.round(unroundedFrameCount);
16
+ const newNumberOfFrames = Math.round(unroundedFrameCount / ratio / playbackRate);
10
17
  if (newNumberOfFrames === 0) {
11
18
  throw new Error('Cannot resample - the given sample rate would result in less than 1 sample');
12
19
  }
@@ -24,7 +31,7 @@ export const convertAudioData = ({ audioData, newSampleRate, trimStartInSeconds,
24
31
  const chunkSize = frameCount / newNumberOfFrames;
25
32
  if (newNumberOfFrames === frameCount &&
26
33
  targetNumberOfChannels === srcNumberOfChannels &&
27
- volume === 1) {
34
+ playbackRate === 1) {
28
35
  return {
29
36
  data: srcChannels,
30
37
  numberOfChannels: targetNumberOfChannels,
@@ -39,7 +46,6 @@ export const convertAudioData = ({ audioData, newSampleRate, trimStartInSeconds,
39
46
  destination: data,
40
47
  targetFrames: newNumberOfFrames,
41
48
  chunkSize,
42
- volume,
43
49
  });
44
50
  const newAudioData = {
45
51
  data,
@@ -1,10 +1,9 @@
1
1
  export declare const TARGET_NUMBER_OF_CHANNELS = 2;
2
2
  export declare const TARGET_SAMPLE_RATE = 48000;
3
- export declare const resampleAudioData: ({ srcNumberOfChannels, sourceChannels, destination, targetFrames, chunkSize, volume, }: {
3
+ export declare const resampleAudioData: ({ srcNumberOfChannels, sourceChannels, destination, targetFrames, chunkSize, }: {
4
4
  srcNumberOfChannels: number;
5
5
  sourceChannels: Int16Array;
6
6
  destination: Int16Array;
7
7
  targetFrames: number;
8
8
  chunkSize: number;
9
- volume: number;
10
9
  }) => void;
@@ -2,29 +2,50 @@
2
2
  export const TARGET_NUMBER_OF_CHANNELS = 2;
3
3
  // Remotion exports all videos with 48kHz sample rate.
4
4
  export const TARGET_SAMPLE_RATE = 48000;
5
- export const resampleAudioData = ({ srcNumberOfChannels, sourceChannels, destination, targetFrames, chunkSize, volume, }) => {
6
- const getSourceValues = (start, end, channelIndex) => {
7
- const sampleCountAvg = end - start;
8
- let itemSum = 0;
9
- let itemCount = 0;
10
- for (let k = 0; k < sampleCountAvg; k++) {
11
- const num = sourceChannels[(start + k) * srcNumberOfChannels + channelIndex];
12
- itemSum += num;
13
- itemCount++;
5
+ const fixFloatingPoint = (value) => {
6
+ if (value % 1 < 0.0000001) {
7
+ return Math.floor(value);
8
+ }
9
+ if (value % 1 > 0.9999999) {
10
+ return Math.ceil(value);
11
+ }
12
+ return value;
13
+ };
14
+ export const resampleAudioData = ({ srcNumberOfChannels, sourceChannels, destination, targetFrames, chunkSize, }) => {
15
+ const getSourceValues = (startUnfixed, endUnfixed, channelIndex) => {
16
+ const start = fixFloatingPoint(startUnfixed);
17
+ const end = fixFloatingPoint(endUnfixed);
18
+ const startFloor = Math.floor(start);
19
+ const startCeil = Math.ceil(start);
20
+ const startFraction = start - startFloor;
21
+ const endFraction = end - Math.floor(end);
22
+ const endFloor = Math.floor(end);
23
+ let weightedSum = 0;
24
+ let totalWeight = 0;
25
+ // Handle first fractional sample
26
+ if (startFraction > 0) {
27
+ const firstSample = sourceChannels[startFloor * srcNumberOfChannels + channelIndex];
28
+ weightedSum += firstSample * (1 - startFraction);
29
+ totalWeight += 1 - startFraction;
14
30
  }
15
- const average = itemSum / itemCount;
16
- const averageVolume = average * volume;
17
- if (averageVolume < -32768) {
18
- return -32768;
31
+ // Handle full samples
32
+ for (let k = startCeil; k < endFloor; k++) {
33
+ const num = sourceChannels[k * srcNumberOfChannels + channelIndex];
34
+ weightedSum += num;
35
+ totalWeight += 1;
19
36
  }
20
- if (averageVolume > 32767) {
21
- return 32767;
37
+ // Handle last fractional sample
38
+ if (endFraction > 0) {
39
+ const lastSample = sourceChannels[endFloor * srcNumberOfChannels + channelIndex];
40
+ weightedSum += lastSample * endFraction;
41
+ totalWeight += endFraction;
22
42
  }
23
- return averageVolume;
43
+ const average = weightedSum / totalWeight;
44
+ return average;
24
45
  };
25
46
  for (let newFrameIndex = 0; newFrameIndex < targetFrames; newFrameIndex++) {
26
- const start = Math.floor(newFrameIndex * chunkSize);
27
- const end = Math.max(Math.floor(start + chunkSize), start + 1);
47
+ const start = newFrameIndex * chunkSize;
48
+ const end = start + chunkSize;
28
49
  if (TARGET_NUMBER_OF_CHANNELS === srcNumberOfChannels) {
29
50
  for (let i = 0; i < srcNumberOfChannels; i++) {
30
51
  destination[newFrameIndex * srcNumberOfChannels + i] = getSourceValues(start, end, i);