@remotion/media 4.0.352 → 4.0.354

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.
Files changed (43) hide show
  1. package/dist/audio/audio-for-rendering.js +37 -27
  2. package/dist/audio/audio.js +6 -3
  3. package/dist/audio/props.d.ts +1 -7
  4. package/dist/audio-extraction/audio-iterator.d.ts +1 -1
  5. package/dist/audio-extraction/audio-iterator.js +2 -2
  6. package/dist/audio-extraction/audio-manager.d.ts +1 -1
  7. package/dist/audio-extraction/extract-audio.d.ts +7 -4
  8. package/dist/audio-extraction/extract-audio.js +16 -7
  9. package/dist/caches.d.ts +6 -6
  10. package/dist/caches.js +5 -6
  11. package/dist/convert-audiodata/apply-volume.d.ts +1 -0
  12. package/dist/convert-audiodata/apply-volume.js +17 -0
  13. package/dist/convert-audiodata/convert-audiodata.d.ts +2 -2
  14. package/dist/convert-audiodata/convert-audiodata.js +13 -7
  15. package/dist/convert-audiodata/resample-audiodata.d.ts +1 -2
  16. package/dist/convert-audiodata/resample-audiodata.js +42 -20
  17. package/dist/esm/index.mjs +242 -182
  18. package/dist/extract-frame-and-audio.d.ts +3 -2
  19. package/dist/extract-frame-and-audio.js +4 -3
  20. package/dist/looped-frame.d.ts +9 -0
  21. package/dist/looped-frame.js +10 -0
  22. package/dist/video/media-player.d.ts +28 -30
  23. package/dist/video/media-player.js +174 -314
  24. package/dist/video/new-video-for-preview.d.ts +1 -1
  25. package/dist/video/new-video-for-preview.js +12 -18
  26. package/dist/video/props.d.ts +0 -5
  27. package/dist/video/timeout-utils.d.ts +2 -0
  28. package/dist/video/timeout-utils.js +18 -0
  29. package/dist/video/video-for-preview.d.ts +11 -0
  30. package/dist/video/video-for-preview.js +113 -0
  31. package/dist/video/video-for-rendering.js +41 -31
  32. package/dist/video/video.js +2 -2
  33. package/dist/video-extraction/extract-frame-via-broadcast-channel.d.ts +4 -3
  34. package/dist/video-extraction/extract-frame-via-broadcast-channel.js +9 -5
  35. package/dist/video-extraction/extract-frame.d.ts +1 -1
  36. package/dist/video-extraction/extract-frame.js +3 -0
  37. package/dist/video-extraction/get-frames-since-keyframe.d.ts +1 -1
  38. package/dist/video-extraction/get-frames-since-keyframe.js +7 -8
  39. package/dist/video-extraction/keyframe-bank.d.ts +1 -1
  40. package/dist/video-extraction/keyframe-bank.js +7 -7
  41. package/dist/video-extraction/keyframe-manager.d.ts +1 -1
  42. package/dist/video-extraction/keyframe-manager.js +6 -6
  43. package/package.json +3 -3
