@remotion/media 4.0.365 → 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.
- package/dist/audio/audio-for-preview.js +91 -12
- package/dist/audio/audio-preview-iterator.d.ts +9 -3
- package/dist/audio/audio-preview-iterator.js +70 -42
- package/dist/audio-iterator-manager.d.ts +66 -0
- package/dist/audio-iterator-manager.js +181 -0
- package/dist/calculate-playbacktime.d.ts +5 -0
- package/dist/calculate-playbacktime.js +4 -0
- package/dist/debug-overlay/preview-overlay.d.ts +6 -14
- package/dist/debug-overlay/preview-overlay.js +13 -8
- package/dist/esm/index.mjs +866 -466
- package/dist/media-player.d.ts +26 -24
- package/dist/media-player.js +181 -286
- package/dist/nonce-manager.d.ts +9 -0
- package/dist/nonce-manager.js +13 -0
- package/dist/video/video-for-preview.js +83 -11
- package/dist/video-iterator-manager.d.ts +37 -0
- package/dist/video-iterator-manager.js +83 -0
- package/package.json +3 -3
package/dist/media-player.js
CHANGED
|
@@ -1,113 +1,65 @@
|
|
|
1
|
-
import { ALL_FORMATS,
|
|
1
|
+
import { ALL_FORMATS, Input, UrlSource } from 'mediabunny';
|
|
2
2
|
import { Internals } from 'remotion';
|
|
3
|
-
import {
|
|
3
|
+
import { audioIteratorManager, } from './audio-iterator-manager';
|
|
4
|
+
import { calculatePlaybackTime } from './calculate-playbacktime';
|
|
4
5
|
import { drawPreviewOverlay } from './debug-overlay/preview-overlay';
|
|
5
6
|
import { getTimeInSeconds } from './get-time-in-seconds';
|
|
6
7
|
import { isNetworkError } from './is-network-error';
|
|
7
|
-
import {
|
|
8
|
+
import { makeNonceManager } from './nonce-manager';
|
|
9
|
+
import { videoIteratorManager } from './video-iterator-manager';
|
|
8
10
|
export class MediaPlayer {
|
|
9
|
-
constructor({ canvas, src, logLevel, sharedAudioContext, loop, trimBefore, trimAfter, playbackRate, audioStreamIndex, fps, debugOverlay, bufferState, }) {
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
12
|
-
this.debugStats = {
|
|
13
|
-
videoIteratorsCreated: 0,
|
|
14
|
-
audioIteratorsCreated: 0,
|
|
15
|
-
framesRendered: 0,
|
|
16
|
-
};
|
|
17
|
-
this.audioSink = null;
|
|
18
|
-
this.audioBufferIterator = null;
|
|
19
|
-
this.gainNode = null;
|
|
20
|
-
this.currentVolume = 1;
|
|
11
|
+
constructor({ canvas, src, logLevel, sharedAudioContext, loop, trimBefore, trimAfter, playbackRate, globalPlaybackRate, audioStreamIndex, fps, debugOverlay, bufferState, isPremounting, isPostmounting, }) {
|
|
12
|
+
this.audioIteratorManager = null;
|
|
13
|
+
this.videoIteratorManager = null;
|
|
21
14
|
// this is the time difference between Web Audio timeline
|
|
22
15
|
// and media file timeline
|
|
23
16
|
this.audioSyncAnchor = 0;
|
|
24
17
|
this.playing = false;
|
|
25
|
-
this.muted = false;
|
|
26
18
|
this.loop = false;
|
|
27
|
-
this.initialized = false;
|
|
28
19
|
this.debugOverlay = false;
|
|
20
|
+
this.onVideoFrameCallback = null;
|
|
29
21
|
this.initializationPromise = null;
|
|
30
|
-
this.input = null;
|
|
31
|
-
this.currentSeekNonce = 0;
|
|
32
22
|
this.seekPromiseChain = Promise.resolve();
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
39
|
-
this.context.drawImage(frame.canvas, 0, 0);
|
|
40
|
-
this.debugStats.framesRendered++;
|
|
41
|
-
this.drawDebugOverlay();
|
|
42
|
-
if (this.onVideoFrameCallback && this.canvas) {
|
|
43
|
-
this.onVideoFrameCallback(this.canvas);
|
|
23
|
+
this.delayPlaybackHandleIfNotPremounting = () => {
|
|
24
|
+
if (this.isPremounting || this.isPostmounting) {
|
|
25
|
+
return {
|
|
26
|
+
unblock: () => { },
|
|
27
|
+
};
|
|
44
28
|
}
|
|
45
|
-
|
|
29
|
+
return this.bufferState.delayPlayback();
|
|
46
30
|
};
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const iterator = makeAudioIterator(this.audioSink, startFromSecond);
|
|
54
|
-
this.debugStats.audioIteratorsCreated++;
|
|
55
|
-
this.audioBufferIterator = iterator;
|
|
56
|
-
// Schedule up to 3 buffers ahead of the current time
|
|
57
|
-
for (let i = 0; i < 3; i++) {
|
|
58
|
-
const result = await iterator.getNext();
|
|
59
|
-
if (iterator.isDestroyed()) {
|
|
60
|
-
delayHandle.unblock();
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (nonce !== this.currentSeekNonce) {
|
|
64
|
-
delayHandle.unblock();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (!result.value) {
|
|
68
|
-
// media ended
|
|
69
|
-
delayHandle.unblock();
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const { buffer, timestamp } = result.value;
|
|
73
|
-
this.audioChunksForAfterResuming.push({
|
|
74
|
-
buffer,
|
|
75
|
-
timestamp,
|
|
76
|
-
});
|
|
31
|
+
this.scheduleAudioNode = (node, mediaTimestamp) => {
|
|
32
|
+
const currentTime = this.getPlaybackTime();
|
|
33
|
+
const delayWithoutPlaybackRate = mediaTimestamp - currentTime;
|
|
34
|
+
const delay = delayWithoutPlaybackRate / (this.playbackRate * this.globalPlaybackRate);
|
|
35
|
+
if (delay >= 0) {
|
|
36
|
+
node.start(this.sharedAudioContext.currentTime + delay);
|
|
77
37
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.startVideoIterator = async (timeToSeek, nonce) => {
|
|
81
|
-
if (!this.canvasSink) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
this.videoFrameIterator?.destroy();
|
|
85
|
-
const iterator = createVideoIterator(timeToSeek, this.canvasSink);
|
|
86
|
-
this.debugStats.videoIteratorsCreated++;
|
|
87
|
-
this.videoFrameIterator = iterator;
|
|
88
|
-
const delayHandle = this.bufferState.delayPlayback();
|
|
89
|
-
const frameResult = await iterator.getNext();
|
|
90
|
-
delayHandle.unblock();
|
|
91
|
-
if (iterator.isDestroyed()) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (nonce !== this.currentSeekNonce) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (this.videoFrameIterator.isDestroyed()) {
|
|
98
|
-
return;
|
|
38
|
+
else {
|
|
39
|
+
node.start(this.sharedAudioContext.currentTime, -delay);
|
|
99
40
|
}
|
|
100
|
-
|
|
101
|
-
|
|
41
|
+
};
|
|
42
|
+
this.drawDebugOverlay = () => {
|
|
43
|
+
if (!this.debugOverlay)
|
|
102
44
|
return;
|
|
45
|
+
if (this.context && this.canvas) {
|
|
46
|
+
drawPreviewOverlay({
|
|
47
|
+
context: this.context,
|
|
48
|
+
audioTime: this.sharedAudioContext.currentTime,
|
|
49
|
+
audioContextState: this.sharedAudioContext.state,
|
|
50
|
+
audioSyncAnchor: this.audioSyncAnchor,
|
|
51
|
+
audioIteratorManager: this.audioIteratorManager,
|
|
52
|
+
playing: this.playing,
|
|
53
|
+
videoIteratorManager: this.videoIteratorManager,
|
|
54
|
+
});
|
|
103
55
|
}
|
|
104
|
-
this.drawFrame(frameResult.value);
|
|
105
56
|
};
|
|
106
57
|
this.canvas = canvas ?? null;
|
|
107
58
|
this.src = src;
|
|
108
59
|
this.logLevel = logLevel ?? window.remotion_logLevel;
|
|
109
60
|
this.sharedAudioContext = sharedAudioContext;
|
|
110
61
|
this.playbackRate = playbackRate;
|
|
62
|
+
this.globalPlaybackRate = globalPlaybackRate;
|
|
111
63
|
this.loop = loop;
|
|
112
64
|
this.trimBefore = trimBefore;
|
|
113
65
|
this.trimAfter = trimAfter;
|
|
@@ -115,6 +67,13 @@ export class MediaPlayer {
|
|
|
115
67
|
this.fps = fps;
|
|
116
68
|
this.debugOverlay = debugOverlay;
|
|
117
69
|
this.bufferState = bufferState;
|
|
70
|
+
this.isPremounting = isPremounting;
|
|
71
|
+
this.isPostmounting = isPostmounting;
|
|
72
|
+
this.nonceManager = makeNonceManager();
|
|
73
|
+
this.input = new Input({
|
|
74
|
+
source: new UrlSource(this.src),
|
|
75
|
+
formats: ALL_FORMATS,
|
|
76
|
+
});
|
|
118
77
|
if (canvas) {
|
|
119
78
|
const context = canvas.getContext('2d', {
|
|
120
79
|
alpha: true,
|
|
@@ -129,16 +88,8 @@ export class MediaPlayer {
|
|
|
129
88
|
this.context = null;
|
|
130
89
|
}
|
|
131
90
|
}
|
|
132
|
-
isReady() {
|
|
133
|
-
return (this.initialized &&
|
|
134
|
-
Boolean(this.sharedAudioContext) &&
|
|
135
|
-
!this.input?.disposed);
|
|
136
|
-
}
|
|
137
|
-
hasAudio() {
|
|
138
|
-
return Boolean(this.audioSink && this.sharedAudioContext && this.gainNode);
|
|
139
|
-
}
|
|
140
91
|
isDisposalError() {
|
|
141
|
-
return this.input
|
|
92
|
+
return this.input.disposed === true;
|
|
142
93
|
}
|
|
143
94
|
initialize(startTimeUnresolved) {
|
|
144
95
|
const promise = this._initialize(startTimeUnresolved);
|
|
@@ -147,17 +98,11 @@ export class MediaPlayer {
|
|
|
147
98
|
}
|
|
148
99
|
async _initialize(startTimeUnresolved) {
|
|
149
100
|
try {
|
|
150
|
-
|
|
151
|
-
const input = new Input({
|
|
152
|
-
source: urlSource,
|
|
153
|
-
formats: ALL_FORMATS,
|
|
154
|
-
});
|
|
155
|
-
this.input = input;
|
|
156
|
-
if (input.disposed) {
|
|
101
|
+
if (this.input.disposed) {
|
|
157
102
|
return { type: 'disposed' };
|
|
158
103
|
}
|
|
159
104
|
try {
|
|
160
|
-
await input.getFormat();
|
|
105
|
+
await this.input.getFormat();
|
|
161
106
|
}
|
|
162
107
|
catch (error) {
|
|
163
108
|
if (this.isDisposalError()) {
|
|
@@ -171,10 +116,13 @@ export class MediaPlayer {
|
|
|
171
116
|
return { type: 'unknown-container-format' };
|
|
172
117
|
}
|
|
173
118
|
const [durationInSeconds, videoTrack, audioTracks] = await Promise.all([
|
|
174
|
-
input.computeDuration(),
|
|
175
|
-
input.getPrimaryVideoTrack(),
|
|
176
|
-
input.getAudioTracks(),
|
|
119
|
+
this.input.computeDuration(),
|
|
120
|
+
this.input.getPrimaryVideoTrack(),
|
|
121
|
+
this.input.getAudioTracks(),
|
|
177
122
|
]);
|
|
123
|
+
if (this.input.disposed) {
|
|
124
|
+
return { type: 'disposed' };
|
|
125
|
+
}
|
|
178
126
|
this.totalDuration = durationInSeconds;
|
|
179
127
|
const audioTrack = audioTracks[this.audioStreamIndex] ?? null;
|
|
180
128
|
if (!videoTrack && !audioTrack) {
|
|
@@ -185,18 +133,18 @@ export class MediaPlayer {
|
|
|
185
133
|
if (!canDecode) {
|
|
186
134
|
return { type: 'cannot-decode' };
|
|
187
135
|
}
|
|
188
|
-
this.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
136
|
+
if (this.input.disposed) {
|
|
137
|
+
return { type: 'disposed' };
|
|
138
|
+
}
|
|
139
|
+
this.videoIteratorManager = videoIteratorManager({
|
|
140
|
+
videoTrack,
|
|
141
|
+
delayPlaybackHandleIfNotPremounting: this.delayPlaybackHandleIfNotPremounting,
|
|
142
|
+
context: this.context,
|
|
143
|
+
canvas: this.canvas,
|
|
144
|
+
getOnVideoFrameCallback: () => this.onVideoFrameCallback,
|
|
145
|
+
logLevel: this.logLevel,
|
|
146
|
+
drawDebugOverlay: this.drawDebugOverlay,
|
|
192
147
|
});
|
|
193
|
-
this.canvas.width = videoTrack.displayWidth;
|
|
194
|
-
this.canvas.height = videoTrack.displayHeight;
|
|
195
|
-
}
|
|
196
|
-
if (audioTrack && this.sharedAudioContext) {
|
|
197
|
-
this.audioSink = new AudioBufferSink(audioTrack);
|
|
198
|
-
this.gainNode = this.sharedAudioContext.createGain();
|
|
199
|
-
this.gainNode.connect(this.sharedAudioContext.destination);
|
|
200
148
|
}
|
|
201
149
|
const startTime = getTimeInSeconds({
|
|
202
150
|
unloopedTimeInSeconds: startTimeUnresolved,
|
|
@@ -210,17 +158,29 @@ export class MediaPlayer {
|
|
|
210
158
|
src: this.src,
|
|
211
159
|
});
|
|
212
160
|
if (startTime === null) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
this.
|
|
161
|
+
throw new Error(`should have asserted that the time is not null`);
|
|
162
|
+
}
|
|
163
|
+
this.setPlaybackTime(startTime, this.playbackRate * this.globalPlaybackRate);
|
|
164
|
+
if (audioTrack) {
|
|
165
|
+
this.audioIteratorManager = audioIteratorManager({
|
|
166
|
+
audioTrack,
|
|
167
|
+
delayPlaybackHandleIfNotPremounting: this.delayPlaybackHandleIfNotPremounting,
|
|
168
|
+
sharedAudioContext: this.sharedAudioContext,
|
|
169
|
+
});
|
|
218
170
|
}
|
|
219
|
-
|
|
171
|
+
const nonce = this.nonceManager.createAsyncOperation();
|
|
220
172
|
try {
|
|
221
173
|
// intentionally not awaited
|
|
222
|
-
|
|
223
|
-
|
|
174
|
+
if (this.audioIteratorManager) {
|
|
175
|
+
this.audioIteratorManager.startAudioIterator({
|
|
176
|
+
nonce,
|
|
177
|
+
playbackRate: this.playbackRate * this.globalPlaybackRate,
|
|
178
|
+
startFromSecond: startTime,
|
|
179
|
+
getIsPlaying: () => this.playing,
|
|
180
|
+
scheduleAudioNode: this.scheduleAudioNode,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
await this.videoIteratorManager?.startVideoIterator(startTime, nonce);
|
|
224
184
|
}
|
|
225
185
|
catch (error) {
|
|
226
186
|
if (this.isDisposalError()) {
|
|
@@ -240,24 +200,7 @@ export class MediaPlayer {
|
|
|
240
200
|
throw error;
|
|
241
201
|
}
|
|
242
202
|
}
|
|
243
|
-
clearCanvas() {
|
|
244
|
-
if (this.context && this.canvas) {
|
|
245
|
-
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
203
|
async seekTo(time) {
|
|
249
|
-
this.currentSeekNonce++;
|
|
250
|
-
const nonce = this.currentSeekNonce;
|
|
251
|
-
await this.seekPromiseChain;
|
|
252
|
-
this.seekPromiseChain = this.seekToDoNotCallDirectly(time, nonce);
|
|
253
|
-
await this.seekPromiseChain;
|
|
254
|
-
}
|
|
255
|
-
async seekToDoNotCallDirectly(time, nonce) {
|
|
256
|
-
if (nonce !== this.currentSeekNonce) {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
if (!this.isReady())
|
|
260
|
-
return;
|
|
261
204
|
const newTime = getTimeInSeconds({
|
|
262
205
|
unloopedTimeInSeconds: time,
|
|
263
206
|
playbackRate: this.playbackRate,
|
|
@@ -270,138 +213,129 @@ export class MediaPlayer {
|
|
|
270
213
|
src: this.src,
|
|
271
214
|
});
|
|
272
215
|
if (newTime === null) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
216
|
+
throw new Error(`should have asserted that the time is not null`);
|
|
217
|
+
}
|
|
218
|
+
const nonce = this.nonceManager.createAsyncOperation();
|
|
219
|
+
await this.seekPromiseChain;
|
|
220
|
+
this.seekPromiseChain = this.seekToDoNotCallDirectly(newTime, nonce);
|
|
221
|
+
await this.seekPromiseChain;
|
|
222
|
+
}
|
|
223
|
+
async seekToDoNotCallDirectly(newTime, nonce) {
|
|
224
|
+
if (nonce.isStale()) {
|
|
279
225
|
return;
|
|
280
226
|
}
|
|
281
227
|
const currentPlaybackTime = this.getPlaybackTime();
|
|
282
228
|
if (currentPlaybackTime === newTime) {
|
|
283
229
|
return;
|
|
284
230
|
}
|
|
285
|
-
const newAudioSyncAnchor = this.sharedAudioContext.currentTime -
|
|
231
|
+
const newAudioSyncAnchor = this.sharedAudioContext.currentTime -
|
|
232
|
+
newTime / (this.playbackRate * this.globalPlaybackRate);
|
|
286
233
|
const diff = Math.abs(newAudioSyncAnchor - this.audioSyncAnchor);
|
|
287
|
-
if (diff > 0.
|
|
288
|
-
this.setPlaybackTime(newTime);
|
|
289
|
-
}
|
|
290
|
-
// Should return immediately, so it's okay to not use Promise.all here
|
|
291
|
-
const videoSatisfyResult = await this.videoFrameIterator?.tryToSatisfySeek(newTime);
|
|
292
|
-
if (videoSatisfyResult?.type === 'satisfied') {
|
|
293
|
-
this.drawFrame(videoSatisfyResult.frame);
|
|
294
|
-
}
|
|
295
|
-
else if (videoSatisfyResult && this.currentSeekNonce === nonce) {
|
|
296
|
-
this.startVideoIterator(newTime, nonce);
|
|
297
|
-
}
|
|
298
|
-
const queuedPeriod = this.audioBufferIterator?.getQueuedPeriod();
|
|
299
|
-
const currentTimeIsAlreadyQueued = isAlreadyQueued(newTime, queuedPeriod);
|
|
300
|
-
const toBeScheduled = [];
|
|
301
|
-
if (!currentTimeIsAlreadyQueued) {
|
|
302
|
-
const audioSatisfyResult = await this.audioBufferIterator?.tryToSatisfySeek(newTime);
|
|
303
|
-
if (this.currentSeekNonce !== nonce) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
if (!audioSatisfyResult) {
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
if (audioSatisfyResult.type === 'not-satisfied') {
|
|
310
|
-
await this.startAudioIterator(newTime, nonce);
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
toBeScheduled.push(...audioSatisfyResult.buffers);
|
|
314
|
-
}
|
|
315
|
-
// TODO: What is this is beyond the end of the video
|
|
316
|
-
const nextTime = newTime +
|
|
317
|
-
// start of next frame
|
|
318
|
-
(1 / this.fps) * this.playbackRate +
|
|
319
|
-
// need the full duration of the next frame to be queued
|
|
320
|
-
(1 / this.fps) * this.playbackRate;
|
|
321
|
-
const nextIsAlreadyQueued = isAlreadyQueued(nextTime, queuedPeriod);
|
|
322
|
-
if (!nextIsAlreadyQueued) {
|
|
323
|
-
const audioSatisfyResult = await this.audioBufferIterator?.tryToSatisfySeek(nextTime);
|
|
324
|
-
if (this.currentSeekNonce !== nonce) {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (!audioSatisfyResult) {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
if (audioSatisfyResult.type === 'not-satisfied') {
|
|
331
|
-
await this.startAudioIterator(nextTime, nonce);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
toBeScheduled.push(...audioSatisfyResult.buffers);
|
|
335
|
-
}
|
|
336
|
-
for (const buffer of toBeScheduled) {
|
|
337
|
-
if (this.playing) {
|
|
338
|
-
this.scheduleAudioChunk(buffer.buffer, buffer.timestamp);
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
this.audioChunksForAfterResuming.push({
|
|
342
|
-
buffer: buffer.buffer,
|
|
343
|
-
timestamp: buffer.timestamp,
|
|
344
|
-
});
|
|
345
|
-
}
|
|
234
|
+
if (diff > 0.04) {
|
|
235
|
+
this.setPlaybackTime(newTime, this.playbackRate * this.globalPlaybackRate);
|
|
346
236
|
}
|
|
237
|
+
await this.videoIteratorManager?.seek({
|
|
238
|
+
newTime,
|
|
239
|
+
nonce,
|
|
240
|
+
});
|
|
241
|
+
await this.audioIteratorManager?.seek({
|
|
242
|
+
newTime,
|
|
243
|
+
nonce,
|
|
244
|
+
fps: this.fps,
|
|
245
|
+
playbackRate: this.playbackRate * this.globalPlaybackRate,
|
|
246
|
+
getIsPlaying: () => this.playing,
|
|
247
|
+
scheduleAudioNode: this.scheduleAudioNode,
|
|
248
|
+
});
|
|
347
249
|
}
|
|
348
250
|
async play(time) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
251
|
+
const newTime = getTimeInSeconds({
|
|
252
|
+
unloopedTimeInSeconds: time,
|
|
253
|
+
playbackRate: this.playbackRate,
|
|
254
|
+
loop: this.loop,
|
|
255
|
+
trimBefore: this.trimBefore,
|
|
256
|
+
trimAfter: this.trimAfter,
|
|
257
|
+
mediaDurationInSeconds: this.totalDuration ?? null,
|
|
258
|
+
fps: this.fps,
|
|
259
|
+
ifNoMediaDuration: 'infinity',
|
|
260
|
+
src: this.src,
|
|
261
|
+
});
|
|
262
|
+
if (newTime === null) {
|
|
263
|
+
throw new Error(`should have asserted that the time is not null`);
|
|
264
|
+
}
|
|
265
|
+
this.setPlaybackTime(newTime, this.playbackRate * this.globalPlaybackRate);
|
|
352
266
|
this.playing = true;
|
|
353
|
-
|
|
354
|
-
this.
|
|
267
|
+
if (this.audioIteratorManager) {
|
|
268
|
+
this.audioIteratorManager.resumeScheduledAudioChunks({
|
|
269
|
+
playbackRate: this.playbackRate * this.globalPlaybackRate,
|
|
270
|
+
scheduleAudioNode: this.scheduleAudioNode,
|
|
271
|
+
});
|
|
355
272
|
}
|
|
356
273
|
if (this.sharedAudioContext.state === 'suspended') {
|
|
357
274
|
await this.sharedAudioContext.resume();
|
|
358
275
|
}
|
|
359
|
-
this.audioChunksForAfterResuming.length = 0;
|
|
360
276
|
this.drawDebugOverlay();
|
|
361
277
|
}
|
|
362
278
|
pause() {
|
|
363
279
|
this.playing = false;
|
|
364
|
-
|
|
365
|
-
if (toQueue) {
|
|
366
|
-
for (const chunk of toQueue) {
|
|
367
|
-
this.audioChunksForAfterResuming.push({
|
|
368
|
-
buffer: chunk.buffer,
|
|
369
|
-
timestamp: chunk.timestamp,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
}
|
|
280
|
+
this.audioIteratorManager?.pausePlayback();
|
|
373
281
|
this.drawDebugOverlay();
|
|
374
282
|
}
|
|
375
283
|
setMuted(muted) {
|
|
376
|
-
this.muted
|
|
377
|
-
if (this.gainNode) {
|
|
378
|
-
this.gainNode.gain.value = muted ? 0 : this.currentVolume;
|
|
379
|
-
}
|
|
284
|
+
this.audioIteratorManager?.setMuted(muted);
|
|
380
285
|
}
|
|
381
286
|
setVolume(volume) {
|
|
382
|
-
if (!this.
|
|
287
|
+
if (!this.audioIteratorManager) {
|
|
383
288
|
return;
|
|
384
289
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
290
|
+
this.audioIteratorManager.setVolume(volume);
|
|
291
|
+
}
|
|
292
|
+
setTrimBefore(trimBefore) {
|
|
293
|
+
this.trimBefore = trimBefore;
|
|
294
|
+
}
|
|
295
|
+
setTrimAfter(trimAfter) {
|
|
296
|
+
this.trimAfter = trimAfter;
|
|
390
297
|
}
|
|
391
298
|
setDebugOverlay(debugOverlay) {
|
|
392
299
|
this.debugOverlay = debugOverlay;
|
|
393
300
|
}
|
|
301
|
+
updateAfterPlaybackRateChange() {
|
|
302
|
+
if (!this.audioIteratorManager) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
this.setPlaybackTime(this.getPlaybackTime(), this.playbackRate * this.globalPlaybackRate);
|
|
306
|
+
const iterator = this.audioIteratorManager.getAudioBufferIterator();
|
|
307
|
+
if (!iterator) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
iterator.moveQueuedChunksToPauseQueue();
|
|
311
|
+
if (this.playing) {
|
|
312
|
+
this.audioIteratorManager.resumeScheduledAudioChunks({
|
|
313
|
+
playbackRate: this.playbackRate * this.globalPlaybackRate,
|
|
314
|
+
scheduleAudioNode: this.scheduleAudioNode,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
394
318
|
setPlaybackRate(rate) {
|
|
395
319
|
this.playbackRate = rate;
|
|
320
|
+
this.updateAfterPlaybackRateChange();
|
|
321
|
+
}
|
|
322
|
+
setGlobalPlaybackRate(rate) {
|
|
323
|
+
this.globalPlaybackRate = rate;
|
|
324
|
+
this.updateAfterPlaybackRateChange();
|
|
396
325
|
}
|
|
397
326
|
setFps(fps) {
|
|
398
327
|
this.fps = fps;
|
|
399
328
|
}
|
|
329
|
+
setIsPremounting(isPremounting) {
|
|
330
|
+
this.isPremounting = isPremounting;
|
|
331
|
+
}
|
|
332
|
+
setIsPostmounting(isPostmounting) {
|
|
333
|
+
this.isPostmounting = isPostmounting;
|
|
334
|
+
}
|
|
400
335
|
setLoop(loop) {
|
|
401
336
|
this.loop = loop;
|
|
402
337
|
}
|
|
403
338
|
async dispose() {
|
|
404
|
-
this.initialized = false;
|
|
405
339
|
if (this.initializationPromise) {
|
|
406
340
|
try {
|
|
407
341
|
// wait for the init to finished
|
|
@@ -413,63 +347,24 @@ export class MediaPlayer {
|
|
|
413
347
|
// Ignore initialization errors during disposal
|
|
414
348
|
}
|
|
415
349
|
}
|
|
416
|
-
|
|
417
|
-
this.
|
|
418
|
-
this.
|
|
419
|
-
this.
|
|
420
|
-
this.
|
|
350
|
+
// Mark all async operations as stale
|
|
351
|
+
this.nonceManager.createAsyncOperation();
|
|
352
|
+
this.videoIteratorManager?.destroy();
|
|
353
|
+
this.audioIteratorManager?.destroy();
|
|
354
|
+
this.input.dispose();
|
|
421
355
|
}
|
|
422
356
|
getPlaybackTime() {
|
|
423
|
-
return
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
357
|
+
return calculatePlaybackTime({
|
|
358
|
+
audioSyncAnchor: this.audioSyncAnchor,
|
|
359
|
+
currentTime: this.sharedAudioContext.currentTime,
|
|
360
|
+
playbackRate: this.playbackRate * this.globalPlaybackRate,
|
|
361
|
+
});
|
|
427
362
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const targetTime = (mediaTimestamp - (this.trimBefore ?? 0) / this.fps) / this.playbackRate;
|
|
432
|
-
const delay = targetTime + this.audioSyncAnchor - this.sharedAudioContext.currentTime;
|
|
433
|
-
const node = this.sharedAudioContext.createBufferSource();
|
|
434
|
-
node.buffer = buffer;
|
|
435
|
-
node.playbackRate.value = this.playbackRate;
|
|
436
|
-
node.connect(this.gainNode);
|
|
437
|
-
if (delay >= 0) {
|
|
438
|
-
node.start(targetTime + this.audioSyncAnchor);
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
node.start(this.sharedAudioContext.currentTime, -delay);
|
|
442
|
-
}
|
|
443
|
-
this.audioBufferIterator.addQueuedAudioNode(node, mediaTimestamp, buffer);
|
|
444
|
-
node.onended = () => {
|
|
445
|
-
return this.audioBufferIterator.removeQueuedAudioNode(node);
|
|
446
|
-
};
|
|
363
|
+
setPlaybackTime(time, playbackRate) {
|
|
364
|
+
this.audioSyncAnchor =
|
|
365
|
+
this.sharedAudioContext.currentTime - time / playbackRate;
|
|
447
366
|
}
|
|
448
|
-
|
|
367
|
+
setVideoFrameCallback(callback) {
|
|
449
368
|
this.onVideoFrameCallback = callback;
|
|
450
|
-
if (this.initialized && callback && this.canvas) {
|
|
451
|
-
callback(this.canvas);
|
|
452
|
-
}
|
|
453
|
-
return () => {
|
|
454
|
-
if (this.onVideoFrameCallback === callback) {
|
|
455
|
-
this.onVideoFrameCallback = undefined;
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
drawDebugOverlay() {
|
|
460
|
-
if (!this.debugOverlay)
|
|
461
|
-
return;
|
|
462
|
-
if (this.context && this.canvas) {
|
|
463
|
-
drawPreviewOverlay({
|
|
464
|
-
context: this.context,
|
|
465
|
-
stats: this.debugStats,
|
|
466
|
-
audioTime: this.sharedAudioContext.currentTime,
|
|
467
|
-
audioContextState: this.sharedAudioContext.state,
|
|
468
|
-
audioSyncAnchor: this.audioSyncAnchor,
|
|
469
|
-
audioIterator: this.audioBufferIterator,
|
|
470
|
-
audioChunksForAfterResuming: this.audioChunksForAfterResuming,
|
|
471
|
-
playing: this.playing,
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
369
|
}
|
|
475
370
|
}
|