@remotion/media 4.0.364 → 4.0.366

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useContext, useEffect, useMemo, useRef, useState } from 'react';
3
- import { Internals, Audio as RemotionAudio, useBufferState, useCurrentFrame, } from 'remotion';
3
+ import { Internals, Audio as RemotionAudio, useBufferState, useCurrentFrame, useVideoConfig, } from 'remotion';
4
+ import { getTimeInSeconds } from '../get-time-in-seconds';
4
5
  import { MediaPlayer } from '../media-player';
5
6
  import { useLoopDisplay } from '../show-in-timeline';
6
7
  import { useMediaInTimeline } from '../use-media-in-timeline';
7
8
  const { useUnsafeVideoConfig, Timeline, SharedAudioContext, useMediaMutedState, useMediaVolumeState, useFrameForVolumeProp, evaluateVolume, warnAboutTooHighVolume, usePreload, SequenceContext, } = Internals;
8
- const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVolumeCurveBehavior, loop, trimAfter, trimBefore, name, showInTimeline, stack, disallowFallbackToHtml5Audio, toneFrequency, audioStreamIndex, fallbackHtml5AudioProps, }) => {
9
+ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, volume, loopVolumeCurveBehavior, loop, trimAfter, trimBefore, name, showInTimeline, stack, disallowFallbackToHtml5Audio, toneFrequency, audioStreamIndex, fallbackHtml5AudioProps, }) => {
9
10
  const videoConfig = useUnsafeVideoConfig();
10
11
  const frame = useCurrentFrame();
11
12
  const mediaPlayerRef = useRef(null);
@@ -37,6 +38,8 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
37
38
  currentTimeRef.current = currentTime;
38
39
  const preloadedSrc = usePreload(src);
39
40
  const parentSequence = useContext(SequenceContext);
41
+ const isPremounting = Boolean(parentSequence?.premounting);
42
+ const isPostmounting = Boolean(parentSequence?.postmounting);
40
43
  const loopDisplay = useLoopDisplay({
41
44
  loop,
42
45
  mediaDurationInSeconds: videoConfig.durationInFrames,
@@ -59,6 +62,11 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
59
62
  trimAfter,
60
63
  trimBefore,
61
64
  });
65
+ const buffering = useContext(Internals.BufferingContextReact);
66
+ if (!buffering) {
67
+ throw new Error('useMediaPlayback must be used inside a <BufferingContext>');
68
+ }
69
+ const isPlayerBuffering = Internals.useIsPlayerBuffering(buffering);
62
70
  useEffect(() => {
63
71
  if (!sharedAudioContext)
64
72
  return;
@@ -78,6 +86,9 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
78
86
  audioStreamIndex: audioStreamIndex ?? 0,
79
87
  debugOverlay: false,
80
88
  bufferState: buffer,
89
+ isPostmounting,
90
+ isPremounting,
91
+ globalPlaybackRate,
81
92
  });
82
93
  mediaPlayerRef.current = player;
83
94
  player
@@ -117,16 +128,16 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
117
128
  }
118
129
  if (result.type === 'success') {
119
130
  setMediaPlayerReady(true);
120
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[NewAudioForPreview] MediaPlayer initialized successfully`);
131
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[AudioForPreview] MediaPlayer initialized successfully`);
121
132
  }
122
133
  })
123
134
  .catch((error) => {
124
- Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] Failed to initialize MediaPlayer', error);
135
+ Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[AudioForPreview] Failed to initialize MediaPlayer', error);
125
136
  setShouldFallbackToNativeAudio(true);
126
137
  });
127
138
  }
128
139
  catch (error) {
129
- Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] MediaPlayer initialization failed', error);
140
+ Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[AudioForPreview] MediaPlayer initialization failed', error);
130
141
  setShouldFallbackToNativeAudio(true);
131
142
  }
132
143
  return () => {
@@ -135,7 +146,7 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
135
146
  delayHandleRef.current = null;
136
147
  }
137
148
  if (mediaPlayerRef.current) {
138
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[NewAudioForPreview] Disposing MediaPlayer`);
149
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[AudioForPreview] Disposing MediaPlayer`);
139
150
  mediaPlayerRef.current.dispose();
140
151
  mediaPlayerRef.current = null;
141
152
  }
@@ -155,43 +166,30 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
155
166
  audioStreamIndex,
156
167
  disallowFallbackToHtml5Audio,
157
168
  buffer,
