@remotion/media 4.0.390 → 4.0.392
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 +2 -0
- package/dist/audio/audio-preview-iterator.d.ts +3 -2
- package/dist/audio/audio-preview-iterator.js +2 -2
- package/dist/audio-for-rendering.d.ts +3 -0
- package/dist/audio-for-rendering.js +94 -0
- package/dist/audio-iterator-manager.d.ts +5 -1
- package/dist/audio-iterator-manager.js +21 -5
- package/dist/audio.d.ts +3 -0
- package/dist/audio.js +60 -0
- package/dist/audiodata-to-array.d.ts +0 -0
- package/dist/audiodata-to-array.js +1 -0
- package/dist/convert-audiodata/data-types.d.ts +1 -0
- package/dist/convert-audiodata/data-types.js +22 -0
- package/dist/convert-audiodata/is-planar-format.d.ts +1 -0
- package/dist/convert-audiodata/is-planar-format.js +3 -0
- package/dist/convert-audiodata/log-audiodata.d.ts +1 -0
- package/dist/convert-audiodata/log-audiodata.js +8 -0
- package/dist/convert-audiodata/trim-audiodata.d.ts +0 -0
- package/dist/convert-audiodata/trim-audiodata.js +1 -0
- package/dist/deserialized-audiodata.d.ts +15 -0
- package/dist/deserialized-audiodata.js +26 -0
- package/dist/esm/index.mjs +185 -27
- package/dist/extract-audio.d.ts +7 -0
- package/dist/extract-audio.js +98 -0
- package/dist/extract-frame-via-broadcast-channel.d.ts +15 -0
- package/dist/extract-frame-via-broadcast-channel.js +104 -0
- package/dist/extract-frame.d.ts +27 -0
- package/dist/extract-frame.js +21 -0
- package/dist/extrct-audio.d.ts +7 -0
- package/dist/extrct-audio.js +94 -0
- package/dist/get-frames-since-keyframe.d.ts +22 -0
- package/dist/get-frames-since-keyframe.js +41 -0
- package/dist/get-time-in-seconds.d.ts +8 -0
- package/dist/get-time-in-seconds.js +15 -0
- package/dist/is-network-error.d.ts +6 -0
- package/dist/is-network-error.js +17 -0
- package/dist/keyframe-bank.d.ts +25 -0
- package/dist/keyframe-bank.js +120 -0
- package/dist/keyframe-manager.d.ts +23 -0
- package/dist/keyframe-manager.js +170 -0
- package/dist/log.d.ts +10 -0
- package/dist/log.js +33 -0
- package/dist/media-player.d.ts +4 -1
- package/dist/media-player.js +56 -17
- package/dist/new-video-for-rendering.d.ts +3 -0
- package/dist/new-video-for-rendering.js +108 -0
- package/dist/new-video.d.ts +3 -0
- package/dist/new-video.js +37 -0
- package/dist/prewarm-iterator-for-looping.d.ts +17 -0
- package/dist/prewarm-iterator-for-looping.js +56 -0
- package/dist/props.d.ts +29 -0
- package/dist/props.js +1 -0
- package/dist/remember-actual-matroska-timestamps.d.ts +4 -0
- package/dist/remember-actual-matroska-timestamps.js +19 -0
- package/dist/serialize-videoframe.d.ts +0 -0
- package/dist/serialize-videoframe.js +1 -0
- package/dist/video/media-player.d.ts +62 -0
- package/dist/video/media-player.js +361 -0
- package/dist/video/new-video-for-preview.d.ts +10 -0
- package/dist/video/new-video-for-preview.js +108 -0
- package/dist/video/timeout-utils.d.ts +2 -0
- package/dist/video/timeout-utils.js +18 -0
- package/dist/video/video-for-preview.js +2 -0
- package/dist/video/video-preview-iterator.d.ts +3 -2
- package/dist/video/video-preview-iterator.js +2 -2
- package/dist/video-extraction/media-player.d.ts +64 -0
- package/dist/video-extraction/media-player.js +501 -0
- package/dist/video-extraction/new-video-for-preview.d.ts +10 -0
- package/dist/video-extraction/new-video-for-preview.js +114 -0
- package/dist/video-for-rendering.d.ts +3 -0
- package/dist/video-for-rendering.js +108 -0
- package/dist/video-iterator-manager.d.ts +4 -1
- package/dist/video-iterator-manager.js +27 -4
- package/dist/video.d.ts +3 -0
- package/dist/video.js +37 -0
- package/package.json +3 -3
package/dist/esm/index.mjs
CHANGED
|
@@ -49,6 +49,25 @@ var getTimeInSeconds = ({
|
|
|
49
49
|
}
|
|
50
50
|
return timeInSeconds + (trimBefore ?? 0) / fps;
|
|
51
51
|
};
|
|
52
|
+
var calculateEndTime = ({
|
|
53
|
+
mediaDurationInSeconds,
|
|
54
|
+
ifNoMediaDuration,
|
|
55
|
+
src,
|
|
56
|
+
trimAfter,
|
|
57
|
+
trimBefore,
|
|
58
|
+
fps
|
|
59
|
+
}) => {
|
|
60
|
+
if (mediaDurationInSeconds === null && ifNoMediaDuration === "fail") {
|
|
61
|
+
throw new Error(`Could not determine duration of ${src}, but "loop" was set.`);
|
|
62
|
+
}
|
|
63
|
+
const mediaDuration = Internals.calculateMediaDuration({
|
|
64
|
+
trimAfter,
|
|
65
|
+
mediaDurationInFrames: mediaDurationInSeconds ? mediaDurationInSeconds * fps : Infinity,
|
|
66
|
+
playbackRate: 1,
|
|
67
|
+
trimBefore
|
|
68
|
+
}) / fps;
|
|
69
|
+
return mediaDuration + (trimBefore ?? 0) / fps;
|
|
70
|
+
};
|
|
52
71
|
|
|
53
72
|
// src/media-player.ts
|
|
54
73
|
import { ALL_FORMATS, Input, UrlSource } from "mediabunny";
|
|
@@ -80,9 +99,9 @@ var allowWaitRoutine = async (next, waitFn) => {
|
|
|
80
99
|
};
|
|
81
100
|
|
|
82
101
|
// src/audio/audio-preview-iterator.ts
|
|
83
|
-
var makeAudioIterator = (
|
|
102
|
+
var makeAudioIterator = (startFromSecond, cache) => {
|
|
84
103
|
let destroyed = false;
|
|
85
|
-
const iterator =
|
|
104
|
+
const iterator = cache.makeIteratorOrUsePrewarmed(startFromSecond);
|
|
86
105
|
const queuedAudioNodes = [];
|
|
87
106
|
const audioChunksForAfterResuming = [];
|
|
88
107
|
let mostRecentTimestamp = -Infinity;
|
|
@@ -254,19 +273,83 @@ var isAlreadyQueued = (time, queuedPeriod) => {
|
|
|
254
273
|
return time >= queuedPeriod.from && time < queuedPeriod.until;
|
|
255
274
|
};
|
|
256
275
|
|
|
276
|
+
// src/prewarm-iterator-for-looping.ts
|
|
277
|
+
var makePrewarmedVideoIteratorCache = (videoSink) => {
|
|
278
|
+
const prewarmedVideoIterators = new Map;
|
|
279
|
+
const prewarmIteratorForLooping = ({ timeToSeek }) => {
|
|
280
|
+
if (!prewarmedVideoIterators.has(timeToSeek)) {
|
|
281
|
+
prewarmedVideoIterators.set(timeToSeek, videoSink.canvases(timeToSeek));
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const makeIteratorOrUsePrewarmed = (timeToSeek) => {
|
|
285
|
+
const prewarmedIterator = prewarmedVideoIterators.get(timeToSeek);
|
|
286
|
+
if (prewarmedIterator) {
|
|
287
|
+
prewarmedVideoIterators.delete(timeToSeek);
|
|
288
|
+
return prewarmedIterator;
|
|
289
|
+
}
|
|
290
|
+
const iterator = videoSink.canvases(timeToSeek);
|
|
291
|
+
return iterator;
|
|
292
|
+
};
|
|
293
|
+
const destroy = () => {
|
|
294
|
+
for (const iterator of prewarmedVideoIterators.values()) {
|
|
295
|
+
iterator.return();
|
|
296
|
+
}
|
|
297
|
+
prewarmedVideoIterators.clear();
|
|
298
|
+
};
|
|
299
|
+
return {
|
|
300
|
+
prewarmIteratorForLooping,
|
|
301
|
+
makeIteratorOrUsePrewarmed,
|
|
302
|
+
destroy
|
|
303
|
+
};
|
|
304
|
+
};
|
|
305
|
+
var makePrewarmedAudioIteratorCache = (audioSink) => {
|
|
306
|
+
const prewarmedAudioIterators = new Map;
|
|
307
|
+
const prewarmIteratorForLooping = ({ timeToSeek }) => {
|
|
308
|
+
if (!prewarmedAudioIterators.has(timeToSeek)) {
|
|
309
|
+
prewarmedAudioIterators.set(timeToSeek, audioSink.buffers(timeToSeek));
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const makeIteratorOrUsePrewarmed = (timeToSeek) => {
|
|
313
|
+
const prewarmedIterator = prewarmedAudioIterators.get(timeToSeek);
|
|
314
|
+
if (prewarmedIterator) {
|
|
315
|
+
prewarmedAudioIterators.delete(timeToSeek);
|
|
316
|
+
return prewarmedIterator;
|
|
317
|
+
}
|
|
318
|
+
const iterator = audioSink.buffers(timeToSeek);
|
|
319
|
+
return iterator;
|
|
320
|
+
};
|
|
321
|
+
const destroy = () => {
|
|
322
|
+
for (const iterator of prewarmedAudioIterators.values()) {
|
|
323
|
+
iterator.return();
|
|
324
|
+
}
|
|
325
|
+
prewarmedAudioIterators.clear();
|
|
326
|
+
};
|
|
327
|
+
return {
|
|
328
|
+
prewarmIteratorForLooping,
|
|
329
|
+
makeIteratorOrUsePrewarmed,
|
|
330
|
+
destroy
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
|
|
257
334
|
// src/audio-iterator-manager.ts
|
|
258
335
|
var audioIteratorManager = ({
|
|
259
336
|
audioTrack,
|
|
260
337
|
delayPlaybackHandleIfNotPremounting,
|
|
261
|
-
sharedAudioContext
|
|
338
|
+
sharedAudioContext,
|
|
339
|
+
getIsLooping,
|
|
340
|
+
getEndTime,
|
|
341
|
+
getStartTime,
|
|
342
|
+
updatePlaybackTime
|
|
262
343
|
}) => {
|
|
263
344
|
let muted = false;
|
|
264
345
|
let currentVolume = 1;
|
|
265
346
|
const gainNode = sharedAudioContext.createGain();
|
|
266
347
|
gainNode.connect(sharedAudioContext.destination);
|
|
267
348
|
const audioSink = new AudioBufferSink(audioTrack);
|
|
349
|
+
const prewarmedAudioIteratorCache = makePrewarmedAudioIteratorCache(audioSink);
|
|
268
350
|
let audioBufferIterator = null;
|
|
269
351
|
let audioIteratorsCreated = 0;
|
|
352
|
+
let currentDelayHandle = null;
|
|
270
353
|
const scheduleAudioChunk = ({
|
|
271
354
|
buffer,
|
|
272
355
|
mediaTimestamp,
|
|
@@ -316,24 +399,23 @@ var audioIteratorManager = ({
|
|
|
316
399
|
getIsPlaying,
|
|
317
400
|
scheduleAudioNode
|
|
318
401
|
}) => {
|
|
402
|
+
updatePlaybackTime(startFromSecond);
|
|
319
403
|
audioBufferIterator?.destroy();
|
|
320
404
|
const delayHandle = delayPlaybackHandleIfNotPremounting();
|
|
321
|
-
|
|
405
|
+
currentDelayHandle = delayHandle;
|
|
406
|
+
const iterator = makeAudioIterator(startFromSecond, prewarmedAudioIteratorCache);
|
|
322
407
|
audioIteratorsCreated++;
|
|
323
408
|
audioBufferIterator = iterator;
|
|
324
409
|
try {
|
|
325
410
|
for (let i = 0;i < 3; i++) {
|
|
326
411
|
const result = await iterator.getNext();
|
|
327
412
|
if (iterator.isDestroyed()) {
|
|
328
|
-
delayHandle.unblock();
|
|
329
413
|
return;
|
|
330
414
|
}
|
|
331
415
|
if (nonce.isStale()) {
|
|
332
|
-
delayHandle.unblock();
|
|
333
416
|
return;
|
|
334
417
|
}
|
|
335
418
|
if (!result.value) {
|
|
336
|
-
delayHandle.unblock();
|
|
337
419
|
return;
|
|
338
420
|
}
|
|
339
421
|
onAudioChunk({
|
|
@@ -350,6 +432,7 @@ var audioIteratorManager = ({
|
|
|
350
432
|
throw e;
|
|
351
433
|
} finally {
|
|
352
434
|
delayHandle.unblock();
|
|
435
|
+
currentDelayHandle = null;
|
|
353
436
|
}
|
|
354
437
|
};
|
|
355
438
|
const pausePlayback = () => {
|
|
@@ -367,6 +450,13 @@ var audioIteratorManager = ({
|
|
|
367
450
|
scheduleAudioNode,
|
|
368
451
|
bufferState
|
|
369
452
|
}) => {
|
|
453
|
+
if (getIsLooping()) {
|
|
454
|
+
if (getEndTime() - newTime < 1) {
|
|
455
|
+
prewarmedAudioIteratorCache.prewarmIteratorForLooping({
|
|
456
|
+
timeToSeek: getStartTime()
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
370
460
|
if (!audioBufferIterator) {
|
|
371
461
|
await startAudioIterator({
|
|
372
462
|
nonce,
|
|
@@ -467,8 +557,13 @@ var audioIteratorManager = ({
|
|
|
467
557
|
pausePlayback,
|
|
468
558
|
getAudioBufferIterator: () => audioBufferIterator,
|
|
469
559
|
destroyIterator: () => {
|
|
560
|
+
prewarmedAudioIteratorCache.destroy();
|
|
470
561
|
audioBufferIterator?.destroy();
|
|
471
562
|
audioBufferIterator = null;
|
|
563
|
+
if (currentDelayHandle) {
|
|
564
|
+
currentDelayHandle.unblock();
|
|
565
|
+
currentDelayHandle = null;
|
|
566
|
+
}
|
|
472
567
|
},
|
|
473
568
|
seek,
|
|
474
569
|
getAudioIteratorsCreated: () => audioIteratorsCreated,
|
|
@@ -570,9 +665,9 @@ import { CanvasSink } from "mediabunny";
|
|
|
570
665
|
import { Internals as Internals2 } from "remotion";
|
|
571
666
|
|
|
572
667
|
// src/video/video-preview-iterator.ts
|
|
573
|
-
var createVideoIterator = (timeToSeek,
|
|
668
|
+
var createVideoIterator = (timeToSeek, cache) => {
|
|
574
669
|
let destroyed = false;
|
|
575
|
-
const iterator =
|
|
670
|
+
const iterator = cache.makeIteratorOrUsePrewarmed(timeToSeek);
|
|
576
671
|
let lastReturnedFrame = null;
|
|
577
672
|
let iteratorEnded = false;
|
|
578
673
|
const getNextOrNullIfNotAvailable = async () => {
|
|
@@ -700,11 +795,15 @@ var videoIteratorManager = ({
|
|
|
700
795
|
drawDebugOverlay,
|
|
701
796
|
logLevel,
|
|
702
797
|
getOnVideoFrameCallback,
|
|
703
|
-
videoTrack
|
|
798
|
+
videoTrack,
|
|
799
|
+
getEndTime,
|
|
800
|
+
getStartTime,
|
|
801
|
+
getIsLooping
|
|
704
802
|
}) => {
|
|
705
803
|
let videoIteratorsCreated = 0;
|
|
706
804
|
let videoFrameIterator = null;
|
|
707
805
|
let framesRendered = 0;
|
|
806
|
+
let currentDelayHandle = null;
|
|
708
807
|
if (canvas) {
|
|
709
808
|
canvas.width = videoTrack.displayWidth;
|
|
710
809
|
canvas.height = videoTrack.displayHeight;
|
|
@@ -714,6 +813,7 @@ var videoIteratorManager = ({
|
|
|
714
813
|
fit: "contain",
|
|
715
814
|
alpha: true
|
|
716
815
|
});
|
|
816
|
+
const prewarmedVideoIteratorCache = makePrewarmedVideoIteratorCache(canvasSink);
|
|
717
817
|
const drawFrame = (frame) => {
|
|
718
818
|
if (context && canvas) {
|
|
719
819
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
@@ -729,12 +829,18 @@ var videoIteratorManager = ({
|
|
|
729
829
|
};
|
|
730
830
|
const startVideoIterator = async (timeToSeek, nonce) => {
|
|
731
831
|
videoFrameIterator?.destroy();
|
|
732
|
-
const iterator = createVideoIterator(timeToSeek,
|
|
832
|
+
const iterator = createVideoIterator(timeToSeek, prewarmedVideoIteratorCache);
|
|
733
833
|
videoIteratorsCreated++;
|
|
734
834
|
videoFrameIterator = iterator;
|
|
735
835
|
const delayHandle = delayPlaybackHandleIfNotPremounting();
|
|
736
|
-
|
|
737
|
-
|
|
836
|
+
currentDelayHandle = delayHandle;
|
|
837
|
+
let frameResult;
|
|
838
|
+
try {
|
|
839
|
+
frameResult = await iterator.getNext();
|
|
840
|
+
} finally {
|
|
841
|
+
delayHandle.unblock();
|
|
842
|
+
currentDelayHandle = null;
|
|
843
|
+
}
|
|
738
844
|
if (iterator.isDestroyed()) {
|
|
739
845
|
return;
|
|
740
846
|
}
|
|
@@ -753,6 +859,13 @@ var videoIteratorManager = ({
|
|
|
753
859
|
if (!videoFrameIterator) {
|
|
754
860
|
return;
|
|
755
861
|
}
|
|
862
|
+
if (getIsLooping()) {
|
|
863
|
+
if (getEndTime() - newTime < 1) {
|
|
864
|
+
prewarmedVideoIteratorCache.prewarmIteratorForLooping({
|
|
865
|
+
timeToSeek: getStartTime()
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
}
|
|
756
869
|
const videoSatisfyResult = await videoFrameIterator.tryToSatisfySeek(newTime);
|
|
757
870
|
if (videoSatisfyResult.type === "satisfied") {
|
|
758
871
|
drawFrame(videoSatisfyResult.frame);
|
|
@@ -768,10 +881,15 @@ var videoIteratorManager = ({
|
|
|
768
881
|
getVideoIteratorsCreated: () => videoIteratorsCreated,
|
|
769
882
|
seek,
|
|
770
883
|
destroy: () => {
|
|
884
|
+
prewarmedVideoIteratorCache.destroy();
|
|
771
885
|
videoFrameIterator?.destroy();
|
|
772
886
|
if (context && canvas) {
|
|
773
887
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
774
888
|
}
|
|
889
|
+
if (currentDelayHandle) {
|
|
890
|
+
currentDelayHandle.unblock();
|
|
891
|
+
currentDelayHandle = null;
|
|
892
|
+
}
|
|
775
893
|
videoFrameIterator = null;
|
|
776
894
|
},
|
|
777
895
|
getVideoFrameIterator: () => videoFrameIterator,
|
|
@@ -823,7 +941,8 @@ class MediaPlayer {
|
|
|
823
941
|
bufferState,
|
|
824
942
|
isPremounting,
|
|
825
943
|
isPostmounting,
|
|
826
|
-
onVideoFrameCallback
|
|
944
|
+
onVideoFrameCallback,
|
|
945
|
+
playing
|
|
827
946
|
}) {
|
|
828
947
|
this.canvas = canvas ?? null;
|
|
829
948
|
this.src = src;
|
|
@@ -842,6 +961,7 @@ class MediaPlayer {
|
|
|
842
961
|
this.isPostmounting = isPostmounting;
|
|
843
962
|
this.nonceManager = makeNonceManager();
|
|
844
963
|
this.onVideoFrameCallback = onVideoFrameCallback;
|
|
964
|
+
this.playing = playing;
|
|
845
965
|
this.input = new Input({
|
|
846
966
|
source: new UrlSource(this.src),
|
|
847
967
|
formats: ALL_FORMATS
|
|
@@ -866,9 +986,24 @@ class MediaPlayer {
|
|
|
866
986
|
initialize(startTimeUnresolved) {
|
|
867
987
|
const promise = this._initialize(startTimeUnresolved);
|
|
868
988
|
this.initializationPromise = promise;
|
|
989
|
+
this.seekPromiseChain = promise;
|
|
869
990
|
return promise;
|
|
870
991
|
}
|
|
992
|
+
getStartTime() {
|
|
993
|
+
return (this.trimBefore ?? 0) / this.fps;
|
|
994
|
+
}
|
|
995
|
+
getEndTime() {
|
|
996
|
+
return calculateEndTime({
|
|
997
|
+
mediaDurationInSeconds: this.totalDuration,
|
|
998
|
+
ifNoMediaDuration: "fail",
|
|
999
|
+
src: this.src,
|
|
1000
|
+
trimAfter: this.trimAfter,
|
|
1001
|
+
trimBefore: this.trimBefore,
|
|
1002
|
+
fps: this.fps
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
871
1005
|
async _initialize(startTimeUnresolved) {
|
|
1006
|
+
const delayHandle = this.delayPlaybackHandleIfNotPremounting();
|
|
872
1007
|
try {
|
|
873
1008
|
if (this.input.disposed) {
|
|
874
1009
|
return { type: "disposed" };
|
|
@@ -914,7 +1049,10 @@ class MediaPlayer {
|
|
|
914
1049
|
canvas: this.canvas,
|
|
915
1050
|
getOnVideoFrameCallback: () => this.onVideoFrameCallback,
|
|
916
1051
|
logLevel: this.logLevel,
|
|
917
|
-
drawDebugOverlay: this.drawDebugOverlay
|
|
1052
|
+
drawDebugOverlay: this.drawDebugOverlay,
|
|
1053
|
+
getEndTime: () => this.getEndTime(),
|
|
1054
|
+
getStartTime: () => this.getStartTime(),
|
|
1055
|
+
getIsLooping: () => this.loop
|
|
918
1056
|
});
|
|
919
1057
|
}
|
|
920
1058
|
const startTime = getTimeInSeconds({
|
|
@@ -936,21 +1074,25 @@ class MediaPlayer {
|
|
|
936
1074
|
this.audioIteratorManager = audioIteratorManager({
|
|
937
1075
|
audioTrack,
|
|
938
1076
|
delayPlaybackHandleIfNotPremounting: this.delayPlaybackHandleIfNotPremounting,
|
|
939
|
-
sharedAudioContext: this.sharedAudioContext
|
|
1077
|
+
sharedAudioContext: this.sharedAudioContext,
|
|
1078
|
+
getIsLooping: () => this.loop,
|
|
1079
|
+
getEndTime: () => this.getEndTime(),
|
|
1080
|
+
getStartTime: () => this.getStartTime(),
|
|
1081
|
+
updatePlaybackTime: (time) => this.setPlaybackTime(time, this.playbackRate * this.globalPlaybackRate)
|
|
940
1082
|
});
|
|
941
1083
|
}
|
|
942
1084
|
const nonce = this.nonceManager.createAsyncOperation();
|
|
943
1085
|
try {
|
|
944
|
-
|
|
945
|
-
this.audioIteratorManager.startAudioIterator({
|
|
1086
|
+
await Promise.all([
|
|
1087
|
+
this.audioIteratorManager ? this.audioIteratorManager.startAudioIterator({
|
|
946
1088
|
nonce,
|
|
947
1089
|
playbackRate: this.playbackRate * this.globalPlaybackRate,
|
|
948
1090
|
startFromSecond: startTime,
|
|
949
1091
|
getIsPlaying: () => this.playing,
|
|
950
1092
|
scheduleAudioNode: this.scheduleAudioNode
|
|
951
|
-
})
|
|
952
|
-
|
|
953
|
-
|
|
1093
|
+
}) : Promise.resolve(),
|
|
1094
|
+
this.videoIteratorManager ? this.videoIteratorManager.startVideoIterator(startTime, nonce) : Promise.resolve()
|
|
1095
|
+
]);
|
|
954
1096
|
} catch (error) {
|
|
955
1097
|
if (this.isDisposalError()) {
|
|
956
1098
|
return { type: "disposed" };
|
|
@@ -966,6 +1108,8 @@ class MediaPlayer {
|
|
|
966
1108
|
}
|
|
967
1109
|
Internals3.Log.error({ logLevel: this.logLevel, tag: "@remotion/media" }, "[MediaPlayer] Failed to initialize", error);
|
|
968
1110
|
throw error;
|
|
1111
|
+
} finally {
|
|
1112
|
+
delayHandle.unblock();
|
|
969
1113
|
}
|
|
970
1114
|
}
|
|
971
1115
|
async seekTo(time) {
|
|
@@ -1013,6 +1157,9 @@ class MediaPlayer {
|
|
|
1013
1157
|
]);
|
|
1014
1158
|
}
|
|
1015
1159
|
async play(time) {
|
|
1160
|
+
if (this.playing) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1016
1163
|
const newTime = getTimeInSeconds({
|
|
1017
1164
|
unloopedTimeInSeconds: time,
|
|
1018
1165
|
playbackRate: this.playbackRate,
|
|
@@ -1049,6 +1196,9 @@ class MediaPlayer {
|
|
|
1049
1196
|
return this.bufferState.delayPlayback();
|
|
1050
1197
|
};
|
|
1051
1198
|
pause() {
|
|
1199
|
+
if (!this.playing) {
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1052
1202
|
this.playing = false;
|
|
1053
1203
|
this.audioIteratorManager?.pausePlayback();
|
|
1054
1204
|
this.drawDebugOverlay();
|
|
@@ -1083,12 +1233,16 @@ class MediaPlayer {
|
|
|
1083
1233
|
this.audioIteratorManager?.destroyIterator();
|
|
1084
1234
|
}
|
|
1085
1235
|
setTrimBefore(trimBefore, unloopedTimeInSeconds) {
|
|
1086
|
-
this.trimBefore
|
|
1087
|
-
|
|
1236
|
+
if (this.trimBefore !== trimBefore) {
|
|
1237
|
+
this.trimBefore = trimBefore;
|
|
1238
|
+
this.updateAfterTrimChange(unloopedTimeInSeconds);
|
|
1239
|
+
}
|
|
1088
1240
|
}
|
|
1089
1241
|
setTrimAfter(trimAfter, unloopedTimeInSeconds) {
|
|
1090
|
-
this.trimAfter
|
|
1091
|
-
|
|
1242
|
+
if (this.trimAfter !== trimAfter) {
|
|
1243
|
+
this.trimAfter = trimAfter;
|
|
1244
|
+
this.updateAfterTrimChange(unloopedTimeInSeconds);
|
|
1245
|
+
}
|
|
1092
1246
|
}
|
|
1093
1247
|
setDebugOverlay(debugOverlay) {
|
|
1094
1248
|
this.debugOverlay = debugOverlay;
|
|
@@ -1439,6 +1593,7 @@ var AudioForPreviewAssertedShowing = ({
|
|
|
1439
1593
|
throw new Error("useMediaPlayback must be used inside a <BufferingContext>");
|
|
1440
1594
|
}
|
|
1441
1595
|
const isPlayerBuffering = Internals6.useIsPlayerBuffering(bufferingContext);
|
|
1596
|
+
const initialPlaying = useRef(playing && !isPlayerBuffering);
|
|
1442
1597
|
const initialIsPremounting = useRef(isPremounting);
|
|
1443
1598
|
const initialIsPostmounting = useRef(isPostmounting);
|
|
1444
1599
|
const initialGlobalPlaybackRate = useRef(globalPlaybackRate);
|
|
@@ -1465,7 +1620,8 @@ var AudioForPreviewAssertedShowing = ({
|
|
|
1465
1620
|
isPostmounting: initialIsPostmounting.current,
|
|
1466
1621
|
isPremounting: initialIsPremounting.current,
|
|
1467
1622
|
globalPlaybackRate: initialGlobalPlaybackRate.current,
|
|
1468
|
-
onVideoFrameCallback: null
|
|
1623
|
+
onVideoFrameCallback: null,
|
|
1624
|
+
playing: initialPlaying.current
|
|
1469
1625
|
});
|
|
1470
1626
|
mediaPlayerRef.current = player;
|
|
1471
1627
|
player.initialize(currentTimeRef.current).then((result) => {
|
|
@@ -3778,6 +3934,7 @@ var VideoForPreviewAssertedShowing = ({
|
|
|
3778
3934
|
throw new Error("useMediaPlayback must be used inside a <BufferingContext>");
|
|
3779
3935
|
}
|
|
3780
3936
|
const isPlayerBuffering = Internals15.useIsPlayerBuffering(buffering);
|
|
3937
|
+
const initialPlaying = useRef2(playing && !isPlayerBuffering);
|
|
3781
3938
|
const initialIsPremounting = useRef2(isPremounting);
|
|
3782
3939
|
const initialIsPostmounting = useRef2(isPostmounting);
|
|
3783
3940
|
const initialGlobalPlaybackRate = useRef2(globalPlaybackRate);
|
|
@@ -3804,7 +3961,8 @@ var VideoForPreviewAssertedShowing = ({
|
|
|
3804
3961
|
isPremounting: initialIsPremounting.current,
|
|
3805
3962
|
isPostmounting: initialIsPostmounting.current,
|
|
3806
3963
|
globalPlaybackRate: initialGlobalPlaybackRate.current,
|
|
3807
|
-
onVideoFrameCallback: initialOnVideoFrameRef.current ?? null
|
|
3964
|
+
onVideoFrameCallback: initialOnVideoFrameRef.current ?? null,
|
|
3965
|
+
playing: initialPlaying.current
|
|
3808
3966
|
});
|
|
3809
3967
|
mediaPlayerRef.current = player;
|
|
3810
3968
|
player.initialize(currentTimeRef.current).then((result) => {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PcmS16AudioData } from './convert-audiodata/convert-audiodata';
|
|
2
|
+
export declare const extractAudio: ({ src, timeInSeconds, durationInSeconds, volume, }: {
|
|
3
|
+
src: string;
|
|
4
|
+
timeInSeconds: number;
|
|
5
|
+
durationInSeconds: number;
|
|
6
|
+
volume: number;
|
|
7
|
+
}) => Promise<PcmS16AudioData | null>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { combineAudioDataAndClosePrevious } from './convert-audiodata/combine-audiodata';
|
|
2
|
+
import { convertAudioData } from './convert-audiodata/convert-audiodata';
|
|
3
|
+
import { TARGET_NUMBER_OF_CHANNELS, TARGET_SAMPLE_RATE, } from './convert-audiodata/resample-audiodata';
|
|
4
|
+
import { sinkPromises } from './extract-frame';
|
|
5
|
+
import { getSinks } from './video-extraction/get-frames-since-keyframe';
|
|
6
|
+
export const extractAudio = async ({ src, timeInSeconds, durationInSeconds, volume, }) => {
|
|
7
|
+
console.time('extractAudio');
|
|
8
|
+
if (!sinkPromises[src]) {
|
|
9
|
+
sinkPromises[src] = getSinks(src);
|
|
10
|
+
}
|
|
11
|
+
const { audio, actualMatroskaTimestamps, isMatroska } = await sinkPromises[src];
|
|
12
|
+
if (audio === null) {
|
|
13
|
+
console.timeEnd('extractAudio');
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
// https://discord.com/channels/@me/1409810025844838481/1415028953093111870
|
|
17
|
+
// Audio frames might have dependencies on previous and next frames so we need to decode a bit more
|
|
18
|
+
// and then discard it.
|
|
19
|
+
// The worst case seems to be FLAC files with a 65'535 sample window, which would be 1486.0ms at 44.1Khz.
|
|
20
|
+
// So let's set a threshold of 1.5 seconds.
|
|
21
|
+
const extraThreshold = 1.5;
|
|
22
|
+
// Matroska timestamps are not accurate unless we start from the beginning
|
|
23
|
+
// So for matroska, we need to decode all samples :(
|
|
24
|
+
// https://github.com/Vanilagy/mediabunny/issues/105
|
|
25
|
+
const sampleIterator = audio.sampleSink.samples(isMatroska ? 0 : Math.max(0, timeInSeconds - extraThreshold), timeInSeconds + durationInSeconds);
|
|
26
|
+
const samples = [];
|
|
27
|
+
for await (const sample of sampleIterator) {
|
|
28
|
+
const realTimestamp = actualMatroskaTimestamps.getRealTimestamp(sample.timestamp);
|
|
29
|
+
if (realTimestamp !== null && realTimestamp !== sample.timestamp) {
|
|
30
|
+
sample.setTimestamp(realTimestamp);
|
|
31
|
+
}
|
|
32
|
+
actualMatroskaTimestamps.observeTimestamp(sample.timestamp);
|
|
33
|
+
actualMatroskaTimestamps.observeTimestamp(sample.timestamp + sample.duration);
|
|
34
|
+
if (sample.timestamp + sample.duration - 0.0000000001 <= timeInSeconds) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (sample.timestamp >= timeInSeconds + durationInSeconds - 0.0000000001) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
samples.push(sample);
|
|
41
|
+
}
|
|
42
|
+
const audioDataArray = [];
|
|
43
|
+
for (let i = 0; i < samples.length; i++) {
|
|
44
|
+
const sample = samples[i];
|
|
45
|
+
// Less than 1 sample would be included - we did not need it after all!
|
|
46
|
+
if (Math.abs(sample.timestamp - (timeInSeconds + durationInSeconds)) *
|
|
47
|
+
sample.sampleRate <
|
|
48
|
+
1) {
|
|
49
|
+
sample.close();
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// Less than 1 sample would be included - we did not need it after all!
|
|
53
|
+
if (sample.timestamp + sample.duration <= timeInSeconds) {
|
|
54
|
+
sample.close();
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const isFirstSample = i === 0;
|
|
58
|
+
const isLastSample = i === samples.length - 1;
|
|
59
|
+
const audioDataRaw = sample.toAudioData();
|
|
60
|
+
// amount of samples to shave from start and end
|
|
61
|
+
let trimStartInSeconds = 0;
|
|
62
|
+
let trimEndInSeconds = 0;
|
|
63
|
+
// TODO: Apply playback rate
|
|
64
|
+
// TODO: Apply tone frequency
|
|
65
|
+
if (isFirstSample) {
|
|
66
|
+
trimStartInSeconds = timeInSeconds - sample.timestamp;
|
|
67
|
+
}
|
|
68
|
+
if (isLastSample) {
|
|
69
|
+
trimEndInSeconds =
|
|
70
|
+
// clamp to 0 in case the audio ends early
|
|
71
|
+
Math.max(0, sample.timestamp +
|
|
72
|
+
sample.duration -
|
|
73
|
+
(timeInSeconds + durationInSeconds));
|
|
74
|
+
}
|
|
75
|
+
const audioData = convertAudioData({
|
|
76
|
+
audioData: audioDataRaw,
|
|
77
|
+
newSampleRate: TARGET_SAMPLE_RATE,
|
|
78
|
+
trimStartInSeconds,
|
|
79
|
+
trimEndInSeconds,
|
|
80
|
+
targetNumberOfChannels: TARGET_NUMBER_OF_CHANNELS,
|
|
81
|
+
volume,
|
|
82
|
+
});
|
|
83
|
+
audioDataRaw.close();
|
|
84
|
+
if (audioData.numberOfFrames === 0) {
|
|
85
|
+
sample.close();
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
audioDataArray.push(audioData);
|
|
89
|
+
sample.close();
|
|
90
|
+
}
|
|
91
|
+
if (audioDataArray.length === 0) {
|
|
92
|
+
console.timeEnd('extractAudio');
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const combined = combineAudioDataAndClosePrevious(audioDataArray);
|
|
96
|
+
console.timeEnd('extractAudio');
|
|
97
|
+
return combined;
|
|
98
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PcmS16AudioData } from './convert-audiodata/convert-audiodata';
|
|
2
|
+
import type { LogLevel } from './log';
|
|
3
|
+
export declare const extractFrameViaBroadcastChannel: ({ src, timeInSeconds, logLevel, durationInSeconds, includeAudio, includeVideo, isClientSideRendering, volume, }: {
|
|
4
|
+
src: string;
|
|
5
|
+
timeInSeconds: number;
|
|
6
|
+
durationInSeconds: number;
|
|
7
|
+
logLevel: LogLevel;
|
|
8
|
+
includeAudio: boolean;
|
|
9
|
+
includeVideo: boolean;
|
|
10
|
+
isClientSideRendering: boolean;
|
|
11
|
+
volume: number;
|
|
12
|
+
}) => Promise<{
|
|
13
|
+
frame: ImageBitmap | VideoFrame | null;
|
|
14
|
+
audio: PcmS16AudioData | null;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { extractFrameAndAudio } from './extract-frame-and-audio';
|
|
2
|
+
// Doesn't exist in studio
|
|
3
|
+
if (window.remotion_broadcastChannel && window.remotion_isMainTab) {
|
|
4
|
+
window.remotion_broadcastChannel.addEventListener('message', async (event) => {
|
|
5
|
+
const data = event.data;
|
|
6
|
+
if (data.type === 'request') {
|
|
7
|
+
try {
|
|
8
|
+
const { frame, audio } = await extractFrameAndAudio({
|
|
9
|
+
src: data.src,
|
|
10
|
+
timeInSeconds: data.timeInSeconds,
|
|
11
|
+
logLevel: data.logLevel,
|
|
12
|
+
durationInSeconds: data.durationInSeconds,
|
|
13
|
+
includeAudio: data.includeAudio,
|
|
14
|
+
includeVideo: data.includeVideo,
|
|
15
|
+
volume: data.volume,
|
|
16
|
+
});
|
|
17
|
+
const videoFrame = frame;
|
|
18
|
+
const imageBitmap = videoFrame
|
|
19
|
+
? await createImageBitmap(videoFrame)
|
|
20
|
+
: null;
|
|
21
|
+
if (videoFrame) {
|
|
22
|
+
videoFrame.close();
|
|
23
|
+
}
|
|
24
|
+
const response = {
|
|
25
|
+
type: 'response-success',
|
|
26
|
+
id: data.id,
|
|
27
|
+
frame: imageBitmap,
|
|
28
|
+
audio,
|
|
29
|
+
};
|
|
30
|
+
window.remotion_broadcastChannel.postMessage(response);
|
|
31
|
+
videoFrame?.close();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const response = {
|
|
35
|
+
type: 'response-error',
|
|
36
|
+
id: data.id,
|
|
37
|
+
errorStack: error.stack ?? 'No stack trace',
|
|
38
|
+
};
|
|
39
|
+
window.remotion_broadcastChannel.postMessage(response);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
throw new Error('Invalid message: ' + JSON.stringify(data));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export const extractFrameViaBroadcastChannel = ({ src, timeInSeconds, logLevel, durationInSeconds, includeAudio, includeVideo, isClientSideRendering, volume, }) => {
|
|
48
|
+
if (isClientSideRendering || window.remotion_isMainTab) {
|
|
49
|
+
return extractFrameAndAudio({
|
|
50
|
+
logLevel,
|
|
51
|
+
src,
|
|
52
|
+
timeInSeconds,
|
|
53
|
+
durationInSeconds,
|
|
54
|
+
includeAudio,
|
|
55
|
+
includeVideo,
|
|
56
|
+
volume,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const requestId = crypto.randomUUID();
|
|
60
|
+
const resolvePromise = new Promise((resolve, reject) => {
|
|
61
|
+
const onMessage = (event) => {
|
|
62
|
+
const data = event.data;
|
|
63
|
+
if (!data) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (data.type === 'response-success' && data.id === requestId) {
|
|
67
|
+
resolve({
|
|
68
|
+
frame: data.frame ? data.frame : null,
|
|
69
|
+
audio: data.audio ? data.audio : null,
|
|
70
|
+
});
|
|
71
|
+
window.remotion_broadcastChannel.removeEventListener('message', onMessage);
|
|
72
|
+
}
|
|
73
|
+
else if (data.type === 'response-error' && data.id === requestId) {
|
|
74
|
+
reject(data.errorStack);
|
|
75
|
+
window.remotion_broadcastChannel.removeEventListener('message', onMessage);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
window.remotion_broadcastChannel.addEventListener('message', onMessage);
|
|
79
|
+
});
|
|
80
|
+
const request = {
|
|
81
|
+
type: 'request',
|
|
82
|
+
src,
|
|
83
|
+
timeInSeconds,
|
|
84
|
+
id: requestId,
|
|
85
|
+
logLevel,
|
|
86
|
+
durationInSeconds,
|
|
87
|
+
includeAudio,
|
|
88
|
+
includeVideo,
|
|
89
|
+
volume,
|
|
90
|
+
};
|
|
91
|
+
window.remotion_broadcastChannel.postMessage(request);
|
|
92
|
+
let timeoutId;
|
|
93
|
+
return Promise.race([
|
|
94
|
+
resolvePromise.then((res) => {
|
|
95
|
+
clearTimeout(timeoutId);
|
|
96
|
+
return res;
|
|
97
|
+
}),
|
|
98
|
+
new Promise((_, reject) => {
|
|
99
|
+
timeoutId = setTimeout(() => {
|
|
100
|
+
reject(new Error(`Timeout while extracting frame at time ${timeInSeconds}sec from ${src}`));
|
|
101
|
+
}, Math.max(3000, window.remotion_puppeteerTimeout - 5000));
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LogLevel } from './log';
|
|
2
|
+
import { type GetSink } from './video-extraction/get-frames-since-keyframe';
|
|
3
|
+
export declare const keyframeManager: {
|
|
4
|
+
requestKeyframeBank: ({ packetSink, timestamp, videoSampleSink, src, logLevel, }: {
|
|
5
|
+
timestamp: number;
|
|
6
|
+
packetSink: import("mediabunny").EncodedPacketSink;
|
|
7
|
+
videoSampleSink: import("mediabunny").VideoSampleSink;
|
|
8
|
+
src: string;
|
|
9
|
+
logLevel: LogLevel;
|
|
10
|
+
}) => Promise<import("./video-extraction/keyframe-bank").KeyframeBank>;
|
|
11
|
+
addKeyframeBank: ({ src, bank, startTimestampInSeconds, }: {
|
|
12
|
+
src: string;
|
|
13
|
+
bank: Promise<import("./video-extraction/keyframe-bank").KeyframeBank>;
|
|
14
|
+
startTimestampInSeconds: number;
|
|
15
|
+
}) => void;
|
|
16
|
+
getCacheStats: () => Promise<{
|
|
17
|
+
count: number;
|
|
18
|
+
totalSize: number;
|
|
19
|
+
}>;
|
|
20
|
+
clearAll: () => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
export declare const sinkPromises: Record<string, Promise<GetSink>>;
|
|
23
|
+
export declare const extractFrame: ({ src, timeInSeconds, logLevel, }: {
|
|
24
|
+
src: string;
|
|
25
|
+
timeInSeconds: number;
|
|
26
|
+
logLevel: LogLevel;
|
|
27
|
+
}) => Promise<import("mediabunny").VideoSample | null>;
|