@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.
- package/dist/audio/audio-preview-iterator.d.ts +8 -17
- package/dist/audio/get-scheduled-time.d.ts +5 -5
- package/dist/audio-iterator-manager.d.ts +22 -13
- package/dist/debug-overlay/preview-overlay.d.ts +14 -14
- package/dist/esm/index.mjs +243 -159
- package/dist/make-iterator-with-priming.d.ts +15 -7
- package/package.json +5 -5
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
17
|
+
export type MakeAudioIteratorOptions = {
|
|
17
18
|
startFromSecond: number;
|
|
18
|
-
|
|
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<
|
|
24
|
+
getNextFn: () => Promise<IteratorResult<AudioBufferSlice, void>>;
|
|
28
25
|
isDestroyed: () => boolean;
|
|
29
|
-
addQueuedAudioNode: (
|
|
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: ({
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
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<
|
|
49
|
+
getNextFn: () => Promise<IteratorResult<AudioBufferSlice, void>>;
|
|
40
50
|
isDestroyed: () => boolean;
|
|
41
|
-
addQueuedAudioNode: (
|
|
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").
|
|
24
|
+
getNextFn: () => Promise<IteratorResult<import("../make-iterator-with-priming").AudioBufferSlice, void>>;
|
|
24
25
|
isDestroyed: () => boolean;
|
|
25
|
-
addQueuedAudioNode: (
|
|
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
|
-
|
|
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;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
160
|
+
for (const { node } of queuedAudioNodes) {
|
|
161
|
+
unscheduleAudioNode(node);
|
|
238
162
|
try {
|
|
239
|
-
node.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
offset
|
|
324
|
-
originalUnloopedMediaTimestamp
|
|
232
|
+
sourceDurationInSeconds,
|
|
233
|
+
sourceOffsetInSeconds,
|
|
234
|
+
offset
|
|
325
235
|
}) => {
|
|
326
|
-
|
|
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.
|
|
535
|
+
if (buffer.timelineTimestamp + buffer.sourceDurationInSeconds <= startTime) {
|
|
552
536
|
return;
|
|
553
537
|
}
|
|
554
|
-
if (buffer.
|
|
538
|
+
if (buffer.timelineTimestamp >= sequenceEndTime) {
|
|
555
539
|
return;
|
|
556
540
|
}
|
|
557
|
-
const scheduledStart = Math.max(buffer.
|
|
558
|
-
const scheduledEnd = Math.min(buffer.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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 <
|
|
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
|
|
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
|
-
|
|
1227
|
-
} else {
|
|
1228
|
-
peekedFrame = await next.wait();
|
|
1245
|
+
return { type: "ready", frame: setPeekedFrame(next.frame) };
|
|
1229
1246
|
}
|
|
1230
|
-
return
|
|
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
|
-
|
|
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 =
|
|
1349
|
-
|
|
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
|
|
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
|
-
|
|
2005
|
-
|
|
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
|
|
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 (
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
|
2
|
+
export type AudioBufferSlice = {
|
|
3
3
|
buffer: WrappedAudioBuffer;
|
|
4
|
-
|
|
4
|
+
timelineTimestamp: number;
|
|
5
|
+
sourceOffsetInSeconds: number;
|
|
6
|
+
sourceDurationInSeconds: number;
|
|
5
7
|
};
|
|
6
|
-
export declare
|
|
8
|
+
export declare function makeIteratorWithPriming({ audioSink, timeToSeek, maximumTimestamp }: {
|
|
7
9
|
audioSink: AudioBufferSink;
|
|
8
10
|
timeToSeek: number;
|
|
9
11
|
maximumTimestamp: number;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
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.
|
|
26
|
-
"remotion": "4.0.
|
|
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.
|
|
35
|
-
"@remotion/player": "4.0.
|
|
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",
|