@remotion/media 4.0.486 → 4.0.488

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,10 +1,11 @@
1
- import type { AudioBufferSink } from 'mediabunny';
1
+ import type { AudioBufferSlice } from '../make-iterator-with-priming';
2
2
  export declare const HEALTHY_BUFFER_THRESHOLD_SECONDS = 1;
3
3
  export declare const ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT = 0.1;
4
4
  export type QueuedNode = {
5
5
  node: AudioBufferSourceNode;
6
6
  timestamp: number;
7
7
  buffer: AudioBuffer;
8
+ sourceDurationInSeconds: number;
8
9
  scheduledTime: number;
9
10
  playbackRate: number;
10
11
  scheduledAtAnchor: number;
@@ -13,26 +14,16 @@ export type QueuedPeriod = {
13
14
  from: number;
14
15
  until: number;
15
16
  };
16
- export declare const makeAudioIterator: ({ startFromSecond, maximumTimestamp, audioSink, loop, playbackRate, sequenceDurationInSeconds, unscheduleAudioNode, }: {
17
+ export type MakeAudioIteratorOptions = {
17
18
  startFromSecond: number;
18
- maximumTimestamp: number;
19
- logLevel: "error" | "info" | "trace" | "verbose" | "warn";
20
- audioSink: AudioBufferSink;
21
- loop: boolean;
22
- playbackRate: number;
23
- sequenceDurationInSeconds: number;
19
+ iterator: AsyncGenerator<AudioBufferSlice, void, unknown>;
24
20
  unscheduleAudioNode: (node: AudioBufferSourceNode) => void;
25
- }) => {
21
+ };
22
+ export declare const makeAudioIterator: ({ startFromSecond, iterator, unscheduleAudioNode, }: MakeAudioIteratorOptions) => {
26
23
  destroy: () => void;
27
- getNextFn: () => Promise<IteratorResult<import("../make-iterator-with-priming").BufferWithMediaTimestamp, void>>;
24
+ getNextFn: () => Promise<IteratorResult<AudioBufferSlice, void>>;
28
25
  isDestroyed: () => boolean;
29
- addQueuedAudioNode: ({ node, timestamp, buffer, scheduledTime, scheduledAtAnchor, }: {
30
- node: AudioBufferSourceNode;
31
- timestamp: number;
32
- buffer: AudioBuffer;
33
- scheduledTime: number;
34
- scheduledAtAnchor: number;
35
- }) => void;
26
+ addQueuedAudioNode: (queuedNode: QueuedNode) => void;
36
27
  guessNextTimestamp: () => number;
37
28
  getQueuedPeriod: () => {
38
29
  from: number;
@@ -4,15 +4,15 @@ export declare const getScheduledTime: ({ mediaTimestamp, targetTime, currentTim
4
4
  currentTime: number;
5
5
  sequenceStartTime: number;
6
6
  }) => number;
7
- export declare const getDurationOfNode: ({ bufferDuration, loopSegmentMediaEndTimestamp, offset, originalUnloopedMediaTimestamp, }: {
8
- bufferDuration: number;
9
- loopSegmentMediaEndTimestamp: number;
7
+ export declare const getDurationOfNode: ({ sourceDurationInSeconds, sourceOffsetInSeconds, offset, }: {
8
+ sourceDurationInSeconds: number;
9
+ sourceOffsetInSeconds: number;
10
10
  offset: number;
11
- originalUnloopedMediaTimestamp: number;
12
11
  }) => number;
13
- export declare const getTrimStartForAudioNode: ({ mediaTimestamp, targetTime, sequenceStartTime, combinedPlaybackRate, }: {
12
+ export declare const getTrimStartForAudioNode: ({ mediaTimestamp, targetTime, sequenceStartTime, combinedPlaybackRate, sourceStartOffsetInSeconds, }: {
14
13
  mediaTimestamp: number;
15
14
  targetTime: number;
16
15
  sequenceStartTime: number;
17
16
  combinedPlaybackRate: number;
17
+ sourceStartOffsetInSeconds: number;
18
18
  }) => number;
@@ -1,10 +1,19 @@
1
1
  import { type InputAudioTrack } from 'mediabunny';
2
2
  import { type ScheduleAudioNodeResult } from 'remotion';
3
3
  import type { DelayPlaybackIfNotPremounting } from './delay-playback-if-not-premounting';
4
- import type { BufferWithMediaTimestamp } from './make-iterator-with-priming';
4
+ import { type AudioBufferSlice } from './make-iterator-with-priming';
5
5
  import type { Nonce } from './nonce-manager';
6
6
  import type { SharedAudioContextForMediaPlayer } from './shared-audio-context-for-media-player';
7
- type ScheduleAudioNode = (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number) => ScheduleAudioNodeResult;
7
+ type ScheduleAudioNode = (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number, sourceOffsetInSeconds: number, sourceDurationInSeconds: number) => ScheduleAudioNodeResult;
8
+ export type AudioIteratorAnchor = {
9
+ unloopedStartInSeconds: number;
10
+ mediaStartInSeconds: number;
11
+ };
12
+ export declare const anchorToContinuousTime: ({ anchor, unloopedTimeInSeconds, playbackRate, }: {
13
+ anchor: AudioIteratorAnchor;
14
+ unloopedTimeInSeconds: number;
15
+ playbackRate: number;
16
+ }) => number;
8
17
  export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfNotPremounting, sharedAudioContext, getSequenceEndTimestamp, getSequenceDurationInSeconds, getMediaEndTimestamp, getStartTime, initialMuted, drawDebugOverlay, initialPlaybackRate, initialTrimBefore, initialTrimAfter, initialSequenceOffset, initialSequenceDurationInFrames, initialLoop, initialFps, }: {
9
18
  audioTrack: InputAudioTrack;
10
19
  delayPlaybackHandleIfNotPremounting: () => DelayPlaybackIfNotPremounting;
@@ -23,8 +32,9 @@ export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfN
23
32
  initialLoop: boolean;
24
33
  initialFps: number;
25
34
  }) => {
26
- startAudioIterator: ({ nonce, playbackRate, startFromSecond, scheduleAudioNode, getTargetTime, logLevel, loop, unscheduleAudioNode, getAudioContextCurrentTimeMockedInTest, }: {
35
+ startAudioIterator: ({ nonce, playbackRate, startFromSecond, unloopedStartFromSecond, scheduleAudioNode, getTargetTime, logLevel, loop, unscheduleAudioNode, getAudioContextCurrentTimeMockedInTest, }: {
27
36
  startFromSecond: number;
37
+ unloopedStartFromSecond: number;
28
38
  nonce: Nonce;
29
39
  playbackRate: number;
30
40
  scheduleAudioNode: ScheduleAudioNode;
@@ -36,26 +46,23 @@ export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfN
36
46
  }) => void;
37
47
  getAudioBufferIterator: () => {
38
48
  destroy: () => void;
39
- getNextFn: () => Promise<IteratorResult<BufferWithMediaTimestamp, void>>;
49
+ getNextFn: () => Promise<IteratorResult<AudioBufferSlice, void>>;
40
50
  isDestroyed: () => boolean;
41
- addQueuedAudioNode: ({ node, timestamp, buffer, scheduledTime, scheduledAtAnchor, }: {
42
- node: AudioBufferSourceNode;
43
- timestamp: number;
44
- buffer: AudioBuffer;
45
- scheduledTime: number;
46
- scheduledAtAnchor: number;
47
- }) => void;
51
+ addQueuedAudioNode: (queuedNode: import("./audio/audio-preview-iterator").QueuedNode) => void;
48
52
  guessNextTimestamp: () => number;
49
53
  getQueuedPeriod: () => {
50
54
  from: number;
51
55
  until: number;
52
56
  } | null;
53
57
  } | null;
58
+ getCurrentAnchor: () => AudioIteratorAnchor | null;
54
59
  destroyIterator: () => void;
55
- seek: ({ newTime, nonce, playbackRate, scheduleAudioNode, getTargetTime, logLevel, loop, trimBefore, trimAfter, sequenceOffset, sequenceDurationInFrames, fps, getAudioContextCurrentTimeMockedInTest, }: {
60
+ seek: ({ newTime, unloopedNewTime, nonce, playbackRate, localPlaybackRate, scheduleAudioNode, getTargetTime, logLevel, loop, trimBefore, trimAfter, sequenceOffset, sequenceDurationInFrames, fps, getAudioContextCurrentTimeMockedInTest, }: {
56
61
  newTime: number;
62
+ unloopedNewTime: number;
57
63
  nonce: Nonce;
58
64
  playbackRate: number;
65
+ localPlaybackRate: number;
59
66
  scheduleAudioNode: ScheduleAudioNode;
60
67
  getTargetTime: (mediaTimestamp: number, currentTime: number) => number | null;
61
68
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
@@ -71,13 +78,15 @@ export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfN
71
78
  getTotalAudioScheduledInSeconds: () => number;
72
79
  setMuted: (newMuted: boolean) => void;
73
80
  setVolume: (volume: number) => void;
74
- scheduleAudioChunk: ({ buffer, mediaTimestamp, originalUnloopedMediaTimestamp, playbackRate, scheduleAudioNode, logLevel, }: {
81
+ scheduleAudioChunk: ({ buffer, mediaTimestamp, originalUnloopedMediaTimestamp, sourceOffsetInSeconds, sourceDurationInSeconds, playbackRate, scheduleAudioNode, logLevel, }: {
75
82
  buffer: AudioBuffer;
76
83
  mediaTimestamp: number;
77
84
  playbackRate: number;
78
85
  scheduleAudioNode: ScheduleAudioNode;
79
86
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
80
87
  originalUnloopedMediaTimestamp: number;
88
+ sourceOffsetInSeconds: number;
89
+ sourceDurationInSeconds: number;
81
90
  }) => void;
82
91
  waitForNScheduledNodes: (n: number) => Promise<void>;
83
92
  };
@@ -7,11 +7,12 @@ export declare const drawPreviewOverlay: ({ context, audioTime, audioContextStat
7
7
  } | null;
8
8
  playing: boolean;
9
9
  audioIteratorManager: {
10
- startAudioIterator: ({ nonce, playbackRate, startFromSecond, scheduleAudioNode, getTargetTime, logLevel, loop, unscheduleAudioNode, getAudioContextCurrentTimeMockedInTest, }: {
10
+ startAudioIterator: ({ nonce, playbackRate, startFromSecond, unloopedStartFromSecond, scheduleAudioNode, getTargetTime, logLevel, loop, unscheduleAudioNode, getAudioContextCurrentTimeMockedInTest, }: {
11
11
  startFromSecond: number;
12
+ unloopedStartFromSecond: number;
12
13
  nonce: import("../nonce-manager").Nonce;
13
14
  playbackRate: number;
14
- scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number) => import("remotion").ScheduleAudioNodeResult;
15
+ scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number, sourceOffsetInSeconds: number, sourceDurationInSeconds: number) => import("remotion").ScheduleAudioNodeResult;
15
16
  getTargetTime: (mediaTimestamp: number, currentTime: number) => number | null;
16
17
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
17
18
  loop: boolean;
@@ -20,27 +21,24 @@ export declare const drawPreviewOverlay: ({ context, audioTime, audioContextStat
20
21
  }) => void;
21
22
  getAudioBufferIterator: () => {
22
23
  destroy: () => void;
23
- getNextFn: () => Promise<IteratorResult<import("../make-iterator-with-priming").BufferWithMediaTimestamp, void>>;
24
+ getNextFn: () => Promise<IteratorResult<import("../make-iterator-with-priming").AudioBufferSlice, void>>;
24
25
  isDestroyed: () => boolean;
25
- addQueuedAudioNode: ({ node, timestamp, buffer, scheduledTime, scheduledAtAnchor, }: {
26
- node: AudioBufferSourceNode;
27
- timestamp: number;
28
- buffer: AudioBuffer;
29
- scheduledTime: number;
30
- scheduledAtAnchor: number;
31
- }) => void;
26
+ addQueuedAudioNode: (queuedNode: import("../audio/audio-preview-iterator").QueuedNode) => void;
32
27
  guessNextTimestamp: () => number;
33
28
  getQueuedPeriod: () => {
34
29
  from: number;
35
30
  until: number;
36
31
  } | null;
37
32
  } | null;
33
+ getCurrentAnchor: () => import("../audio-iterator-manager").AudioIteratorAnchor | null;
38
34
  destroyIterator: () => void;
39
- seek: ({ newTime, nonce, playbackRate, scheduleAudioNode, getTargetTime, logLevel, loop, trimBefore, trimAfter, sequenceOffset, sequenceDurationInFrames, fps, getAudioContextCurrentTimeMockedInTest, }: {
35
+ seek: ({ newTime, unloopedNewTime, nonce, playbackRate, localPlaybackRate, scheduleAudioNode, getTargetTime, logLevel, loop, trimBefore, trimAfter, sequenceOffset, sequenceDurationInFrames, fps, getAudioContextCurrentTimeMockedInTest, }: {
40
36
  newTime: number;
37
+ unloopedNewTime: number;
41
38
  nonce: import("../nonce-manager").Nonce;
42
39
  playbackRate: number;
43
- scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number) => import("remotion").ScheduleAudioNodeResult;
40
+ localPlaybackRate: number;
41
+ scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number, sourceOffsetInSeconds: number, sourceDurationInSeconds: number) => import("remotion").ScheduleAudioNodeResult;
44
42
  getTargetTime: (mediaTimestamp: number, currentTime: number) => number | null;
45
43
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
46
44
  loop: boolean;
@@ -55,13 +53,15 @@ export declare const drawPreviewOverlay: ({ context, audioTime, audioContextStat
55
53
  getTotalAudioScheduledInSeconds: () => number;
56
54
  setMuted: (newMuted: boolean) => void;
57
55
  setVolume: (volume: number) => void;
58
- scheduleAudioChunk: ({ buffer, mediaTimestamp, originalUnloopedMediaTimestamp, playbackRate, scheduleAudioNode, logLevel, }: {
56
+ scheduleAudioChunk: ({ buffer, mediaTimestamp, originalUnloopedMediaTimestamp, sourceOffsetInSeconds, sourceDurationInSeconds, playbackRate, scheduleAudioNode, logLevel, }: {
59
57
  buffer: AudioBuffer;
60
58
  mediaTimestamp: number;
61
59
  playbackRate: number;
62
- scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number) => import("remotion").ScheduleAudioNodeResult;
60
+ scheduleAudioNode: (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number, sourceOffsetInSeconds: number, sourceDurationInSeconds: number) => import("remotion").ScheduleAudioNodeResult;
63
61
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
64
62
  originalUnloopedMediaTimestamp: number;
63
+ sourceOffsetInSeconds: number;
64
+ sourceDurationInSeconds: number;
65
65
  }) => void;
66
66
  waitForNScheduledNodes: (n: number) => Promise<void>;
67
67
  } | null;
@@ -146,97 +146,21 @@ import {
146
146
  } from "mediabunny";
147
147
  import { Internals as Internals3 } from "remotion";
148
148
 
149
- // src/make-iterator-with-priming.ts
150
- var AUDIO_PRIMING_SECONDS = 0.5;
151
- async function* makeIteratorWithPrimingInner(audioSink, timeToSeek, maximumTimestamp) {
152
- const primingStart = Math.max(0, timeToSeek - AUDIO_PRIMING_SECONDS);
153
- const iterator = audioSink.buffers(primingStart, maximumTimestamp);
154
- for await (const buffer of iterator) {
155
- if (buffer.timestamp + buffer.duration <= timeToSeek) {
156
- continue;
157
- }
158
- yield {
159
- buffer,
160
- timestamp: buffer.timestamp
161
- };
162
- }
163
- }
164
- async function* makeLoopingIterator({
165
- audioSink,
166
- segmentStartInSeconds,
167
- segmentEndInSeconds,
168
- playbackRate,
169
- sequenceDurationInSeconds
170
- }) {
171
- const duration = segmentEndInSeconds - segmentStartInSeconds;
172
- let iteration = 0;
173
- let broken = false;
174
- while (true) {
175
- for await (const item of makeIteratorWithPrimingInner(audioSink, segmentStartInSeconds, segmentEndInSeconds)) {
176
- const timestamp = item.timestamp + iteration * duration;
177
- const endTimestamp = duration * iteration + (item.timestamp - segmentStartInSeconds + item.buffer.duration);
178
- if (endTimestamp > sequenceDurationInSeconds * playbackRate) {
179
- broken = true;
180
- break;
181
- }
182
- yield {
183
- buffer: item.buffer,
184
- timestamp
185
- };
186
- }
187
- if (broken) {
188
- break;
189
- }
190
- iteration++;
191
- }
192
- }
193
- var makeIteratorWithPriming = ({
194
- audioSink,
195
- timeToSeek,
196
- maximumTimestamp,
197
- loop,
198
- playbackRate,
199
- sequenceDurationInSeconds
200
- }) => {
201
- if (loop) {
202
- return makeLoopingIterator({
203
- audioSink,
204
- segmentStartInSeconds: timeToSeek,
205
- segmentEndInSeconds: maximumTimestamp,
206
- playbackRate,
207
- sequenceDurationInSeconds
208
- });
209
- }
210
- return makeIteratorWithPrimingInner(audioSink, timeToSeek, maximumTimestamp);
211
- };
212
-
213
149
  // src/audio/audio-preview-iterator.ts
214
150
  var ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT = 0.1;
215
151
  var makeAudioIterator = ({
216
152
  startFromSecond,
217
- maximumTimestamp,
218
- audioSink,
219
- loop,
220
- playbackRate,
221
- sequenceDurationInSeconds,
153
+ iterator,
222
154
  unscheduleAudioNode
223
155
  }) => {
224
156
  let destroyed = false;
225
- const iterator = makeIteratorWithPriming({
226
- audioSink,
227
- timeToSeek: startFromSecond,
228
- maximumTimestamp,
229
- loop,
230
- playbackRate,
231
- sequenceDurationInSeconds
232
- });
233
157
  const queuedAudioNodes = [];
234
158
  let mostRecentTimestamp = -Infinity;
235
159
  const cleanupAudioQueue = () => {
236
- for (const node of queuedAudioNodes) {
237
- unscheduleAudioNode(node.node);
160
+ for (const { node } of queuedAudioNodes) {
161
+ unscheduleAudioNode(node);
238
162
  try {
239
- node.node.stop();
163
+ node.stop();
240
164
  } catch {}
241
165
  }
242
166
  queuedAudioNodes.length = 0;
@@ -244,7 +168,7 @@ var makeAudioIterator = ({
244
168
  const getNextFn = async () => {
245
169
  const next = await iterator.next();
246
170
  if (next.value) {
247
- mostRecentTimestamp = Math.max(mostRecentTimestamp, next.value.timestamp + next.value.buffer.duration);
171
+ mostRecentTimestamp = Math.max(mostRecentTimestamp, next.value.timelineTimestamp + next.value.sourceDurationInSeconds);
248
172
  }
249
173
  return next;
250
174
  };
@@ -260,21 +184,8 @@ var makeAudioIterator = ({
260
184
  isDestroyed: () => {
261
185
  return destroyed;
262
186
  },
263
- addQueuedAudioNode: ({
264
- node,
265
- timestamp,
266
- buffer,
267
- scheduledTime,
268
- scheduledAtAnchor
269
- }) => {
270
- queuedAudioNodes.push({
271
- node,
272
- timestamp,
273
- buffer,
274
- scheduledTime,
275
- playbackRate,
276
- scheduledAtAnchor
277
- });
187
+ addQueuedAudioNode: (queuedNode) => {
188
+ queuedAudioNodes.push(queuedNode);
278
189
  },
279
190
  guessNextTimestamp: () => {
280
191
  return !Number.isFinite(mostRecentTimestamp) ? startFromSecond : mostRecentTimestamp;
@@ -283,7 +194,7 @@ var makeAudioIterator = ({
283
194
  let until = -Infinity;
284
195
  let from = Infinity;
285
196
  for (const node of queuedAudioNodes) {
286
- until = Math.max(until, node.timestamp + node.buffer.duration);
197
+ until = Math.max(until, node.timestamp + node.sourceDurationInSeconds);
287
198
  from = Math.min(from, node.timestamp);
288
199
  }
289
200
  if (!Number.isFinite(from) || !Number.isFinite(until)) {
@@ -318,27 +229,23 @@ var getScheduledTime = ({
318
229
  return scheduledTime;
319
230
  };
320
231
  var getDurationOfNode = ({
321
- bufferDuration,
322
- loopSegmentMediaEndTimestamp,
323
- offset,
324
- originalUnloopedMediaTimestamp
232
+ sourceDurationInSeconds,
233
+ sourceOffsetInSeconds,
234
+ offset
325
235
  }) => {
326
- const originalUnloopedMediaEndTime = originalUnloopedMediaTimestamp + bufferDuration;
327
- const needsTrimEnd = originalUnloopedMediaEndTime > loopSegmentMediaEndTimestamp;
328
- const durationMinusOffset = bufferDuration - offset;
329
- const duration = needsTrimEnd ? durationMinusOffset - Math.max(0, originalUnloopedMediaEndTime - loopSegmentMediaEndTimestamp) : durationMinusOffset;
330
- return duration;
236
+ return Math.max(0, sourceDurationInSeconds - (offset - sourceOffsetInSeconds));
331
237
  };
332
238
  var getTrimStartForAudioNode = ({
333
239
  mediaTimestamp,
334
240
  targetTime,
335
241
  sequenceStartTime,
336
- combinedPlaybackRate
242
+ combinedPlaybackRate,
243
+ sourceStartOffsetInSeconds
337
244
  }) => {
338
245
  const needsTrimStart = mediaTimestamp < sequenceStartTime;
339
246
  const offsetBecauseOfTrim = needsTrimStart ? sequenceStartTime - mediaTimestamp : 0;
340
247
  const offsetBecauseOfTooLate = targetTime < 0 ? -targetTime * combinedPlaybackRate : 0;
341
- return offsetBecauseOfTrim + offsetBecauseOfTooLate;
248
+ return sourceStartOffsetInSeconds + offsetBecauseOfTrim + offsetBecauseOfTooLate;
342
249
  };
343
250
 
344
251
  // src/audio/sort-by-priority.ts
@@ -442,7 +349,79 @@ var waitForTurn = ({
442
349
  processNext();
443
350
  };
444
351
 
352
+ // src/make-iterator-with-priming.ts
353
+ var AUDIO_PRIMING_SECONDS = 0.5;
354
+ async function* makeIteratorWithPriming({
355
+ audioSink,
356
+ timeToSeek,
357
+ maximumTimestamp
358
+ }) {
359
+ const primingStart = Math.max(0, timeToSeek - AUDIO_PRIMING_SECONDS);
360
+ const iterator = audioSink.buffers(primingStart, maximumTimestamp);
361
+ for await (const buffer of iterator) {
362
+ const sourceStart = Math.max(buffer.timestamp, timeToSeek);
363
+ const sourceEnd = Math.min(buffer.timestamp + buffer.duration, maximumTimestamp);
364
+ if (sourceEnd <= sourceStart) {
365
+ continue;
366
+ }
367
+ yield {
368
+ buffer,
369
+ timelineTimestamp: sourceStart,
370
+ sourceOffsetInSeconds: sourceStart - buffer.timestamp,
371
+ sourceDurationInSeconds: sourceEnd - sourceStart
372
+ };
373
+ }
374
+ }
375
+ async function* makeLoopingIterator({
376
+ audioSink,
377
+ seekTimeInSeconds,
378
+ loopStartInSeconds,
379
+ segmentEndInSeconds,
380
+ maximumContinuousTimestamp
381
+ }) {
382
+ if (segmentEndInSeconds <= loopStartInSeconds) {
383
+ throw new Error(`Cannot loop audio over an empty range (${loopStartInSeconds} to ${segmentEndInSeconds})`);
384
+ }
385
+ let passStartInSeconds = seekTimeInSeconds;
386
+ let passBaseTimestamp = seekTimeInSeconds;
387
+ while (true) {
388
+ let yieldedInPass = false;
389
+ for await (const item of makeIteratorWithPriming({
390
+ audioSink,
391
+ timeToSeek: passStartInSeconds,
392
+ maximumTimestamp: segmentEndInSeconds
393
+ })) {
394
+ yieldedInPass = true;
395
+ const timelineTimestamp = passBaseTimestamp + (item.timelineTimestamp - passStartInSeconds);
396
+ const sourceDurationInSeconds = Math.min(item.sourceDurationInSeconds, maximumContinuousTimestamp - timelineTimestamp);
397
+ if (sourceDurationInSeconds <= 0) {
398
+ return;
399
+ }
400
+ yield {
401
+ ...item,
402
+ timelineTimestamp,
403
+ sourceDurationInSeconds
404
+ };
405
+ if (timelineTimestamp + sourceDurationInSeconds >= maximumContinuousTimestamp) {
406
+ return;
407
+ }
408
+ }
409
+ if (!yieldedInPass) {
410
+ throw new Error(`Audio loop pass from ${passStartInSeconds} to ${segmentEndInSeconds} yielded no buffers`);
411
+ }
412
+ passBaseTimestamp += segmentEndInSeconds - passStartInSeconds;
413
+ passStartInSeconds = loopStartInSeconds;
414
+ }
415
+ }
416
+
445
417
  // src/audio-iterator-manager.ts
418
+ var anchorToContinuousTime = ({
419
+ anchor,
420
+ unloopedTimeInSeconds,
421
+ playbackRate
422
+ }) => {
423
+ return anchor.mediaStartInSeconds + (unloopedTimeInSeconds - anchor.unloopedStartInSeconds) * playbackRate;
424
+ };
446
425
  var audioIteratorManager = ({
447
426
  audioTrack,
448
427
  delayPlaybackHandleIfNotPremounting,
@@ -477,6 +456,7 @@ var audioIteratorManager = ({
477
456
  gainNode.connect(sharedAudioContext.gainNode);
478
457
  const audioSink = new AudioBufferSink(audioTrack);
479
458
  let audioBufferIterator = null;
459
+ let currentAnchor = null;
480
460
  let audioIteratorsCreated = 0;
481
461
  let totalAudioScheduledInSeconds = 0;
482
462
  let currentDelayHandle = null;
@@ -509,6 +489,8 @@ var audioIteratorManager = ({
509
489
  buffer,
510
490
  mediaTimestamp,
511
491
  originalUnloopedMediaTimestamp,
492
+ sourceOffsetInSeconds,
493
+ sourceDurationInSeconds,
512
494
  playbackRate,
513
495
  scheduleAudioNode,
514
496
  logLevel
@@ -523,7 +505,7 @@ var audioIteratorManager = ({
523
505
  node.buffer = buffer;
524
506
  node.playbackRate.value = playbackRate;
525
507
  node.connect(gainNode);
526
- const started = scheduleAudioNode(node, mediaTimestamp, originalUnloopedMediaTimestamp);
508
+ const started = scheduleAudioNode(node, mediaTimestamp, originalUnloopedMediaTimestamp, sourceOffsetInSeconds, sourceDurationInSeconds);
527
509
  if (started.type === "not-started") {
528
510
  Internals3.Log.verbose({ logLevel, tag: "audio-scheduling" }, "not started, disconnected: %s %s", mediaTimestamp.toFixed(3), buffer.duration.toFixed(3));
529
511
  node.disconnect();
@@ -533,7 +515,9 @@ var audioIteratorManager = ({
533
515
  node,
534
516
  timestamp: mediaTimestamp,
535
517
  buffer,
518
+ sourceDurationInSeconds,
536
519
  scheduledTime: started.scheduledTime,
520
+ playbackRate,
537
521
  scheduledAtAnchor: sharedAudioContext.audioSyncAnchor.value
538
522
  });
539
523
  };
@@ -548,22 +532,24 @@ var audioIteratorManager = ({
548
532
  }
549
533
  const startTime = getStartTime();
550
534
  const sequenceEndTime = getSequenceEndTimestamp();
551
- if (buffer.timestamp + buffer.buffer.duration <= startTime) {
535
+ if (buffer.timelineTimestamp + buffer.sourceDurationInSeconds <= startTime) {
552
536
  return;
553
537
  }
554
- if (buffer.timestamp >= sequenceEndTime) {
538
+ if (buffer.timelineTimestamp >= sequenceEndTime) {
555
539
  return;
556
540
  }
557
- const scheduledStart = Math.max(buffer.timestamp, startTime);
558
- const scheduledEnd = Math.min(buffer.timestamp + buffer.buffer.duration, sequenceEndTime);
541
+ const scheduledStart = Math.max(buffer.timelineTimestamp, startTime);
542
+ const scheduledEnd = Math.min(buffer.timelineTimestamp + buffer.sourceDurationInSeconds, sequenceEndTime);
559
543
  totalAudioScheduledInSeconds += Math.max(0, scheduledEnd - scheduledStart);
560
544
  scheduleAudioChunk({
561
545
  buffer: buffer.buffer.buffer,
562
- mediaTimestamp: buffer.timestamp,
546
+ mediaTimestamp: buffer.timelineTimestamp,
563
547
  playbackRate,
564
548
  scheduleAudioNode,
565
549
  logLevel,
566
- originalUnloopedMediaTimestamp: buffer.buffer.timestamp
550
+ originalUnloopedMediaTimestamp: buffer.buffer.timestamp,
551
+ sourceOffsetInSeconds: buffer.sourceOffsetInSeconds,
552
+ sourceDurationInSeconds: buffer.sourceDurationInSeconds
567
553
  });
568
554
  drawDebugOverlay();
569
555
  };
@@ -611,7 +597,7 @@ var audioIteratorManager = ({
611
597
  onDone();
612
598
  return;
613
599
  }
614
- onScheduled(result.value.timestamp);
600
+ onScheduled(result.value.timelineTimestamp);
615
601
  notifyNodeScheduled();
616
602
  onAudioChunk({
617
603
  buffer: result.value,
@@ -651,6 +637,7 @@ var audioIteratorManager = ({
651
637
  nonce,
652
638
  playbackRate,
653
639
  startFromSecond,
640
+ unloopedStartFromSecond,
654
641
  scheduleAudioNode,
655
642
  getTargetTime,
656
643
  logLevel,
@@ -669,14 +656,25 @@ var audioIteratorManager = ({
669
656
  unblockCurrentDelayHandle();
670
657
  const delayHandle = delayPlaybackHandleIfNotPremounting();
671
658
  currentDelayHandle = delayHandle;
659
+ currentAnchor = {
660
+ unloopedStartInSeconds: unloopedStartFromSecond,
661
+ mediaStartInSeconds: startFromSecond
662
+ };
663
+ const maximumContinuousTimestamp = startFromSecond + getSequenceDurationInSeconds() * playbackRate;
664
+ const source = loop ? makeLoopingIterator({
665
+ audioSink,
666
+ seekTimeInSeconds: startFromSecond,
667
+ loopStartInSeconds: getStartTime(),
668
+ segmentEndInSeconds: maximumTimestamp,
669
+ maximumContinuousTimestamp
670
+ }) : makeIteratorWithPriming({
671
+ audioSink,
672
+ timeToSeek: startFromSecond,
673
+ maximumTimestamp
674
+ });
672
675
  const iterator = makeAudioIterator({
673
676
  startFromSecond,
674
- maximumTimestamp,
675
- audioSink,
676
- logLevel,
677
- loop,
678
- playbackRate,
679
- sequenceDurationInSeconds: getSequenceDurationInSeconds(),
677
+ iterator: source,
680
678
  unscheduleAudioNode
681
679
  });
682
680
  audioIteratorsCreated++;
@@ -707,8 +705,10 @@ var audioIteratorManager = ({
707
705
  };
708
706
  const seek = ({
709
707
  newTime,
708
+ unloopedNewTime,
710
709
  nonce,
711
710
  playbackRate,
711
+ localPlaybackRate,
712
712
  scheduleAudioNode,
713
713
  getTargetTime,
714
714
  logLevel,
@@ -740,18 +740,23 @@ var audioIteratorManager = ({
740
740
  return;
741
741
  }
742
742
  if (audioBufferIterator && !audioBufferIterator.isDestroyed()) {
743
+ const timeToCheck = loop && currentAnchor ? anchorToContinuousTime({
744
+ anchor: currentAnchor,
745
+ unloopedTimeInSeconds: unloopedNewTime,
746
+ playbackRate: localPlaybackRate
747
+ }) : newTime;
743
748
  const queuedPeriod = audioBufferIterator.getQueuedPeriod();
744
749
  const queuedPeriodMinusLatency = queuedPeriod ? {
745
750
  from: queuedPeriod.from - ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT - sharedAudioContext.audioContext.baseLatency - sharedAudioContext.audioContext.outputLatency,
746
751
  until: queuedPeriod.until
747
752
  } : null;
748
- const currentTimeIsAlreadyQueued = isAlreadyQueued(newTime, queuedPeriodMinusLatency);
753
+ const currentTimeIsAlreadyQueued = isAlreadyQueued(timeToCheck, queuedPeriodMinusLatency);
749
754
  if (currentTimeIsAlreadyQueued) {
750
755
  processNext();
751
756
  return;
752
757
  }
753
758
  const currentIteratorTimestamp = audioBufferIterator.guessNextTimestamp();
754
- if (currentIteratorTimestamp < newTime && Math.abs(currentIteratorTimestamp - newTime) < 1) {
759
+ if (currentIteratorTimestamp < timeToCheck && Math.abs(currentIteratorTimestamp - timeToCheck) < 1) {
755
760
  processNext();
756
761
  return;
757
762
  }
@@ -760,6 +765,7 @@ var audioIteratorManager = ({
760
765
  nonce,
761
766
  playbackRate,
762
767
  startFromSecond: newTime,
768
+ unloopedStartFromSecond: unloopedNewTime,
763
769
  scheduleAudioNode,
764
770
  getTargetTime,
765
771
  logLevel,
@@ -771,9 +777,11 @@ var audioIteratorManager = ({
771
777
  return {
772
778
  startAudioIterator,
773
779
  getAudioBufferIterator: () => audioBufferIterator,
780
+ getCurrentAnchor: () => currentAnchor,
774
781
  destroyIterator: () => {
775
782
  audioBufferIterator?.destroy();
776
783
  audioBufferIterator = null;
784
+ currentAnchor = null;
777
785
  unblockCurrentDelayHandle();
778
786
  },
779
787
  seek,
@@ -1203,6 +1211,7 @@ var makePrewarmedVideoIteratorCache = (videoSink) => {
1203
1211
  };
1204
1212
 
1205
1213
  // src/video/video-preview-iterator.ts
1214
+ var MAXIMUM_AWAITED_PEEK_DISTANCE_SECONDS = 0.05;
1206
1215
  var createVideoIterator = async (timeToSeek, cache) => {
1207
1216
  let destroyed = false;
1208
1217
  const iterator = cache.makeIteratorOrUsePrewarmed(timeToSeek);
@@ -1217,17 +1226,61 @@ var createVideoIterator = async (timeToSeek, cache) => {
1217
1226
  }
1218
1227
  lastReturnedFrame = frame;
1219
1228
  };
1220
- const peek = async () => {
1229
+ const setPeekedFrame = (frame) => {
1230
+ peekedFrame = frame;
1231
+ if (peekedFrame === null) {
1232
+ iteratorEnded = true;
1233
+ }
1234
+ return peekedFrame;
1235
+ };
1236
+ const peekIfReady = () => {
1221
1237
  if (peekedFrame) {
1222
- return peekedFrame;
1238
+ return { type: "ready", frame: peekedFrame };
1239
+ }
1240
+ if (iteratorEnded) {
1241
+ return { type: "ready", frame: null };
1223
1242
  }
1224
1243
  const next = iterator.next();
1225
1244
  if (next.type === "ready") {
1226
- peekedFrame = next.frame;
1227
- } else {
1228
- peekedFrame = await next.wait();
1245
+ return { type: "ready", frame: setPeekedFrame(next.frame) };
1229
1246
  }
1230
- return peekedFrame;
1247
+ return {
1248
+ type: "pending",
1249
+ wait: next.wait
1250
+ };
1251
+ };
1252
+ const peek = async () => {
1253
+ const peeked = peekIfReady();
1254
+ if (peeked.type === "ready") {
1255
+ return peeked.frame;
1256
+ }
1257
+ return setPeekedFrame(await peeked.wait());
1258
+ };
1259
+ const getFrameEndTimestampFromPeek = (frame) => {
1260
+ return frame ? roundTo4Digits(frame.timestamp) : Infinity;
1261
+ };
1262
+ const getFrameEndTimestamp = async () => {
1263
+ return getFrameEndTimestampFromPeek(await peek());
1264
+ };
1265
+ const getFrameEndTimestampIfCloseEnough = async ({
1266
+ timestamp,
1267
+ frameTimestamp
1268
+ }) => {
1269
+ const peeked = peekIfReady();
1270
+ if (peeked.type === "ready") {
1271
+ return {
1272
+ type: "ready",
1273
+ timestamp: getFrameEndTimestampFromPeek(peeked.frame)
1274
+ };
1275
+ }
1276
+ if (timestamp - frameTimestamp > MAXIMUM_AWAITED_PEEK_DISTANCE_SECONDS) {
1277
+ return { type: "pending" };
1278
+ }
1279
+ const awaitedPeeked = setPeekedFrame(await peeked.wait());
1280
+ return {
1281
+ type: "ready",
1282
+ timestamp: getFrameEndTimestampFromPeek(awaitedPeeked)
1283
+ };
1231
1284
  };
1232
1285
  const getNextOrNullIfNotAvailable = () => {
1233
1286
  if (peekedFrame) {
@@ -1295,14 +1348,7 @@ var createVideoIterator = async (timeToSeek, cache) => {
1295
1348
  reason: `iterator is too far, most recently returned ${frameTimestamp}`
1296
1349
  };
1297
1350
  }
1298
- let lastFrameDuration = lastReturnedFrame.duration;
1299
- if (lastFrameDuration === 0) {
1300
- const peeked = await peek();
1301
- if (peeked) {
1302
- lastFrameDuration = peeked.timestamp - lastReturnedFrame.timestamp;
1303
- }
1304
- }
1305
- const frameEndTimestamp = roundTo4Digits(lastReturnedFrame.timestamp + lastFrameDuration);
1351
+ const frameEndTimestamp = await getFrameEndTimestamp();
1306
1352
  if (frameTimestamp <= timestamp && frameEndTimestamp > timestamp) {
1307
1353
  return {
1308
1354
  type: "satisfied",
@@ -1345,8 +1391,17 @@ var createVideoIterator = async (timeToSeek, cache) => {
1345
1391
  };
1346
1392
  }
1347
1393
  const frameTimestamp = roundTo4Digits(frame.frame.timestamp);
1348
- const frameEndTimestamp = roundTo4Digits(frame.frame.timestamp + frame.frame.duration);
1349
- if (frameTimestamp <= timestamp && frameEndTimestamp > timestamp) {
1394
+ const frameEndTimestamp = await getFrameEndTimestampIfCloseEnough({
1395
+ frameTimestamp,
1396
+ timestamp
1397
+ });
1398
+ if (frameEndTimestamp.type === "pending") {
1399
+ return {
1400
+ type: "not-satisfied",
1401
+ reason: "iterator did not have next frame ready"
1402
+ };
1403
+ }
1404
+ if (frameTimestamp <= timestamp && frameEndTimestamp.timestamp > timestamp) {
1350
1405
  return {
1351
1406
  type: "satisfied",
1352
1407
  frame: frame.frame
@@ -1770,6 +1825,7 @@ class MediaPlayer {
1770
1825
  nonce,
1771
1826
  playbackRate: this.playbackRate * this.globalPlaybackRate,
1772
1827
  startFromSecond: startTime,
1828
+ unloopedStartFromSecond: startTimeUnresolved,
1773
1829
  scheduleAudioNode: this.scheduleAudioNode,
1774
1830
  getTargetTime: this.getTargetTime,
1775
1831
  logLevel: this.logLevel,
@@ -1801,10 +1857,10 @@ class MediaPlayer {
1801
1857
  __callDispose(__stack, _err, _hasErr);
1802
1858
  }
1803
1859
  }
1804
- seekToWithQueue = async (newTime) => {
1860
+ seekToWithQueue = async (newTime, unloopedNewTime) => {
1805
1861
  const nonce = this.nonceManager.createAsyncOperation();
1806
1862
  await this.seekPromiseChain;
1807
- this.seekPromiseChain = this.seekToDoNotCallDirectly(newTime, nonce);
1863
+ this.seekPromiseChain = this.seekToDoNotCallDirectly(newTime, unloopedNewTime, nonce);
1808
1864
  await this.seekPromiseChain;
1809
1865
  };
1810
1866
  async seekTo(time) {
@@ -1812,9 +1868,9 @@ class MediaPlayer {
1812
1868
  if (newTime === null) {
1813
1869
  throw new Error(`should have asserted that the time is not null`);
1814
1870
  }
1815
- await this.seekToWithQueue(newTime);
1871
+ await this.seekToWithQueue(newTime, time);
1816
1872
  }
1817
- async seekToDoNotCallDirectly(newTime, nonce) {
1873
+ async seekToDoNotCallDirectly(newTime, unloopedNewTime, nonce) {
1818
1874
  if (nonce.isStale()) {
1819
1875
  return;
1820
1876
  }
@@ -1826,8 +1882,10 @@ class MediaPlayer {
1826
1882
  }),
1827
1883
  this.audioIteratorManager?.seek({
1828
1884
  newTime,
1885
+ unloopedNewTime,
1829
1886
  nonce,
1830
1887
  playbackRate: this.playbackRate * this.globalPlaybackRate,
1888
+ localPlaybackRate: this.playbackRate,
1831
1889
  getTargetTime: this.getTargetTime,
1832
1890
  logLevel: this.logLevel,
1833
1891
  loop: this.loop,
@@ -1973,14 +2031,19 @@ class MediaPlayer {
1973
2031
  }
1974
2032
  const globalTime = (currentTime - this.sharedAudioContext.audioSyncAnchor.value) * this.globalPlaybackRate;
1975
2033
  const timeInSeconds = globalTime - this.sequenceOffset;
1976
- const localTime = this.getTrimmedTime(timeInSeconds);
2034
+ const anchor = this.loop ? this.audioIteratorManager?.getCurrentAnchor() ?? null : null;
2035
+ const localTime = anchor ? anchorToContinuousTime({
2036
+ anchor,
2037
+ unloopedTimeInSeconds: timeInSeconds,
2038
+ playbackRate: this.playbackRate
2039
+ }) : this.getTrimmedTime(timeInSeconds);
1977
2040
  if (localTime === null) {
1978
2041
  return null;
1979
2042
  }
1980
2043
  const targetTime = (mediaTimestamp - localTime) / (this.playbackRate * this.globalPlaybackRate);
1981
2044
  return targetTime;
1982
2045
  };
1983
- scheduleAudioNode = (node, mediaTimestamp, originalUnloopedMediaTimestamp) => {
2046
+ scheduleAudioNode = (node, mediaTimestamp, originalUnloopedMediaTimestamp, sourceOffsetInSeconds, sourceDurationInSeconds) => {
1984
2047
  if (!this.sharedAudioContext) {
1985
2048
  throw new Error("Shared audio context not found");
1986
2049
  }
@@ -1993,18 +2056,17 @@ class MediaPlayer {
1993
2056
  };
1994
2057
  }
1995
2058
  const sequenceStartTime = this.getStartTime();
1996
- const loopSegmentMediaEndTimestamp = this.getLoopSegmentMediaEndTimestamp();
1997
2059
  const offset = getTrimStartForAudioNode({
1998
2060
  mediaTimestamp,
1999
2061
  targetTime,
2000
2062
  sequenceStartTime,
2001
- combinedPlaybackRate
2063
+ combinedPlaybackRate,
2064
+ sourceStartOffsetInSeconds: sourceOffsetInSeconds
2002
2065
  });
2003
2066
  const duration = getDurationOfNode({
2004
- bufferDuration: node.buffer?.duration ?? 0,
2005
- loopSegmentMediaEndTimestamp,
2006
- offset,
2007
- originalUnloopedMediaTimestamp
2067
+ sourceDurationInSeconds,
2068
+ sourceOffsetInSeconds,
2069
+ offset
2008
2070
  });
2009
2071
  const scheduledTime = getScheduledTime({
2010
2072
  mediaTimestamp,
@@ -2015,6 +2077,7 @@ class MediaPlayer {
2015
2077
  return this.sharedAudioContext.scheduleAudioNode({
2016
2078
  node,
2017
2079
  mediaTimestamp,
2080
+ sourceOffset: sourceOffsetInSeconds,
2018
2081
  scheduledTime,
2019
2082
  duration,
2020
2083
  offset,
@@ -2961,17 +3024,30 @@ var makeKeyframeBank = async ({
2961
3024
  let hasReachedEndOfVideo = false;
2962
3025
  let lastUsed = Date.now();
2963
3026
  let allocationSize = 0;
2964
- const getDurationOfFrame = (timestamp) => {
3027
+ const getMeasuredDurationOfFrame = (timestamp) => {
2965
3028
  const index = frameTimestamps.indexOf(timestamp);
2966
3029
  if (index === -1) {
2967
3030
  throw new Error(`Frame ${timestamp} not found`);
2968
3031
  }
2969
3032
  const nextTimestamp = frameTimestamps[index + 1];
2970
- if (!nextTimestamp) {
3033
+ if (nextTimestamp === undefined) {
2971
3034
  return null;
2972
3035
  }
2973
3036
  return nextTimestamp - timestamp;
2974
3037
  };
3038
+ const getKnownDurationOfFrame = (timestamp) => {
3039
+ const measuredDuration = getMeasuredDurationOfFrame(timestamp);
3040
+ if (measuredDuration !== null) {
3041
+ return measuredDuration;
3042
+ }
3043
+ if (!hasReachedEndOfVideo) {
3044
+ return null;
3045
+ }
3046
+ return frames[timestamp].duration ?? 0;
3047
+ };
3048
+ const getEstimatedDurationOfFrame = (timestamp) => {
3049
+ return getMeasuredDurationOfFrame(timestamp) ?? frames[timestamp].duration ?? 0;
3050
+ };
2975
3051
  const deleteFrameAtTimestamp = (timestamp) => {
2976
3052
  allocationSize -= getAllocationSize(frames[timestamp]);
2977
3053
  frameTimestamps.splice(frameTimestamps.indexOf(timestamp), 1);
@@ -2993,7 +3069,10 @@ var makeKeyframeBank = async ({
2993
3069
  if (!frames[frameTimestamp]) {
2994
3070
  continue;
2995
3071
  }
2996
- const duration = getDurationOfFrame(frameTimestamp) ?? frames[frameTimestamp].duration;
3072
+ const duration = getKnownDurationOfFrame(frameTimestamp);
3073
+ if (duration === null) {
3074
+ continue;
3075
+ }
2997
3076
  if (frameTimestamp + duration < timestampInSeconds) {
2998
3077
  deleteFrameAtTimestamp(frameTimestamp);
2999
3078
  deletedTimestamps.push(frameTimestamp);
@@ -3005,14 +3084,20 @@ var makeKeyframeBank = async ({
3005
3084
  };
3006
3085
  const hasDecodedEnoughForTimestamp = (timestamp) => {
3007
3086
  const lastFrameTimestamp = frameTimestamps[frameTimestamps.length - 1];
3008
- if (!lastFrameTimestamp) {
3087
+ if (lastFrameTimestamp === undefined) {
3009
3088
  return false;
3010
3089
  }
3011
3090
  const lastFrame = frames[lastFrameTimestamp];
3012
3091
  if (!lastFrame) {
3013
3092
  return true;
3014
3093
  }
3015
- const duration = getDurationOfFrame(lastFrameTimestamp) ?? lastFrame.duration;
3094
+ if (roundTo4Digits(lastFrameTimestamp) >= roundTo4Digits(timestamp)) {
3095
+ return true;
3096
+ }
3097
+ const duration = getKnownDurationOfFrame(lastFrameTimestamp);
3098
+ if (duration === null) {
3099
+ return false;
3100
+ }
3016
3101
  return roundTo4Digits(lastFrameTimestamp + duration) > roundTo4Digits(timestamp);
3017
3102
  };
3018
3103
  const addFrame = (frame, logLevel) => {
@@ -3086,8 +3171,7 @@ var makeKeyframeBank = async ({
3086
3171
  }
3087
3172
  const firstTimestamp = frameTimestamps[0];
3088
3173
  const lastTimestamp = frameTimestamps[frameTimestamps.length - 1];
3089
- const lastFrame = frames[lastTimestamp];
3090
- const lastFrameDuration = getDurationOfFrame(lastTimestamp) ?? lastFrame.duration ?? 0;
3174
+ const lastFrameDuration = getEstimatedDurationOfFrame(lastTimestamp);
3091
3175
  return {
3092
3176
  firstTimestamp,
3093
3177
  lastTimestamp: lastTimestamp + lastFrameDuration
@@ -1,13 +1,21 @@
1
1
  import type { AudioBufferSink, WrappedAudioBuffer } from 'mediabunny';
2
- export type BufferWithMediaTimestamp = {
2
+ export type AudioBufferSlice = {
3
3
  buffer: WrappedAudioBuffer;
4
- timestamp: number;
4
+ timelineTimestamp: number;
5
+ sourceOffsetInSeconds: number;
6
+ sourceDurationInSeconds: number;
5
7
  };
6
- export declare const makeIteratorWithPriming: ({ audioSink, timeToSeek, maximumTimestamp, loop, playbackRate, sequenceDurationInSeconds, }: {
8
+ export declare function makeIteratorWithPriming({ audioSink, timeToSeek, maximumTimestamp }: {
7
9
  audioSink: AudioBufferSink;
8
10
  timeToSeek: number;
9
11
  maximumTimestamp: number;
10
- loop: boolean;
11
- playbackRate: number;
12
- sequenceDurationInSeconds: number;
13
- }) => AsyncGenerator<BufferWithMediaTimestamp, void, unknown>;
12
+ }): AsyncGenerator<AudioBufferSlice, void, unknown>;
13
+ type MakeLoopingIteratorOptions = {
14
+ audioSink: AudioBufferSink;
15
+ seekTimeInSeconds: number;
16
+ loopStartInSeconds: number;
17
+ segmentEndInSeconds: number;
18
+ maximumContinuousTimestamp: number;
19
+ };
20
+ export declare function makeLoopingIterator({ audioSink, seekTimeInSeconds, loopStartInSeconds, segmentEndInSeconds, maximumContinuousTimestamp }: MakeLoopingIteratorOptions): AsyncGenerator<AudioBufferSlice, void, unknown>;
21
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.486",
3
+ "version": "4.0.488",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -22,8 +22,8 @@
22
22
  "make": "tsgo && bun --env-file=../.env.bundle bundle.ts"
23
23
  },
24
24
  "dependencies": {
25
- "mediabunny": "1.50.3",
26
- "remotion": "4.0.486",
25
+ "mediabunny": "1.50.8",
26
+ "remotion": "4.0.488",
27
27
  "zod": "4.3.6"
28
28
  },
29
29
  "peerDependencies": {
@@ -31,8 +31,8 @@
31
31
  "react-dom": ">=16.8.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@remotion/eslint-config-internal": "4.0.486",
35
- "@remotion/player": "4.0.486",
34
+ "@remotion/eslint-config-internal": "4.0.488",
35
+ "@remotion/player": "4.0.488",
36
36
  "@vitest/browser-webdriverio": "4.0.9",
37
37
  "eslint": "9.19.0",
38
38
  "react": "19.2.3",