169
+ isPremounting,
170
+ isPostmounting,
171
+ globalPlaybackRate,
158
172
  ]);
159
173
  useEffect(() => {
160
174
  const audioPlayer = mediaPlayerRef.current;
161
175
  if (!audioPlayer)
162
176
  return;
163
- if (playing) {
164
- audioPlayer.play().catch((error) => {
165
- Internals.Log.error({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] Failed to play', error);
166
- });
177
+ if (playing && !isPlayerBuffering) {
178
+ audioPlayer.play(currentTimeRef.current);
167
179
  }
168
180
  else {
169
181
  audioPlayer.pause();
170
182
  }
171
- }, [playing, logLevel, mediaPlayerReady]);
183
+ }, [isPlayerBuffering, logLevel, playing]);
172
184
  useEffect(() => {
173
185
  const audioPlayer = mediaPlayerRef.current;
174
186
  if (!audioPlayer || !mediaPlayerReady)
175
187
  return;
176
- audioPlayer.seekTo(currentTime);
177
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[NewAudioForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
178
- }, [currentTime, logLevel, mediaPlayerReady]);
179
- useEffect(() => {
180
- const audioPlayer = mediaPlayerRef.current;
181
- if (!audioPlayer || !mediaPlayerReady)
182
- return;
183
- audioPlayer.onBufferingChange((newBufferingState) => {
184
- if (newBufferingState && !delayHandleRef.current) {
185
- delayHandleRef.current = buffer.delayPlayback();
186
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] MediaPlayer buffering - blocking Remotion playback');
187
- }
188
- else if (!newBufferingState && delayHandleRef.current) {
189
- delayHandleRef.current.unblock();
190
- delayHandleRef.current = null;
191
- Internals.Log.trace({ logLevel, tag: '@remotion/media' }, '[NewAudioForPreview] MediaPlayer unbuffering - unblocking Remotion playback');
192
- }
188
+ audioPlayer.seekTo(currentTime).catch(() => {
189
+ // Might be disposed
193
190
  });
194
- }, [mediaPlayerReady, buffer, logLevel]);
191
+ Internals.Log.trace({ logLevel, tag: '@remotion/media' }, `[AudioForPreview] Updating target time to ${currentTime.toFixed(3)}s`);
192
+ }, [currentTime, logLevel, mediaPlayerReady]);
195
193
  const effectiveMuted = muted || mediaMuted || userPreferredVolume <= 0;
196
194
  useEffect(() => {
197
195
  const audioPlayer = mediaPlayerRef.current;
@@ -206,14 +204,20 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
206
204
  }
207
205
  audioPlayer.setVolume(userPreferredVolume);
208
206
  }, [userPreferredVolume, mediaPlayerReady]);
209
- const effectivePlaybackRate = useMemo(() => playbackRate * globalPlaybackRate, [playbackRate, globalPlaybackRate]);
210
207
  useEffect(() => {
211
208
  const audioPlayer = mediaPlayerRef.current;
212
209
  if (!audioPlayer || !mediaPlayerReady) {
213
210
  return;
214
211
  }
215
- audioPlayer.setPlaybackRate(effectivePlaybackRate);
216
- }, [effectivePlaybackRate, mediaPlayerReady]);
212
+ audioPlayer.setPlaybackRate(playbackRate);
213
+ }, [playbackRate, mediaPlayerReady]);
214
+ useEffect(() => {
215
+ const audioPlayer = mediaPlayerRef.current;
216
+ if (!audioPlayer || !mediaPlayerReady) {
217
+ return;
218
+ }
219
+ audioPlayer.setGlobalPlaybackRate(globalPlaybackRate);
220
+ }, [globalPlaybackRate, mediaPlayerReady]);
217
221
  useEffect(() => {
218
222
  const audioPlayer = mediaPlayerRef.current;
219
223
  if (!audioPlayer || !mediaPlayerReady) {
@@ -221,6 +225,41 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
221
225
  }
222
226
  audioPlayer.setFps(videoConfig.fps);
223
227
  }, [videoConfig.fps, mediaPlayerReady]);
