@remotion/media 4.0.371 → 4.0.373
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 +1 -1
- package/dist/audio-extraction/extract-audio.js +3 -1
- package/dist/audio-iterator-manager.js +33 -21
- package/dist/esm/index.mjs +45 -29
- package/dist/video/video-for-preview.js +1 -1
- package/dist/video-extraction/keyframe-bank.js +20 -8
- package/dist/video-extraction/keyframe-manager.js +5 -2
- package/package.json +6 -6
|
@@ -13,7 +13,7 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
|
|
|
13
13
|
const [mediaPlayerReady, setMediaPlayerReady] = useState(false);
|
|
14
14
|
const [shouldFallbackToNativeAudio, setShouldFallbackToNativeAudio] = useState(false);
|
|
15
15
|
const [playing] = Timeline.usePlayingState();
|
|
16
|
-
const timelineContext = useContext(
|
|
16
|
+
const timelineContext = useContext(Internals.TimelineContext);
|
|
17
17
|
const globalPlaybackRate = timelineContext.playbackRate;
|
|
18
18
|
const sharedAudioContext = useContext(SharedAudioContext);
|
|
19
19
|
const buffer = useBufferState();
|
|
@@ -44,6 +44,7 @@ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
44
44
|
const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
|
|
45
45
|
const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
|
|
46
46
|
audioManager.logOpenFrames();
|
|
47
|
+
const trimStartToleranceInSeconds = 0.002;
|
|
47
48
|
const audioDataArray = [];
|
|
48
49
|
for (let i = 0; i < samples.length; i++) {
|
|
49
50
|
const sample = samples[i];
|
|
@@ -65,7 +66,8 @@ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
65
66
|
let trimEndInSeconds = 0;
|
|
66
67
|
if (isFirstSample) {
|
|
67
68
|
trimStartInSeconds = timeInSeconds - sample.timestamp;
|
|
68
|
-
if (trimStartInSeconds < 0 &&
|
|
69
|
+
if (trimStartInSeconds < 0 &&
|
|
70
|
+
trimStartInSeconds > -trimStartToleranceInSeconds) {
|
|
69
71
|
trimStartInSeconds = 0;
|
|
70
72
|
}
|
|
71
73
|
if (trimStartInSeconds < 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AudioBufferSink } from 'mediabunny';
|
|
1
|
+
import { AudioBufferSink, InputDisposedError } from 'mediabunny';
|
|
2
2
|
import { isAlreadyQueued, makeAudioIterator, } from './audio/audio-preview-iterator';
|
|
3
3
|
export const audioIteratorManager = ({ audioTrack, delayPlaybackHandleIfNotPremounting, sharedAudioContext, }) => {
|
|
4
4
|
let muted = false;
|
|
@@ -48,30 +48,42 @@ export const audioIteratorManager = ({ audioTrack, delayPlaybackHandleIfNotPremo
|
|
|
48
48
|
const iterator = makeAudioIterator(audioSink, startFromSecond);
|
|
49
49
|
audioIteratorsCreated++;
|
|
50
50
|
audioBufferIterator = iterator;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
try {
|
|
52
|
+
// Schedule up to 3 buffers ahead of the current time
|
|
53
|
+
for (let i = 0; i < 3; i++) {
|
|
54
|
+
const result = await iterator.getNext();
|
|
55
|
+
if (iterator.isDestroyed()) {
|
|
56
|
+
delayHandle.unblock();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (nonce.isStale()) {
|
|
60
|
+
delayHandle.unblock();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!result.value) {
|
|
64
|
+
// media ended
|
|
65
|
+
delayHandle.unblock();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
onAudioChunk({
|
|
69
|
+
getIsPlaying,
|
|
70
|
+
buffer: result.value,
|
|
71
|
+
playbackRate,
|
|
72
|
+
scheduleAudioNode,
|
|
73
|
+
});
|
|
61
74
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
if (e instanceof InputDisposedError) {
|
|
78
|
+
// iterator was disposed by a newer startAudioIterator call
|
|
79
|
+
// this is expected during rapid seeking
|
|
65
80
|
return;
|
|
66
81
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
scheduleAudioNode,
|
|
72
|
-
});
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
delayHandle.unblock();
|
|
73
86
|
}
|
|
74
|
-
delayHandle.unblock();
|
|
75
87
|
};
|
|
76
88
|
const pausePlayback = () => {
|
|
77
89
|
if (!audioBufferIterator) {
|
package/dist/esm/index.mjs
CHANGED
|
@@ -55,7 +55,7 @@ import { ALL_FORMATS, Input, UrlSource } from "mediabunny";
|
|
|
55
55
|
import { Internals as Internals3 } from "remotion";
|
|
56
56
|
|
|
57
57
|
// src/audio-iterator-manager.ts
|
|
58
|
-
import { AudioBufferSink } from "mediabunny";
|
|
58
|
+
import { AudioBufferSink, InputDisposedError } from "mediabunny";
|
|
59
59
|
|
|
60
60
|
// src/helpers/round-to-4-digits.ts
|
|
61
61
|
var roundTo4Digits = (timestamp) => {
|
|
@@ -337,28 +337,36 @@ var audioIteratorManager = ({
|
|
|
337
337
|
const iterator = makeAudioIterator(audioSink, startFromSecond);
|
|
338
338
|
audioIteratorsCreated++;
|
|
339
339
|
audioBufferIterator = iterator;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
340
|
+
try {
|
|
341
|
+
for (let i = 0;i < 3; i++) {
|
|
342
|
+
const result = await iterator.getNext();
|
|
343
|
+
if (iterator.isDestroyed()) {
|
|
344
|
+
delayHandle.unblock();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (nonce.isStale()) {
|
|
348
|
+
delayHandle.unblock();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (!result.value) {
|
|
352
|
+
delayHandle.unblock();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
onAudioChunk({
|
|
356
|
+
getIsPlaying,
|
|
357
|
+
buffer: result.value,
|
|
358
|
+
playbackRate,
|
|
359
|
+
scheduleAudioNode
|
|
360
|
+
});
|
|
349
361
|
}
|
|
350
|
-
|
|
351
|
-
|
|
362
|
+
} catch (e) {
|
|
363
|
+
if (e instanceof InputDisposedError) {
|
|
352
364
|
return;
|
|
353
365
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
playbackRate,
|
|
358
|
-
scheduleAudioNode
|
|
359
|
-
});
|
|
366
|
+
throw e;
|
|
367
|
+
} finally {
|
|
368
|
+
delayHandle.unblock();
|
|
360
369
|
}
|
|
361
|
-
delayHandle.unblock();
|
|
362
370
|
};
|
|
363
371
|
const pausePlayback = () => {
|
|
364
372
|
if (!audioBufferIterator) {
|
|
@@ -1354,7 +1362,7 @@ var AudioForPreviewAssertedShowing = ({
|
|
|
1354
1362
|
const [mediaPlayerReady, setMediaPlayerReady] = useState2(false);
|
|
1355
1363
|
const [shouldFallbackToNativeAudio, setShouldFallbackToNativeAudio] = useState2(false);
|
|
1356
1364
|
const [playing] = Timeline.usePlayingState();
|
|
1357
|
-
const timelineContext = useContext2(
|
|
1365
|
+
const timelineContext = useContext2(Internals6.TimelineContext);
|
|
1358
1366
|
const globalPlaybackRate = timelineContext.playbackRate;
|
|
1359
1367
|
const sharedAudioContext = useContext2(SharedAudioContext);
|
|
1360
1368
|
const buffer = useBufferState();
|
|
@@ -2262,19 +2270,26 @@ var makeKeyframeBank = ({
|
|
|
2262
2270
|
};
|
|
2263
2271
|
const getFrameFromTimestamp = async (timestampInSeconds) => {
|
|
2264
2272
|
lastUsed = Date.now();
|
|
2265
|
-
|
|
2266
|
-
|
|
2273
|
+
const maxClampToleranceInSeconds = 0.1;
|
|
2274
|
+
let adjustedTimestamp = timestampInSeconds;
|
|
2275
|
+
if (roundTo4Digits(timestampInSeconds) < roundTo4Digits(startTimestampInSeconds)) {
|
|
2276
|
+
const differenceInSeconds = startTimestampInSeconds - timestampInSeconds;
|
|
2277
|
+
if (differenceInSeconds <= maxClampToleranceInSeconds) {
|
|
2278
|
+
adjustedTimestamp = startTimestampInSeconds;
|
|
2279
|
+
} else {
|
|
2280
|
+
return Promise.reject(new Error(`Timestamp is before start timestamp (requested: ${timestampInSeconds}sec, start: ${startTimestampInSeconds}sec, difference: ${differenceInSeconds.toFixed(3)}sec exceeds tolerance of ${maxClampToleranceInSeconds}sec)`));
|
|
2281
|
+
}
|
|
2267
2282
|
}
|
|
2268
|
-
if (
|
|
2269
|
-
return Promise.reject(new Error(`Timestamp is after end timestamp (requested: ${timestampInSeconds}sec, end: ${endTimestampInSeconds})`));
|
|
2283
|
+
if (roundTo4Digits(adjustedTimestamp) > roundTo4Digits(endTimestampInSeconds)) {
|
|
2284
|
+
return Promise.reject(new Error(`Timestamp is after end timestamp (requested: ${timestampInSeconds}sec, end: ${endTimestampInSeconds}sec)`));
|
|
2270
2285
|
}
|
|
2271
|
-
await ensureEnoughFramesForTimestamp(
|
|
2286
|
+
await ensureEnoughFramesForTimestamp(adjustedTimestamp);
|
|
2272
2287
|
for (let i = frameTimestamps.length - 1;i >= 0; i--) {
|
|
2273
2288
|
const sample = frames[frameTimestamps[i]];
|
|
2274
2289
|
if (!sample) {
|
|
2275
2290
|
return null;
|
|
2276
2291
|
}
|
|
2277
|
-
if (roundTo4Digits(sample.timestamp) <= roundTo4Digits(
|
|
2292
|
+
if (roundTo4Digits(sample.timestamp) <= roundTo4Digits(adjustedTimestamp) || Math.abs(sample.timestamp - adjustedTimestamp) <= 0.001) {
|
|
2278
2293
|
return sample;
|
|
2279
2294
|
}
|
|
2280
2295
|
}
|
|
@@ -2580,7 +2595,7 @@ var makeKeyframeManager = () => {
|
|
|
2580
2595
|
}) => {
|
|
2581
2596
|
const startPacket = await packetSink.getKeyPacket(timestamp, {
|
|
2582
2597
|
verifyKeyPackets: true
|
|
2583
|
-
});
|
|
2598
|
+
}) ?? await packetSink.getFirstPacket({ verifyKeyPackets: true });
|
|
2584
2599
|
const hasAlpha = startPacket?.sideData.alpha;
|
|
2585
2600
|
if (hasAlpha && !canBrowserUseWebGl2()) {
|
|
2586
2601
|
return "has-alpha";
|
|
@@ -2882,6 +2897,7 @@ var extractAudioInternal = async ({
|
|
|
2882
2897
|
const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
|
|
2883
2898
|
const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
|
|
2884
2899
|
audioManager.logOpenFrames();
|
|
2900
|
+
const trimStartToleranceInSeconds = 0.002;
|
|
2885
2901
|
const audioDataArray = [];
|
|
2886
2902
|
for (let i = 0;i < samples.length; i++) {
|
|
2887
2903
|
const sample = samples[i];
|
|
@@ -2898,7 +2914,7 @@ var extractAudioInternal = async ({
|
|
|
2898
2914
|
let trimEndInSeconds = 0;
|
|
2899
2915
|
if (isFirstSample) {
|
|
2900
2916
|
trimStartInSeconds = timeInSeconds - sample.timestamp;
|
|
2901
|
-
if (trimStartInSeconds < 0 && trimStartInSeconds > -
|
|
2917
|
+
if (trimStartInSeconds < 0 && trimStartInSeconds > -trimStartToleranceInSeconds) {
|
|
2902
2918
|
trimStartInSeconds = 0;
|
|
2903
2919
|
}
|
|
2904
2920
|
if (trimStartInSeconds < 0) {
|
|
@@ -3575,7 +3591,7 @@ var VideoForPreviewAssertedShowing = ({
|
|
|
3575
3591
|
const [mediaPlayerReady, setMediaPlayerReady] = useState4(false);
|
|
3576
3592
|
const [shouldFallbackToNativeVideo, setShouldFallbackToNativeVideo] = useState4(false);
|
|
3577
3593
|
const [playing] = Timeline2.usePlayingState();
|
|
3578
|
-
const timelineContext = useContext4(
|
|
3594
|
+
const timelineContext = useContext4(Internals15.TimelineContext);
|
|
3579
3595
|
const globalPlaybackRate = timelineContext.playbackRate;
|
|
3580
3596
|
const sharedAudioContext = useContext4(SharedAudioContext2);
|
|
3581
3597
|
const buffer = useBufferState2();
|
|
@@ -15,7 +15,7 @@ const VideoForPreviewAssertedShowing = ({ src: unpreloadedSrc, style, playbackRa
|
|
|
15
15
|
const [mediaPlayerReady, setMediaPlayerReady] = useState(false);
|
|
16
16
|
const [shouldFallbackToNativeVideo, setShouldFallbackToNativeVideo] = useState(false);
|
|
17
17
|
const [playing] = Timeline.usePlayingState();
|
|
18
|
-
const timelineContext = useContext(
|
|
18
|
+
const timelineContext = useContext(Internals.TimelineContext);
|
|
19
19
|
const globalPlaybackRate = timelineContext.playbackRate;
|
|
20
20
|
const sharedAudioContext = useContext(SharedAudioContext);
|
|
21
21
|
const buffer = useBufferState();
|
|
@@ -68,23 +68,35 @@ export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSecond
|
|
|
68
68
|
};
|
|
69
69
|
const getFrameFromTimestamp = async (timestampInSeconds) => {
|
|
70
70
|
lastUsed = Date.now();
|
|
71
|
-
if
|
|
72
|
-
|
|
71
|
+
// Videos may start slightly after timestamp 0 due to encoding, but if the requested timestamp is too far before the bank start, something is likely wrong.
|
|
72
|
+
const maxClampToleranceInSeconds = 0.1;
|
|
73
|
+
// If the requested timestamp is before the start of this bank, clamp it to the start if within tolerance. This handles videos that don't start at timestamp 0.
|
|
74
|
+
// For example, requesting frame at 0sec when video starts at 0.04sec should return the frame at 0.04sec.
|
|
75
|
+
// Test case: https://github.com/remotion-dev/remotion/issues/5915
|
|
76
|
+
let adjustedTimestamp = timestampInSeconds;
|
|
77
|
+
if (roundTo4Digits(timestampInSeconds) <
|
|
78
|
+
roundTo4Digits(startTimestampInSeconds)) {
|
|
79
|
+
const differenceInSeconds = startTimestampInSeconds - timestampInSeconds;
|
|
80
|
+
if (differenceInSeconds <= maxClampToleranceInSeconds) {
|
|
81
|
+
adjustedTimestamp = startTimestampInSeconds;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
return Promise.reject(new Error(`Timestamp is before start timestamp (requested: ${timestampInSeconds}sec, start: ${startTimestampInSeconds}sec, difference: ${differenceInSeconds.toFixed(3)}sec exceeds tolerance of ${maxClampToleranceInSeconds}sec)`));
|
|
85
|
+
}
|
|
73
86
|
}
|
|
74
|
-
if (
|
|
75
|
-
return Promise.reject(new Error(`Timestamp is after end timestamp (requested: ${timestampInSeconds}sec, end: ${endTimestampInSeconds})`));
|
|
87
|
+
if (roundTo4Digits(adjustedTimestamp) > roundTo4Digits(endTimestampInSeconds)) {
|
|
88
|
+
return Promise.reject(new Error(`Timestamp is after end timestamp (requested: ${timestampInSeconds}sec, end: ${endTimestampInSeconds}sec)`));
|
|
76
89
|
}
|
|
77
|
-
await ensureEnoughFramesForTimestamp(
|
|
90
|
+
await ensureEnoughFramesForTimestamp(adjustedTimestamp);
|
|
78
91
|
for (let i = frameTimestamps.length - 1; i >= 0; i--) {
|
|
79
92
|
const sample = frames[frameTimestamps[i]];
|
|
80
93
|
if (!sample) {
|
|
81
94
|
return null;
|
|
82
95
|
}
|
|
83
|
-
if (roundTo4Digits(sample.timestamp) <=
|
|
84
|
-
roundTo4Digits(timestampInSeconds) ||
|
|
96
|
+
if (roundTo4Digits(sample.timestamp) <= roundTo4Digits(adjustedTimestamp) ||
|
|
85
97
|
// Match 0.3333333333 to 0.33355555
|
|
86
98
|
// this does not satisfy the previous condition, since one rounds up and one rounds down
|
|
87
|
-
Math.abs(sample.timestamp -
|
|
99
|
+
Math.abs(sample.timestamp - adjustedTimestamp) <= 0.001) {
|
|
88
100
|
return sample;
|
|
89
101
|
}
|
|
90
102
|
}
|
|
@@ -111,9 +111,12 @@ export const makeKeyframeManager = () => {
|
|
|
111
111
|
await logCacheStats(logLevel);
|
|
112
112
|
};
|
|
113
113
|
const getKeyframeBankOrRefetch = async ({ packetSink, timestamp, videoSampleSink, src, logLevel, }) => {
|
|
114
|
-
|
|
114
|
+
// Try to get the keypacket at the requested timestamp.
|
|
115
|
+
// If it returns null (timestamp is before the first keypacket), fall back to the first packet.
|
|
116
|
+
// This matches mediabunny's internal behavior and handles videos that don't start at timestamp 0.
|
|
117
|
+
const startPacket = (await packetSink.getKeyPacket(timestamp, {
|
|
115
118
|
verifyKeyPackets: true,
|
|
116
|
-
});
|
|
119
|
+
})) ?? (await packetSink.getFirstPacket({ verifyKeyPackets: true }));
|
|
117
120
|
const hasAlpha = startPacket?.sideData.alpha;
|
|
118
121
|
if (hasAlpha && !canBrowserUseWebGl2()) {
|
|
119
122
|
return 'has-alpha';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remotion/media",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.373",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"module": "dist/esm/index.mjs",
|
|
@@ -22,20 +22,20 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"mediabunny": "1.24.3",
|
|
25
|
-
"remotion": "4.0.
|
|
26
|
-
"webdriverio": "9.19.2"
|
|
25
|
+
"remotion": "4.0.373"
|
|
27
26
|
},
|
|
28
27
|
"peerDependencies": {
|
|
29
28
|
"react": ">=16.8.0",
|
|
30
29
|
"react-dom": ">=16.8.0"
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
33
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
34
|
-
"@vitest/browser": "
|
|
32
|
+
"@remotion/eslint-config-internal": "4.0.373",
|
|
33
|
+
"@vitest/browser-webdriverio": "4.0.7",
|
|
35
34
|
"eslint": "9.19.0",
|
|
36
35
|
"react": "19.0.0",
|
|
37
36
|
"react-dom": "19.0.0",
|
|
38
|
-
"vitest": "
|
|
37
|
+
"vitest": "4.0.7",
|
|
38
|
+
"webdriverio": "9.19.2"
|
|
39
39
|
},
|
|
40
40
|
"keywords": [],
|
|
41
41
|
"publishConfig": {
|