@@ -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 }));
@@ -1,5 +1,4 @@
1
- import type { LoopVolumeCurveBehavior, VolumeProp } from 'remotion';
2
- import type { LogLevel } from '../log';
1
+ import type { LogLevel, LoopVolumeCurveBehavior, VolumeProp } from 'remotion';
3
2
  export type AudioProps = {
4
3
  src: string;
5
4
  trimBefore?: number;
@@ -9,16 +8,11 @@ export type AudioProps = {
9
8
  name?: string;
10
9
  pauseWhenBuffering?: boolean;
11
10
  showInTimeline?: boolean;
12
- onAutoPlayError?: null | (() => void);
13
11
  playbackRate?: number;
14
12
  muted?: boolean;
15
13
  delayRenderRetries?: number;
16
14
  delayRenderTimeoutInMilliseconds?: number;
17
- crossOrigin?: '' | 'anonymous' | 'use-credentials';
18
15
  style?: React.CSSProperties;
19
- onError?: (err: Error) => void;
20
- useWebAudioApi?: boolean;
21
- acceptableTimeShiftInSeconds?: number;
22
16
  /**
23
17
  * @deprecated For internal use only
24
18
  */
@@ -1,5 +1,5 @@
1
1
  import type { AudioSample, AudioSampleSink } from 'mediabunny';
2
- import type { LogLevel } from '../log';
2
+ import { type LogLevel } from 'remotion';
3
3
  import type { RememberActualMatroskaTimestamps } from '../video-extraction/remember-actual-matroska-timestamps';
4
4
  export declare const makeAudioIterator: ({ audioSampleSink, isMatroska, startTimestamp, src, actualMatroskaTimestamps, }: {
5
5
  audioSampleSink: AudioSampleSink;
@@ -1,5 +1,5 @@
1
+ import { Internals } from 'remotion';
1
2
  import { SAFE_BACK_WINDOW_IN_SECONDS } from '../caches';
2
- import { Log } from '../log';
3
3
  import { makeAudioCache } from './audio-cache';
4
4
  // https://discord.com/channels/@me/1409810025844838481/1415028953093111870
5
5
  // Audio frames might have dependencies on previous and next frames so we need to decode a bit more
@@ -62,7 +62,7 @@ export const makeAudioIterator = ({ audioSampleSink, isMatroska, startTimestamp,
62
62
  return samples;
63
63
  };
64
64
  const logOpenFrames = (logLevel) => {
65
- Log.verbose(logLevel, '[Audio] Open samples for src', src, cache
65
+ Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, 'Open audio samples for src', src, cache
66
66
  .getOpenTimestamps()
67
67
  .map((t) => t.toFixed(3))
68
68
  .join(', '));
@@ -1,5 +1,5 @@
1
1
  import type { AudioSampleSink } from 'mediabunny';
2
- import type { LogLevel } from '../log';
2
+ import type { LogLevel } from 'remotion';
3
3
  import type { RememberActualMatroskaTimestamps } from '../video-extraction/remember-actual-matroska-timestamps';
4
4
  export declare const makeAudioManager: () => {
5
5
  makeIterator: ({ timeInSeconds, src, audioSampleSink, isMatroska, actualMatroskaTimestamps, }: {
@@ -1,10 +1,13 @@
1
+ import { type LogLevel } from 'remotion';
1
2
  import type { PcmS16AudioData } from '../convert-audiodata/convert-audiodata';
2
- import type { LogLevel } from '../log';
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
+ }>;
@@ -4,16 +4,20 @@ import { convertAudioData } from '../convert-audiodata/convert-audiodata';
4
4
  import { TARGET_NUMBER_OF_CHANNELS, TARGET_SAMPLE_RATE, } from '../convert-audiodata/resample-audiodata';
5
5
  import { sinkPromises } from '../video-extraction/extract-frame';
6
6
  import { getSinks } from '../video-extraction/get-frames-since-keyframe';
7
- export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds, volume, logLevel, loop, }) => {
7
+ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds, logLevel, loop, playbackRate, }) => {
8
8
  if (!sinkPromises[src]) {
9
9
  sinkPromises[src] = getSinks(src);
10
10
  }
11
11
  const { audio, actualMatroskaTimestamps, isMatroska, getDuration } = await sinkPromises[src];
12
+ let duration = null;
13
+ if (loop) {
14
+ duration = await getDuration();
15
+ }
12
16
  if (audio === null) {
13
- return null;
17
+ return { data: null, durationInSeconds: null };
14
18
  }
15
19
  const timeInSeconds = loop
16
- ? unloopedTimeInSeconds % (await getDuration())
20
+ ? unloopedTimeInSeconds % duration
17
21
  : unloopedTimeInSeconds;
18
22
  const sampleIterator = await audioManager.getIterator({
19
23
  src,
@@ -44,10 +48,15 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
44
48
  // amount of samples to shave from start and end
45
49
  let trimStartInSeconds = 0;
46
50
  let trimEndInSeconds = 0;
47
- // TODO: Apply playback rate
48
51
  // TODO: Apply tone frequency
49
52
  if (isFirstSample) {
50
53
  trimStartInSeconds = timeInSeconds - sample.timestamp;
54
+ if (trimStartInSeconds < 0 && trimStartInSeconds > -1e-10) {
55
+ trimStartInSeconds = 0;
56
+ }
57
+ if (trimStartInSeconds < 0) {
58
+ throw new Error(`trimStartInSeconds is negative: ${trimStartInSeconds}`);
59
+ }
51
60
  }
52
61
  if (isLastSample) {
53
62
  trimEndInSeconds =
@@ -62,7 +71,7 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
62
71
  trimStartInSeconds,
63
72
  trimEndInSeconds,
64
73
  targetNumberOfChannels: TARGET_NUMBER_OF_CHANNELS,
65
- volume,
74
+ playbackRate,
66
75
  });
67
76
  audioDataRaw.close();
68
77
  if (audioData.numberOfFrames === 0) {
@@ -71,8 +80,8 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
71
80
  audioDataArray.push(audioData);
72
81
  }
73
82
  if (audioDataArray.length === 0) {
74
- return null;
83
+ return { data: null, durationInSeconds: duration };
75
84
  }
76
85
  const combined = combineAudioDataAndClosePrevious(audioDataArray);
77
- return combined;
86
+ return { data: combined, durationInSeconds: duration };
78
87
  };
package/dist/caches.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare const keyframeManager: {
6
6
  packetSink: import("mediabunny").EncodedPacketSink;
7
7
  videoSampleSink: import("mediabunny").VideoSampleSink;
8
8
  src: string;
9
- logLevel: import("./log").LogLevel;
9
+ logLevel: LogLevel;
10
10
  }) => Promise<import("./video-extraction/keyframe-bank").KeyframeBank>;
11
11
  addKeyframeBank: ({ src, bank, startTimestampInSeconds, }: {
12
12
  src: string;
@@ -31,7 +31,7 @@ export declare const audioManager: {
31
31
  getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
32
32
  waitForCompletion: () => Promise<boolean>;
33
33
  canSatisfyRequestedTime: (timestamp: number) => boolean;
34
- logOpenFrames: (logLevel: import("./log").LogLevel) => void;
34
+ logOpenFrames: (logLevel: LogLevel) => void;
35
35
  getCacheStats: () => {
36
36
  count: number;
37
37
  size: number;
@@ -46,13 +46,13 @@ export declare const audioManager: {
46
46
  audioSampleSink: import("mediabunny").AudioSampleSink;
47
47
  isMatroska: boolean;
48
48
  actualMatroskaTimestamps: import("./video-extraction/remember-actual-matroska-timestamps").RememberActualMatroskaTimestamps;
49
- logLevel: import("./log").LogLevel;
49
+ logLevel: LogLevel;
50
50
  }) => Promise<{
51
51
  src: string;
52
52
  getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
53
53
  waitForCompletion: () => Promise<boolean>;
54
54
  canSatisfyRequestedTime: (timestamp: number) => boolean;
55
- logOpenFrames: (logLevel: import("./log").LogLevel) => void;
55
+ logOpenFrames: (logLevel: LogLevel) => void;
56
56
  getCacheStats: () => {
57
57
  count: number;
58
58
  size: number;
@@ -70,7 +70,7 @@ export declare const audioManager: {
70
70
  getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
71
71
  waitForCompletion: () => Promise<boolean>;
72
72
  canSatisfyRequestedTime: (timestamp: number) => boolean;
73
- logOpenFrames: (logLevel: import("./log").LogLevel) => void;
73
+ logOpenFrames: (logLevel: LogLevel) => void;
74
74
  getCacheStats: () => {
75
75
  count: number;
76
76
  size: number;
@@ -79,7 +79,7 @@ export declare const audioManager: {
79
79
  prepareForDeletion: () => Promise<void>;
80
80
  startTimestamp: number;
81
81
  } | null;
82
- logOpenFrames: (logLevel: import("./log").LogLevel) => void;
82
+ logOpenFrames: (logLevel: LogLevel) => void;
83
83
  };
84
84
  export declare const getTotalCacheStats: () => Promise<{
85
85
  count: number;
package/dist/caches.js CHANGED
@@ -1,6 +1,5 @@
1
- import { cancelRender } from 'remotion';
1
+ import { cancelRender, Internals } from 'remotion';
2
2
  import { makeAudioManager } from './audio-extraction/audio-manager';
3
- import { Log } from './log';
4
3
  import { makeKeyframeManager } from './video-extraction/keyframe-manager';
5
4
  // TODO: make it dependent on the fps and concurrency
6
5
  export const SAFE_BACK_WINDOW_IN_SECONDS = 1;
@@ -23,21 +22,21 @@ const getUncachedMaxCacheSize = (logLevel) => {
23
22
  if (window.remotion_mediaCacheSizeInBytes > 20000 * 1024 * 1024) {
24
23
  cancelRender(new Error(`The maximum value for the "mediaCacheSizeInBytes" prop is 20GB (${20000 * 1024 * 1024}), got: ${window.remotion_mediaCacheSizeInBytes}`));
25
24
  }
26
- Log.verbose(logLevel, `Using @remotion/media cache size set using "mediaCacheSizeInBytes": ${(window.remotion_mediaCacheSizeInBytes / 1024 / 1024).toFixed(1)} MB`);
25
+ Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Using cache size set using "mediaCacheSizeInBytes": ${(window.remotion_mediaCacheSizeInBytes / 1024 / 1024).toFixed(1)} MB`);
27
26
  return window.remotion_mediaCacheSizeInBytes;
28
27
  }
29
28
  if (window.remotion_initialMemoryAvailable !== undefined &&
30
29
  window.remotion_initialMemoryAvailable !== null) {
31
30
  const value = window.remotion_initialMemoryAvailable / 2;
32
31
  if (value < 240 * 1024 * 1024) {
33
- Log.verbose(logLevel, `Using @remotion/media cache size set based on minimum value of 240MB (which is more than half of the available system memory!)`);
32
+ Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Using cache size set based on minimum value of 240MB (which is more than half of the available system memory!)`);
34
33
  return 240 * 1024 * 1024;
35
34
  }
36
35
  if (value > 20000 * 1024 * 1024) {
37
- Log.verbose(logLevel, `Using @remotion/media cache size set based on maximum value of 20GB (which is less than half of the available system memory)`);
36
+ Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Using cache size set based on maximum value of 20GB (which is less than half of the available system memory)`);
38
37
  return 20000 * 1024 * 1024;
39
38
  }
40
- Log.verbose(logLevel, `Using @remotion/media cache size set based on available memory (50% of available memory): ${(value / 1024 / 1024).toFixed(1)} MB`);
39
+ Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Using cache size set based on available memory (50% of available memory): ${(value / 1024 / 1024).toFixed(1)} MB`);
41
40
  return value;
42
41
  }
43
42
  return 1000 * 1000 * 1000; // 1GB
@@ -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);
@@ -56,8 +77,9 @@ export const resampleAudioData = ({ srcNumberOfChannels, sourceChannels, destina
56
77
  const c = getSourceValues(start, end, 2);
57
78
  const sl = getSourceValues(start, end, 3);
58
79
  const sr = getSourceValues(start, end, 4);
59
- const l2 = l + Math.sqrt(1 / 2) * (c + sl);
60
- const r2 = r + Math.sqrt(1 / 2) * (c + sr);
80
+ const sq = Math.sqrt(1 / 2);
81
+ const l2 = l + sq * (c + sl);
82
+ const r2 = r + sq * (c + sr);
61
83
  destination[newFrameIndex * 2 + 0] = l2;
62
84
  destination[newFrameIndex * 2 + 1] = r2;
63
85
  }