228
+ useEffect(() => {
229
+ const mediaPlayer = mediaPlayerRef.current;
230
+ if (!mediaPlayer || !mediaPlayerReady) {
231
+ return;
232
+ }
233
+ mediaPlayer.setTrimBefore(trimBefore);
234
+ }, [trimBefore, mediaPlayerReady]);
235
+ useEffect(() => {
236
+ const mediaPlayer = mediaPlayerRef.current;
237
+ if (!mediaPlayer || !mediaPlayerReady) {
238
+ return;
239
+ }
240
+ mediaPlayer.setTrimAfter(trimAfter);
241
+ }, [trimAfter, mediaPlayerReady]);
242
+ useEffect(() => {
243
+ const mediaPlayer = mediaPlayerRef.current;
244
+ if (!mediaPlayer || !mediaPlayerReady) {
245
+ return;
246
+ }
247
+ mediaPlayer.setLoop(loop);
248
+ }, [loop, mediaPlayerReady]);
249
+ useEffect(() => {
250
+ const mediaPlayer = mediaPlayerRef.current;
251
+ if (!mediaPlayer || !mediaPlayerReady) {
252
+ return;
253
+ }
254
+ mediaPlayer.setIsPremounting(isPremounting);
255
+ }, [isPremounting, mediaPlayerReady]);
256
+ useEffect(() => {
257
+ const mediaPlayer = mediaPlayerRef.current;
258
+ if (!mediaPlayer || !mediaPlayerReady) {
259
+ return;
260
+ }
261
+ mediaPlayer.setIsPostmounting(isPostmounting);
262
+ }, [isPostmounting, mediaPlayerReady]);
224
263
  if (shouldFallbackToNativeAudio && !disallowFallbackToHtml5Audio) {
225
264
  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 }));
226
265
  }
@@ -228,5 +267,32 @@ const NewAudioForPreview = ({ src, playbackRate, logLevel, muted, volume, loopVo
228
267
  };
229
268
  export const AudioForPreview = ({ loop, src, logLevel, muted, name, volume, loopVolumeCurveBehavior, playbackRate, trimAfter, trimBefore, showInTimeline, stack, disallowFallbackToHtml5Audio, toneFrequency, audioStreamIndex, fallbackHtml5AudioProps, }) => {
230
269
  const preloadedSrc = usePreload(src);
231
- 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 }));
270
+ const frame = useCurrentFrame();
271
+ const videoConfig = useVideoConfig();
272
+ const currentTime = frame / videoConfig.fps;
273
+ const showShow = useMemo(() => {
274
+ return (getTimeInSeconds({
275
+ unloopedTimeInSeconds: currentTime,
276
+ playbackRate: playbackRate ?? 1,
277
+ loop: loop ?? false,
278
+ trimBefore,
279
+ trimAfter,
280
+ mediaDurationInSeconds: Infinity,
281
+ fps: videoConfig.fps,
282
+ ifNoMediaDuration: 'infinity',
283
+ src,
284
+ }) !== null);
285
+ }, [
286
+ currentTime,
287
+ loop,
288
+ playbackRate,
289
+ src,
290
+ trimAfter,
291
+ trimBefore,
292
+ videoConfig.fps,
293
+ ]);
294
+ if (!showShow) {
295
+ return null;
296
+ }
297
+ return (_jsx(AudioForPreviewAssertedShowing, { 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 }));
232
298
  };
@@ -1,14 +1,37 @@
1
- import type { AudioBufferSink } from 'mediabunny';
1
+ import type { AudioBufferSink, WrappedAudioBuffer } from 'mediabunny';
2
2
  export declare const HEALTHY_BUFFER_THRESHOLD_SECONDS = 1;
3
+ export type QueuedNode = {
4
+ node: AudioBufferSourceNode;
5
+ timestamp: number;
6
+ buffer: AudioBuffer;
7
+ };
3
8
  export declare const makeAudioIterator: (audioSink: AudioBufferSink, startFromSecond: number) => {
4
- cleanupAudioQueue: () => void;
5
9
  destroy: () => void;
6
- isReadyToPlay: () => boolean;
7
- setAudioIteratorStarted: (started: boolean) => void;
8
- getNext: () => Promise<IteratorResult<import("mediabunny").WrappedAudioBuffer, void>>;
9
- setAudioBufferHealth: (health: number) => void;
10
+ getNext: () => Promise<IteratorResult<WrappedAudioBuffer, void>>;
10
11
  isDestroyed: () => boolean;
11
- addQueuedAudioNode: (node: AudioBufferSourceNode) => void;
12
+ addQueuedAudioNode: (node: AudioBufferSourceNode, timestamp: number, buffer: AudioBuffer) => void;
12
13
  removeQueuedAudioNode: (node: AudioBufferSourceNode) => void;
14
+ getAndClearAudioChunksForAfterResuming: () => {
15
+ buffer: AudioBuffer;
16
+ timestamp: number;
17
+ }[];
18
+ getQueuedPeriod: (pendingBuffers: WrappedAudioBuffer[]) => {
19
+ from: number;
20
+ until: number;
21
+ } | null;
22
+ tryToSatisfySeek: (time: number, allowWait: boolean) => Promise<{
23
+ type: "not-satisfied";
24
+ reason: string;
25
+ } | {
26
+ type: "satisfied";
27
+ buffers: WrappedAudioBuffer[];
28
+ }>;
29
+ addChunkForAfterResuming: (buffer: AudioBuffer, timestamp: number) => void;
30
+ moveQueuedChunksToPauseQueue: () => void;
31
+ getNumberOfChunksAfterResuming: () => number;
13
32
  };
14
33
  export type AudioIterator = ReturnType<typeof makeAudioIterator>;
34
+ export declare const isAlreadyQueued: (time: number, queuedPeriod: {
35
+ from: number;
36
+ until: number;
37
+ } | undefined | null) => boolean;
@@ -1,43 +1,196 @@
1
+ import { roundTo4Digits } from '../helpers/round-to-4-digits';
1
2
  export const HEALTHY_BUFFER_THRESHOLD_SECONDS = 1;
2
3
  export const makeAudioIterator = (audioSink, startFromSecond) => {
3
4
  let destroyed = false;
4
5
  const iterator = audioSink.buffers(startFromSecond);
5
- let audioIteratorStarted = false;
6
- let audioBufferHealth = 0;
7
- const queuedAudioNodes = new Set();
6
+ const queuedAudioNodes = [];
7
+ const audioChunksForAfterResuming = [];
8
8
  const cleanupAudioQueue = () => {
9
9
  for (const node of queuedAudioNodes) {
10
- node.stop();
10
+ node.node.stop();
11
11
  }
12
- queuedAudioNodes.clear();
12
+ queuedAudioNodes.length = 0;
13
+ };
14
+ let lastReturnedBuffer = null;
15
+ let iteratorEnded = false;
16
+ const getNextOrNullIfNotAvailable = async (allowWait) => {
17
+ const next = iterator.next();
18
+ const result = allowWait
19
+ ? await next
20
+ : await Promise.race([
21
+ next,
22
+ new Promise((resolve) => {
23
+ Promise.resolve().then(() => resolve());
24
+ }),
25
+ ]);
26
+ if (!result) {
27
+ return {
28
+ type: 'need-to-wait-for-it',
29
+ waitPromise: async () => {
30
+ const res = await next;
31
+ if (res.value) {
32
+ lastReturnedBuffer = res.value;
33
+ }
34
+ else {
35
+ iteratorEnded = true;
36
+ }
37
+ return res.value;
38
+ },
39
+ };
40
+ }
41
+ if (result.value) {
42
+ lastReturnedBuffer = result.value;
43
+ }
44
+ else {
45
+ iteratorEnded = true;
46
+ }
47
+ return {
48
+ type: 'got-buffer-or-end',
49
+ buffer: result.value ?? null,
50
+ };
51
+ };
52
+ const tryToSatisfySeek = async (time, allowWait) => {
53
+ if (lastReturnedBuffer) {
54
+ const bufferTimestamp = roundTo4Digits(lastReturnedBuffer.timestamp);
55
+ const bufferEndTimestamp = roundTo4Digits(lastReturnedBuffer.timestamp + lastReturnedBuffer.duration);
56
+ if (roundTo4Digits(time) < bufferTimestamp) {
57
+ return {
58
+ type: 'not-satisfied',
59
+ reason: `iterator is too far, most recently returned ${bufferTimestamp}-${bufferEndTimestamp}, requested ${time}`,
60
+ };
61
+ }
62
+ if (roundTo4Digits(time) <= bufferEndTimestamp) {
63
+ return {
64
+ type: 'satisfied',
65
+ buffers: [lastReturnedBuffer],
66
+ };
67
+ }
68
+ // fall through
69
+ }
70
+ if (iteratorEnded) {
71
+ return {
72
+ type: 'satisfied',
73
+ buffers: lastReturnedBuffer ? [lastReturnedBuffer] : [],
74
+ };
75
+ }
76
+ const toBeReturned = [];
77
+ while (true) {
78
+ const buffer = await getNextOrNullIfNotAvailable(allowWait);
79
+ if (buffer.type === 'need-to-wait-for-it') {
80
+ return {
81
+ type: 'not-satisfied',
82
+ reason: 'iterator did not have buffer ready',
83
+ };
84
+ }
85
+ if (buffer.type === 'got-buffer-or-end') {
86
+ if (buffer.buffer === null) {
87
+ iteratorEnded = true;
88
+ return {
89
+ type: 'satisfied',
90
+ buffers: lastReturnedBuffer ? [lastReturnedBuffer] : [],
91
+ };
92
+ }
93
+ const bufferTimestamp = roundTo4Digits(buffer.buffer.timestamp);
94
+ const bufferEndTimestamp = roundTo4Digits(buffer.buffer.timestamp + buffer.buffer.duration);
95
+ const timestamp = roundTo4Digits(time);
96
+ if (bufferTimestamp <= timestamp && bufferEndTimestamp > timestamp) {
97
+ return {
98
+ type: 'satisfied',
99
+ buffers: [...toBeReturned, buffer.buffer],
100
+ };
101
+ }
102
+ toBeReturned.push(buffer.buffer);
103
+ continue;
104
+ }
105
+ throw new Error('Unreachable');
106
+ }
107
+ };
108
+ const removeAndReturnAllQueuedAudioNodes = () => {
109
+ const nodes = queuedAudioNodes.slice();
110
+ for (const node of nodes) {
111
+ node.node.stop();
112
+ }
113
+ queuedAudioNodes.length = 0;
114
+ return nodes;
115
+ };
116
+ const addChunkForAfterResuming = (buffer, timestamp) => {
117
+ audioChunksForAfterResuming.push({ buffer, timestamp });
118
+ };
119
+ const moveQueuedChunksToPauseQueue = () => {
120
+ const toQueue = removeAndReturnAllQueuedAudioNodes();
121
+ for (const chunk of toQueue) {
122
+ addChunkForAfterResuming(chunk.buffer, chunk.timestamp);
123
+ }
124
+ };
125
+ const getNumberOfChunksAfterResuming = () => {
126
+ return audioChunksForAfterResuming.length;
13
127
  };
14
128
  return {
15
- cleanupAudioQueue,
16
129
  destroy: () => {
17
130
  cleanupAudioQueue();
18
131
  destroyed = true;
19
132
  iterator.return().catch(() => undefined);
133
+ audioChunksForAfterResuming.length = 0;
20
134
  },
21
- isReadyToPlay: () => {
22
- return audioIteratorStarted && audioBufferHealth > 0;
23
- },
24
- setAudioIteratorStarted: (started) => {
25
- audioIteratorStarted = started;
26
- },
27
- getNext: () => {
28
- return iterator.next();
29
- },
30
- setAudioBufferHealth: (health) => {
31
- audioBufferHealth = health;
135
+ getNext: async () => {
136
+ const next = await iterator.next();
137
+ if (next.value) {
138
+ lastReturnedBuffer = next.value;
139
+ }
140
+ else {
141
+ iteratorEnded = true;
142
+ }
143
+ return next;
32
144
  },
33
145
  isDestroyed: () => {
34
146
  return destroyed;
35
147
  },
36
- addQueuedAudioNode: (node) => {
37
- queuedAudioNodes.add(node);
148
+ addQueuedAudioNode: (node, timestamp, buffer) => {
149
+ queuedAudioNodes.push({ node, timestamp, buffer });
38
150
  },
39
151
  removeQueuedAudioNode: (node) => {
40
- queuedAudioNodes.delete(node);
152
+ const index = queuedAudioNodes.findIndex((n) => n.node === node);
153
+ if (index !== -1) {
154
+ queuedAudioNodes.splice(index, 1);
155
+ }
156
+ },
157
+ getAndClearAudioChunksForAfterResuming: () => {
158
+ const chunks = audioChunksForAfterResuming.slice();
159
+ audioChunksForAfterResuming.length = 0;
160
+ return chunks;
41
161
  },
162
+ getQueuedPeriod: (pendingBuffers) => {
163
+ let until = -Infinity;
164
+ let from = Infinity;
165
+ for (const buffer of pendingBuffers) {
166
+ until = Math.max(until, buffer.timestamp + buffer.duration);
167
+ from = Math.min(from, buffer.timestamp);
168
+ }
169
+ for (const node of queuedAudioNodes) {
170
+ until = Math.max(until, node.timestamp + node.buffer.duration);
171
+ from = Math.min(from, node.timestamp);
172
+ }
173
+ for (const chunk of audioChunksForAfterResuming) {
174
+ until = Math.max(until, chunk.timestamp + chunk.buffer.duration);
175
+ from = Math.min(from, chunk.timestamp);
176
+ }
177
+ if (!Number.isFinite(from) || !Number.isFinite(until)) {
178
+ return null;
179
+ }
180
+ return {
181
+ from,
182
+ until,
183
+ };
184
+ },
185
+ tryToSatisfySeek,
186
+ addChunkForAfterResuming,
187
+ moveQueuedChunksToPauseQueue,
188
+ getNumberOfChunksAfterResuming,
42
189
  };
43
190
  };
191
+ export const isAlreadyQueued = (time, queuedPeriod) => {
192
+ if (!queuedPeriod) {
193
+ return false;
194
+ }
195
+ return time >= queuedPeriod.from && time < queuedPeriod.until;
196
+ };
@@ -0,0 +1,66 @@
1
+ import type { InputAudioTrack, WrappedAudioBuffer } from 'mediabunny';
2
+ import type { Nonce } from './nonce-manager';
3
+ export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfNotPremounting, sharedAudioContext, }: {
4
+ audioTrack: InputAudioTrack;
5
+ delayPlaybackHandleIfNotPremounting: () => {
6
+ unblock: () => void;
7
+ };
8
+ sharedAudioContext: AudioContext;
9
+ }) => {
10
+ startAudioIterator: ({ nonce, playbackRate, startFromSecond, getIsPlaying, scheduleAudioNode, }: {
11
+ startFromSecond: number;
12
+ nonce: Nonce;
13
+ playbackRate: number;
14
+ getIsPlaying: () => boolean;
15
+ scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number) => void;
16
+ }) => Promise<void>;
17
+ resumeScheduledAudioChunks: ({ playbackRate, scheduleAudioNode, }: {
18
+ playbackRate: number;
19
+ scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number) => void;
20
+ }) => void;
21
+ pausePlayback: () => void;
22
+ getAudioBufferIterator: () => {
23
+ destroy: () => void;
24
+ getNext: () => Promise<IteratorResult<WrappedAudioBuffer, void>>;
25
+ isDestroyed: () => boolean;
26
+ addQueuedAudioNode: (node: AudioBufferSourceNode, timestamp: number, buffer: AudioBuffer) => void;
27
+ removeQueuedAudioNode: (node: AudioBufferSourceNode) => void;
28
+ getAndClearAudioChunksForAfterResuming: () => {
29
+ buffer: AudioBuffer;
30
+ timestamp: number;
31
+ }[];
32
+ getQueuedPeriod: (pendingBuffers: WrappedAudioBuffer[]) => {
33
+ from: number;
34
+ until: number;
35
+ } | null;
36
+ tryToSatisfySeek: (time: number, allowWait: boolean) => Promise<{
37
+ type: "not-satisfied";
38
+ reason: string;
39
+ } | {
40
+ type: "satisfied";
41
+ buffers: WrappedAudioBuffer[];
42
+ }>;
43
+ addChunkForAfterResuming: (buffer: AudioBuffer, timestamp: number) => void;
44
+ moveQueuedChunksToPauseQueue: () => void;
45
+ getNumberOfChunksAfterResuming: () => number;
46
+ } | null;
47
+ destroy: () => void;
48
+ seek: ({ newTime, nonce, fps, playbackRate, getIsPlaying, scheduleAudioNode, }: {
49
+ newTime: number;
50
+ nonce: Nonce;
51
+ fps: number;
52
+ playbackRate: number;
53
+ getIsPlaying: () => boolean;
54
+ scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number) => void;
55
+ }) => Promise<void>;
56
+ getAudioIteratorsCreated: () => number;
57
+ setMuted: (newMuted: boolean) => void;
58
+ setVolume: (volume: number) => void;
59
+ scheduleAudioChunk: ({ buffer, mediaTimestamp, playbackRate, scheduleAudioNode, }: {
60
+ buffer: AudioBuffer;
61
+ mediaTimestamp: number;
62
+ playbackRate: number;
63
+ scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number) => void;
64
+ }) => void;
65
+ };
66
+ export type AudioIteratorManager = ReturnType<typeof audioIteratorManager>;