@remotion/media 4.0.401 → 4.0.403
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 -4
- package/dist/audio/audio-for-rendering.js +8 -6
- package/dist/audio-extraction/audio-iterator.js +3 -3
- package/dist/audio-extraction/audio-manager.js +9 -1
- package/dist/caches.d.ts +6 -7
- package/dist/caches.js +2 -2
- package/dist/debug-overlay/preview-overlay.d.ts +2 -2
- package/dist/debug-overlay/preview-overlay.js +5 -3
- package/dist/esm/index.mjs +376 -355
- package/dist/extract-frame-and-audio.js +14 -29
- package/dist/media-player.d.ts +3 -3
- package/dist/media-player.js +38 -24
- package/dist/video/video-for-preview.js +0 -2
- package/dist/video/video.js +2 -4
- package/dist/video-extraction/extract-frame.d.ts +1 -1
- package/dist/video-extraction/extract-frame.js +9 -10
- package/dist/video-extraction/get-frames-since-keyframe.d.ts +2 -12
- package/dist/video-extraction/get-frames-since-keyframe.js +14 -17
- package/dist/video-extraction/keyframe-bank.d.ts +12 -10
- package/dist/video-extraction/keyframe-bank.js +93 -52
- package/dist/video-extraction/keyframe-manager.d.ts +6 -7
- package/dist/video-extraction/keyframe-manager.js +72 -77
- package/package.json +3 -3
package/dist/esm/index.mjs
CHANGED
|
@@ -605,12 +605,12 @@ var drawPreviewOverlay = ({
|
|
|
605
605
|
`Audio iterators created: ${audioIteratorManager2?.getAudioIteratorsCreated()}`,
|
|
606
606
|
`Frames rendered: ${videoIteratorManager?.getFramesRendered()}`,
|
|
607
607
|
`Audio context state: ${audioContextState}`,
|
|
608
|
-
`Audio time: ${(audioTime - audioSyncAnchor).toFixed(3)}s`
|
|
609
|
-
];
|
|
608
|
+
audioTime ? `Audio time: ${(audioTime - audioSyncAnchor).toFixed(3)}s` : null
|
|
609
|
+
].filter(Boolean);
|
|
610
610
|
if (audioIteratorManager2) {
|
|
611
611
|
const queuedPeriod = audioIteratorManager2.getAudioBufferIterator()?.getQueuedPeriod();
|
|
612
612
|
const numberOfChunksAfterResuming = audioIteratorManager2?.getAudioBufferIterator()?.getNumberOfChunksAfterResuming();
|
|
613
|
-
if (queuedPeriod) {
|
|
613
|
+
if (queuedPeriod && audioTime) {
|
|
614
614
|
lines.push(`Audio queued until: ${(queuedPeriod.until - (audioTime - audioSyncAnchor)).toFixed(3)}s`);
|
|
615
615
|
} else if (numberOfChunksAfterResuming) {
|
|
616
616
|
lines.push(`Audio chunks for after resuming: ${numberOfChunksAfterResuming}`);
|
|
@@ -946,7 +946,7 @@ class MediaPlayer {
|
|
|
946
946
|
}) {
|
|
947
947
|
this.canvas = canvas ?? null;
|
|
948
948
|
this.src = src;
|
|
949
|
-
this.logLevel = logLevel
|
|
949
|
+
this.logLevel = logLevel;
|
|
950
950
|
this.sharedAudioContext = sharedAudioContext;
|
|
951
951
|
this.playbackRate = playbackRate;
|
|
952
952
|
this.globalPlaybackRate = globalPlaybackRate;
|
|
@@ -1070,7 +1070,7 @@ class MediaPlayer {
|
|
|
1070
1070
|
throw new Error(`should have asserted that the time is not null`);
|
|
1071
1071
|
}
|
|
1072
1072
|
this.setPlaybackTime(startTime, this.playbackRate * this.globalPlaybackRate);
|
|
1073
|
-
if (audioTrack) {
|
|
1073
|
+
if (audioTrack && this.sharedAudioContext) {
|
|
1074
1074
|
this.audioIteratorManager = audioIteratorManager({
|
|
1075
1075
|
audioTrack,
|
|
1076
1076
|
delayPlaybackHandleIfNotPremounting: this.delayPlaybackHandleIfNotPremounting,
|
|
@@ -1136,16 +1136,13 @@ class MediaPlayer {
|
|
|
1136
1136
|
if (nonce.isStale()) {
|
|
1137
1137
|
return;
|
|
1138
1138
|
}
|
|
1139
|
-
const
|
|
1140
|
-
if (currentPlaybackTime === newTime) {
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1139
|
+
const shouldSeekAudio = this.audioIteratorManager && this.sharedAudioContext && this.getAudioPlaybackTime() !== newTime;
|
|
1143
1140
|
await Promise.all([
|
|
1144
1141
|
this.videoIteratorManager?.seek({
|
|
1145
1142
|
newTime,
|
|
1146
1143
|
nonce
|
|
1147
1144
|
}),
|
|
1148
|
-
this.audioIteratorManager?.seek({
|
|
1145
|
+
shouldSeekAudio ? this.audioIteratorManager?.seek({
|
|
1149
1146
|
newTime,
|
|
1150
1147
|
nonce,
|
|
1151
1148
|
fps: this.fps,
|
|
@@ -1153,7 +1150,7 @@ class MediaPlayer {
|
|
|
1153
1150
|
getIsPlaying: () => this.playing,
|
|
1154
1151
|
scheduleAudioNode: this.scheduleAudioNode,
|
|
1155
1152
|
bufferState: this.bufferState
|
|
1156
|
-
})
|
|
1153
|
+
}) : null
|
|
1157
1154
|
]);
|
|
1158
1155
|
}
|
|
1159
1156
|
async play(time) {
|
|
@@ -1182,7 +1179,7 @@ class MediaPlayer {
|
|
|
1182
1179
|
scheduleAudioNode: this.scheduleAudioNode
|
|
1183
1180
|
});
|
|
1184
1181
|
}
|
|
1185
|
-
if (this.sharedAudioContext.state === "suspended") {
|
|
1182
|
+
if (this.sharedAudioContext && this.sharedAudioContext.state === "suspended") {
|
|
1186
1183
|
await this.sharedAudioContext.resume();
|
|
1187
1184
|
}
|
|
1188
1185
|
this.drawDebugOverlay();
|
|
@@ -1247,11 +1244,14 @@ class MediaPlayer {
|
|
|
1247
1244
|
setDebugOverlay(debugOverlay) {
|
|
1248
1245
|
this.debugOverlay = debugOverlay;
|
|
1249
1246
|
}
|
|
1250
|
-
|
|
1247
|
+
updateAudioTimeAfterPlaybackRateChange() {
|
|
1251
1248
|
if (!this.audioIteratorManager) {
|
|
1252
1249
|
return;
|
|
1253
1250
|
}
|
|
1254
|
-
|
|
1251
|
+
if (!this.sharedAudioContext) {
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
this.setPlaybackTime(this.getAudioPlaybackTime(), this.playbackRate * this.globalPlaybackRate);
|
|
1255
1255
|
const iterator = this.audioIteratorManager.getAudioBufferIterator();
|
|
1256
1256
|
if (!iterator) {
|
|
1257
1257
|
return;
|
|
@@ -1266,11 +1266,11 @@ class MediaPlayer {
|
|
|
1266
1266
|
}
|
|
1267
1267
|
setPlaybackRate(rate) {
|
|
1268
1268
|
this.playbackRate = rate;
|
|
1269
|
-
this.
|
|
1269
|
+
this.updateAudioTimeAfterPlaybackRateChange();
|
|
1270
1270
|
}
|
|
1271
1271
|
setGlobalPlaybackRate(rate) {
|
|
1272
1272
|
this.globalPlaybackRate = rate;
|
|
1273
|
-
this.
|
|
1273
|
+
this.updateAudioTimeAfterPlaybackRateChange();
|
|
1274
1274
|
}
|
|
1275
1275
|
setFps(fps) {
|
|
1276
1276
|
this.fps = fps;
|
|
@@ -1296,16 +1296,22 @@ class MediaPlayer {
|
|
|
1296
1296
|
this.input.dispose();
|
|
1297
1297
|
}
|
|
1298
1298
|
scheduleAudioNode = (node, mediaTimestamp) => {
|
|
1299
|
-
const currentTime = this.
|
|
1299
|
+
const currentTime = this.getAudioPlaybackTime();
|
|
1300
1300
|
const delayWithoutPlaybackRate = mediaTimestamp - currentTime;
|
|
1301
1301
|
const delay = delayWithoutPlaybackRate / (this.playbackRate * this.globalPlaybackRate);
|
|
1302
|
+
if (!this.sharedAudioContext) {
|
|
1303
|
+
throw new Error("Shared audio context not found");
|
|
1304
|
+
}
|
|
1302
1305
|
if (delay >= 0) {
|
|
1303
1306
|
node.start(this.sharedAudioContext.currentTime + delay);
|
|
1304
1307
|
} else {
|
|
1305
1308
|
node.start(this.sharedAudioContext.currentTime, -delay);
|
|
1306
1309
|
}
|
|
1307
1310
|
};
|
|
1308
|
-
|
|
1311
|
+
getAudioPlaybackTime() {
|
|
1312
|
+
if (!this.sharedAudioContext) {
|
|
1313
|
+
throw new Error("Shared audio context not found");
|
|
1314
|
+
}
|
|
1309
1315
|
return calculatePlaybackTime({
|
|
1310
1316
|
audioSyncAnchor: this.audioSyncAnchor,
|
|
1311
1317
|
currentTime: this.sharedAudioContext.currentTime,
|
|
@@ -1313,6 +1319,9 @@ class MediaPlayer {
|
|
|
1313
1319
|
});
|
|
1314
1320
|
}
|
|
1315
1321
|
setPlaybackTime(time, playbackRate) {
|
|
1322
|
+
if (!this.sharedAudioContext) {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1316
1325
|
this.audioSyncAnchor = this.sharedAudioContext.currentTime - time / playbackRate;
|
|
1317
1326
|
}
|
|
1318
1327
|
setVideoFrameCallback(callback) {
|
|
@@ -1324,8 +1333,8 @@ class MediaPlayer {
|
|
|
1324
1333
|
if (this.context && this.canvas) {
|
|
1325
1334
|
drawPreviewOverlay({
|
|
1326
1335
|
context: this.context,
|
|
1327
|
-
audioTime: this.sharedAudioContext
|
|
1328
|
-
audioContextState: this.sharedAudioContext
|
|
1336
|
+
audioTime: this.sharedAudioContext?.currentTime ?? null,
|
|
1337
|
+
audioContextState: this.sharedAudioContext?.state ?? null,
|
|
1329
1338
|
audioSyncAnchor: this.audioSyncAnchor,
|
|
1330
1339
|
audioIteratorManager: this.audioIteratorManager,
|
|
1331
1340
|
playing: this.playing,
|
|
@@ -1821,6 +1830,7 @@ var AudioForPreview = ({
|
|
|
1821
1830
|
fallbackHtml5AudioProps
|
|
1822
1831
|
}) => {
|
|
1823
1832
|
const preloadedSrc = usePreload(src);
|
|
1833
|
+
const defaultLogLevel = Internals6.useLogLevel();
|
|
1824
1834
|
const frame = useCurrentFrame2();
|
|
1825
1835
|
const videoConfig = useVideoConfig2();
|
|
1826
1836
|
const currentTime = frame / videoConfig.fps;
|
|
@@ -1852,7 +1862,7 @@ var AudioForPreview = ({
|
|
|
1852
1862
|
audioStreamIndex: audioStreamIndex ?? 0,
|
|
1853
1863
|
src: preloadedSrc,
|
|
1854
1864
|
playbackRate: playbackRate ?? 1,
|
|
1855
|
-
logLevel: logLevel ??
|
|
1865
|
+
logLevel: logLevel ?? defaultLogLevel,
|
|
1856
1866
|
muted: muted ?? false,
|
|
1857
1867
|
volume: volume ?? 1,
|
|
1858
1868
|
loopVolumeCurveBehavior: loopVolumeCurveBehavior ?? "repeat",
|
|
@@ -2002,7 +2012,7 @@ var makeAudioIterator2 = ({
|
|
|
2002
2012
|
const getSamples = async (timestamp, durationInSeconds) => {
|
|
2003
2013
|
lastUsed = Date.now();
|
|
2004
2014
|
if (fullDuration !== null && timestamp > fullDuration) {
|
|
2005
|
-
cache.clearBeforeThreshold(fullDuration -
|
|
2015
|
+
cache.clearBeforeThreshold(fullDuration - SAFE_WINDOW_OF_MONOTONICITY);
|
|
2006
2016
|
return [];
|
|
2007
2017
|
}
|
|
2008
2018
|
const samples = cache.getSamples(timestamp, durationInSeconds);
|
|
@@ -2015,7 +2025,7 @@ var makeAudioIterator2 = ({
|
|
|
2015
2025
|
while (true) {
|
|
2016
2026
|
const sample = await getNextSample();
|
|
2017
2027
|
const deleteBefore = fullDuration === null ? timestamp : Math.min(timestamp, fullDuration);
|
|
2018
|
-
cache.clearBeforeThreshold(deleteBefore -
|
|
2028
|
+
cache.clearBeforeThreshold(deleteBefore - SAFE_WINDOW_OF_MONOTONICITY);
|
|
2019
2029
|
if (sample === null) {
|
|
2020
2030
|
break;
|
|
2021
2031
|
}
|
|
@@ -2145,8 +2155,14 @@ var makeAudioManager = () => {
|
|
|
2145
2155
|
logLevel,
|
|
2146
2156
|
maxCacheSize
|
|
2147
2157
|
}) => {
|
|
2148
|
-
|
|
2158
|
+
let attempts = 0;
|
|
2159
|
+
const maxAttempts = 3;
|
|
2160
|
+
while ((await getTotalCacheStats()).totalSize > maxCacheSize && attempts < maxAttempts) {
|
|
2149
2161
|
deleteOldestIterator();
|
|
2162
|
+
attempts++;
|
|
2163
|
+
}
|
|
2164
|
+
if ((await getTotalCacheStats()).totalSize > maxCacheSize && attempts >= maxAttempts) {
|
|
2165
|
+
Internals8.Log.warn({ logLevel, tag: "@remotion/media" }, `Audio cache: Exceeded max cache size after ${maxAttempts} attempts. Still ${(await getTotalCacheStats()).totalSize} bytes used, target was ${maxCacheSize} bytes.`);
|
|
2150
2166
|
}
|
|
2151
2167
|
for (const iterator of iterators) {
|
|
2152
2168
|
if (iterator.src === src && await iterator.waitForCompletion() && iterator.canSatisfyRequestedTime(timeInSeconds)) {
|
|
@@ -2217,21 +2233,6 @@ var makeAudioManager = () => {
|
|
|
2217
2233
|
// src/video-extraction/keyframe-manager.ts
|
|
2218
2234
|
import { Internals as Internals10 } from "remotion";
|
|
2219
2235
|
|
|
2220
|
-
// src/browser-can-use-webgl2.ts
|
|
2221
|
-
var browserCanUseWebGl2 = null;
|
|
2222
|
-
var browserCanUseWebGl2Uncached = () => {
|
|
2223
|
-
const canvas = new OffscreenCanvas(1, 1);
|
|
2224
|
-
const context = canvas.getContext("webgl2");
|
|
2225
|
-
return context !== null;
|
|
2226
|
-
};
|
|
2227
|
-
var canBrowserUseWebGl2 = () => {
|
|
2228
|
-
if (browserCanUseWebGl2 !== null) {
|
|
2229
|
-
return browserCanUseWebGl2;
|
|
2230
|
-
}
|
|
2231
|
-
browserCanUseWebGl2 = browserCanUseWebGl2Uncached();
|
|
2232
|
-
return browserCanUseWebGl2;
|
|
2233
|
-
};
|
|
2234
|
-
|
|
2235
2236
|
// src/render-timestamp-range.ts
|
|
2236
2237
|
var renderTimestampRange = (timestamps) => {
|
|
2237
2238
|
if (timestamps.length === 0) {
|
|
@@ -2243,18 +2244,6 @@ var renderTimestampRange = (timestamps) => {
|
|
|
2243
2244
|
return `${timestamps[0].toFixed(3)}...${timestamps[timestamps.length - 1].toFixed(3)}`;
|
|
2244
2245
|
};
|
|
2245
2246
|
|
|
2246
|
-
// src/video-extraction/get-frames-since-keyframe.ts
|
|
2247
|
-
import {
|
|
2248
|
-
ALL_FORMATS as ALL_FORMATS2,
|
|
2249
|
-
AudioSampleSink,
|
|
2250
|
-
EncodedPacketSink,
|
|
2251
|
-
Input as Input2,
|
|
2252
|
-
MATROSKA,
|
|
2253
|
-
UrlSource as UrlSource2,
|
|
2254
|
-
VideoSampleSink,
|
|
2255
|
-
WEBM
|
|
2256
|
-
} from "mediabunny";
|
|
2257
|
-
|
|
2258
2247
|
// src/video-extraction/keyframe-bank.ts
|
|
2259
2248
|
import { Internals as Internals9 } from "remotion";
|
|
2260
2249
|
|
|
@@ -2267,36 +2256,42 @@ var getAllocationSize = (sample) => {
|
|
|
2267
2256
|
};
|
|
2268
2257
|
|
|
2269
2258
|
// src/video-extraction/keyframe-bank.ts
|
|
2270
|
-
var
|
|
2271
|
-
|
|
2272
|
-
endTimestampInSeconds,
|
|
2273
|
-
sampleIterator,
|
|
2259
|
+
var BIGGEST_ALLOWED_JUMP_FORWARD_SECONDS = 3;
|
|
2260
|
+
var makeKeyframeBank = async ({
|
|
2274
2261
|
logLevel: parentLogLevel,
|
|
2275
|
-
src
|
|
2262
|
+
src,
|
|
2263
|
+
videoSampleSink,
|
|
2264
|
+
requestedTimestamp
|
|
2276
2265
|
}) => {
|
|
2277
|
-
|
|
2266
|
+
const sampleIterator = videoSampleSink.samples(roundTo4Digits(requestedTimestamp));
|
|
2278
2267
|
const frames = {};
|
|
2279
2268
|
const frameTimestamps = [];
|
|
2269
|
+
let hasReachedEndOfVideo = false;
|
|
2280
2270
|
let lastUsed = Date.now();
|
|
2281
2271
|
let allocationSize = 0;
|
|
2272
|
+
const deleteFrameAtTimestamp = (timestamp) => {
|
|
2273
|
+
allocationSize -= getAllocationSize(frames[timestamp]);
|
|
2274
|
+
frameTimestamps.splice(frameTimestamps.indexOf(timestamp), 1);
|
|
2275
|
+
frames[timestamp].close();
|
|
2276
|
+
delete frames[timestamp];
|
|
2277
|
+
};
|
|
2282
2278
|
const deleteFramesBeforeTimestamp = ({
|
|
2283
2279
|
logLevel,
|
|
2284
2280
|
timestampInSeconds
|
|
2285
2281
|
}) => {
|
|
2286
2282
|
const deletedTimestamps = [];
|
|
2287
2283
|
for (const frameTimestamp of frameTimestamps.slice()) {
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2284
|
+
if (hasReachedEndOfVideo) {
|
|
2285
|
+
const isLast = frameTimestamp === frameTimestamps[frameTimestamps.length - 1];
|
|
2286
|
+
if (isLast) {
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2291
2289
|
}
|
|
2292
2290
|
if (frameTimestamp < timestampInSeconds) {
|
|
2293
2291
|
if (!frames[frameTimestamp]) {
|
|
2294
2292
|
continue;
|
|
2295
2293
|
}
|
|
2296
|
-
|
|
2297
|
-
frameTimestamps.splice(frameTimestamps.indexOf(frameTimestamp), 1);
|
|
2298
|
-
frames[frameTimestamp].close();
|
|
2299
|
-
delete frames[frameTimestamp];
|
|
2294
|
+
deleteFrameAtTimestamp(frameTimestamp);
|
|
2300
2295
|
deletedTimestamps.push(frameTimestamp);
|
|
2301
2296
|
}
|
|
2302
2297
|
}
|
|
@@ -2313,32 +2308,31 @@ var makeKeyframeBank = ({
|
|
|
2313
2308
|
if (!lastFrame) {
|
|
2314
2309
|
return true;
|
|
2315
2310
|
}
|
|
2316
|
-
return roundTo4Digits(lastFrame.timestamp + lastFrame.duration) > roundTo4Digits(timestamp) +
|
|
2311
|
+
return roundTo4Digits(lastFrame.timestamp + lastFrame.duration) > roundTo4Digits(timestamp) + SAFE_WINDOW_OF_MONOTONICITY;
|
|
2317
2312
|
};
|
|
2318
|
-
const addFrame = (frame) => {
|
|
2313
|
+
const addFrame = (frame, logLevel) => {
|
|
2319
2314
|
if (frames[frame.timestamp]) {
|
|
2320
|
-
|
|
2321
|
-
frameTimestamps.splice(frameTimestamps.indexOf(frame.timestamp), 1);
|
|
2322
|
-
frames[frame.timestamp].close();
|
|
2323
|
-
delete frames[frame.timestamp];
|
|
2315
|
+
deleteFrameAtTimestamp(frame.timestamp);
|
|
2324
2316
|
}
|
|
2325
2317
|
frames[frame.timestamp] = frame;
|
|
2326
2318
|
frameTimestamps.push(frame.timestamp);
|
|
2327
2319
|
allocationSize += getAllocationSize(frame);
|
|
2328
2320
|
lastUsed = Date.now();
|
|
2321
|
+
Internals9.Log.trace({ logLevel, tag: "@remotion/media" }, `Added frame at ${frame.timestamp}sec to bank`);
|
|
2329
2322
|
};
|
|
2330
|
-
const ensureEnoughFramesForTimestamp = async (timestampInSeconds) => {
|
|
2323
|
+
const ensureEnoughFramesForTimestamp = async (timestampInSeconds, logLevel) => {
|
|
2331
2324
|
while (!hasDecodedEnoughForTimestamp(timestampInSeconds)) {
|
|
2332
2325
|
const sample = await sampleIterator.next();
|
|
2333
2326
|
if (sample.value) {
|
|
2334
|
-
addFrame(sample.value);
|
|
2327
|
+
addFrame(sample.value, logLevel);
|
|
2335
2328
|
}
|
|
2336
2329
|
if (sample.done) {
|
|
2330
|
+
hasReachedEndOfVideo = true;
|
|
2337
2331
|
break;
|
|
2338
2332
|
}
|
|
2339
2333
|
deleteFramesBeforeTimestamp({
|
|
2340
2334
|
logLevel: parentLogLevel,
|
|
2341
|
-
timestampInSeconds: timestampInSeconds -
|
|
2335
|
+
timestampInSeconds: timestampInSeconds - SAFE_WINDOW_OF_MONOTONICITY
|
|
2342
2336
|
});
|
|
2343
2337
|
}
|
|
2344
2338
|
lastUsed = Date.now();
|
|
@@ -2346,13 +2340,10 @@ var makeKeyframeBank = ({
|
|
|
2346
2340
|
const getFrameFromTimestamp = async (timestampInSeconds) => {
|
|
2347
2341
|
lastUsed = Date.now();
|
|
2348
2342
|
let adjustedTimestamp = timestampInSeconds;
|
|
2349
|
-
if (roundTo4Digits(
|
|
2350
|
-
adjustedTimestamp =
|
|
2343
|
+
if (hasReachedEndOfVideo && roundTo4Digits(adjustedTimestamp) > roundTo4Digits(frameTimestamps[frameTimestamps.length - 1])) {
|
|
2344
|
+
adjustedTimestamp = frameTimestamps[frameTimestamps.length - 1];
|
|
2351
2345
|
}
|
|
2352
|
-
|
|
2353
|
-
adjustedTimestamp = endTimestampInSeconds;
|
|
2354
|
-
}
|
|
2355
|
-
await ensureEnoughFramesForTimestamp(adjustedTimestamp);
|
|
2346
|
+
await ensureEnoughFramesForTimestamp(adjustedTimestamp, parentLogLevel);
|
|
2356
2347
|
for (let i = frameTimestamps.length - 1;i >= 0; i--) {
|
|
2357
2348
|
const sample = frames[frameTimestamps[i]];
|
|
2358
2349
|
if (!sample) {
|
|
@@ -2362,32 +2353,11 @@ var makeKeyframeBank = ({
|
|
|
2362
2353
|
return sample;
|
|
2363
2354
|
}
|
|
2364
2355
|
}
|
|
2365
|
-
return null;
|
|
2356
|
+
return frames[frameTimestamps[0]] ?? null;
|
|
2366
2357
|
};
|
|
2367
2358
|
const hasTimestampInSecond = async (timestamp) => {
|
|
2368
2359
|
return await getFrameFromTimestamp(timestamp) !== null;
|
|
2369
2360
|
};
|
|
2370
|
-
const prepareForDeletion = (logLevel) => {
|
|
2371
|
-
Internals9.Log.verbose({ logLevel, tag: "@remotion/media" }, `Preparing for deletion of keyframe bank from ${startTimestampInSeconds}sec to ${endTimestampInSeconds}sec`);
|
|
2372
|
-
sampleIterator.return().then((result) => {
|
|
2373
|
-
if (result.value) {
|
|
2374
|
-
result.value.close();
|
|
2375
|
-
}
|
|
2376
|
-
return null;
|
|
2377
|
-
});
|
|
2378
|
-
let framesDeleted = 0;
|
|
2379
|
-
for (const frameTimestamp of frameTimestamps) {
|
|
2380
|
-
if (!frames[frameTimestamp]) {
|
|
2381
|
-
continue;
|
|
2382
|
-
}
|
|
2383
|
-
allocationSize -= getAllocationSize(frames[frameTimestamp]);
|
|
2384
|
-
frames[frameTimestamp].close();
|
|
2385
|
-
delete frames[frameTimestamp];
|
|
2386
|
-
framesDeleted++;
|
|
2387
|
-
}
|
|
2388
|
-
frameTimestamps.length = 0;
|
|
2389
|
-
return { framesDeleted };
|
|
2390
|
-
};
|
|
2391
2361
|
const getOpenFrameCount = () => {
|
|
2392
2362
|
return {
|
|
2393
2363
|
size: allocationSize,
|
|
@@ -2398,173 +2368,91 @@ var makeKeyframeBank = ({
|
|
|
2398
2368
|
return lastUsed;
|
|
2399
2369
|
};
|
|
2400
2370
|
let queue = Promise.resolve(undefined);
|
|
2401
|
-
const
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
getFrameFromTimestamp: (timestamp) => {
|
|
2405
|
-
queue = queue.then(() => getFrameFromTimestamp(timestamp));
|
|
2406
|
-
return queue;
|
|
2407
|
-
},
|
|
2408
|
-
prepareForDeletion,
|
|
2409
|
-
hasTimestampInSecond,
|
|
2410
|
-
addFrame,
|
|
2411
|
-
deleteFramesBeforeTimestamp,
|
|
2412
|
-
src,
|
|
2413
|
-
getOpenFrameCount,
|
|
2414
|
-
getLastUsed
|
|
2415
|
-
};
|
|
2416
|
-
return keyframeBank;
|
|
2417
|
-
};
|
|
2418
|
-
|
|
2419
|
-
// src/video-extraction/remember-actual-matroska-timestamps.ts
|
|
2420
|
-
var rememberActualMatroskaTimestamps = (isMatroska) => {
|
|
2421
|
-
const observations = [];
|
|
2422
|
-
const observeTimestamp = (startTime) => {
|
|
2423
|
-
if (!isMatroska) {
|
|
2424
|
-
return;
|
|
2425
|
-
}
|
|
2426
|
-
observations.push(startTime);
|
|
2427
|
-
};
|
|
2428
|
-
const getRealTimestamp = (observedTimestamp) => {
|
|
2429
|
-
if (!isMatroska) {
|
|
2430
|
-
return observedTimestamp;
|
|
2431
|
-
}
|
|
2432
|
-
return observations.find((observation) => Math.abs(observedTimestamp - observation) < 0.001) ?? null;
|
|
2433
|
-
};
|
|
2434
|
-
return {
|
|
2435
|
-
observeTimestamp,
|
|
2436
|
-
getRealTimestamp
|
|
2437
|
-
};
|
|
2438
|
-
};
|
|
2439
|
-
|
|
2440
|
-
// src/video-extraction/get-frames-since-keyframe.ts
|
|
2441
|
-
var getRetryDelay = () => {
|
|
2442
|
-
return null;
|
|
2443
|
-
};
|
|
2444
|
-
var getFormatOrNullOrNetworkError = async (input) => {
|
|
2445
|
-
try {
|
|
2446
|
-
return await input.getFormat();
|
|
2447
|
-
} catch (err) {
|
|
2448
|
-
if (isNetworkError(err)) {
|
|
2449
|
-
return "network-error";
|
|
2450
|
-
}
|
|
2451
|
-
return null;
|
|
2371
|
+
const firstFrame = await sampleIterator.next();
|
|
2372
|
+
if (!firstFrame.value) {
|
|
2373
|
+
throw new Error("No first frame found");
|
|
2452
2374
|
}
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
})
|
|
2460
|
-
});
|
|
2461
|
-
const format = await getFormatOrNullOrNetworkError(input);
|
|
2462
|
-
const isMatroska = format === MATROSKA || format === WEBM;
|
|
2463
|
-
const getVideoSinks = async () => {
|
|
2464
|
-
if (format === "network-error") {
|
|
2465
|
-
return "network-error";
|
|
2466
|
-
}
|
|
2467
|
-
if (format === null) {
|
|
2468
|
-
return "unknown-container-format";
|
|
2469
|
-
}
|
|
2470
|
-
const videoTrack = await input.getPrimaryVideoTrack();
|
|
2471
|
-
if (!videoTrack) {
|
|
2472
|
-
return "no-video-track";
|
|
2473
|
-
}
|
|
2474
|
-
const canDecode = await videoTrack.canDecode();
|
|
2475
|
-
if (!canDecode) {
|
|
2476
|
-
return "cannot-decode";
|
|
2375
|
+
const startTimestampInSeconds = firstFrame.value.timestamp;
|
|
2376
|
+
Internals9.Log.verbose({ logLevel: parentLogLevel, tag: "@remotion/media" }, `Creating keyframe bank from ${startTimestampInSeconds}sec`);
|
|
2377
|
+
addFrame(firstFrame.value, parentLogLevel);
|
|
2378
|
+
const getRangeOfTimestamps = () => {
|
|
2379
|
+
if (frameTimestamps.length === 0) {
|
|
2380
|
+
return null;
|
|
2477
2381
|
}
|
|
2478
2382
|
return {
|
|
2479
|
-
|
|
2480
|
-
|
|
2383
|
+
firstTimestamp: frameTimestamps[0],
|
|
2384
|
+
lastTimestamp: frameTimestamps[frameTimestamps.length - 1]
|
|
2481
2385
|
};
|
|
2482
2386
|
};
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
if (
|
|
2486
|
-
|
|
2387
|
+
const prepareForDeletion = (logLevel, reason) => {
|
|
2388
|
+
const range = getRangeOfTimestamps();
|
|
2389
|
+
if (range) {
|
|
2390
|
+
Internals9.Log.verbose({ logLevel, tag: "@remotion/media" }, `Preparing for deletion (${reason}) of keyframe bank from ${range?.firstTimestamp}sec to ${range?.lastTimestamp}sec`);
|
|
2487
2391
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2392
|
+
let framesDeleted = 0;
|
|
2393
|
+
for (const frameTimestamp of frameTimestamps.slice()) {
|
|
2394
|
+
if (!frames[frameTimestamp]) {
|
|
2395
|
+
continue;
|
|
2396
|
+
}
|
|
2397
|
+
deleteFrameAtTimestamp(frameTimestamp);
|
|
2398
|
+
framesDeleted++;
|
|
2495
2399
|
}
|
|
2496
|
-
|
|
2497
|
-
|
|
2400
|
+
sampleIterator.return();
|
|
2401
|
+
frameTimestamps.length = 0;
|
|
2402
|
+
return { framesDeleted };
|
|
2403
|
+
};
|
|
2404
|
+
const canSatisfyTimestamp = (timestamp) => {
|
|
2405
|
+
if (frameTimestamps.length === 0) {
|
|
2406
|
+
return false;
|
|
2498
2407
|
}
|
|
2499
|
-
const
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2502
|
-
|
|
2408
|
+
const roundedTimestamp = roundTo4Digits(timestamp);
|
|
2409
|
+
const firstFrameTimestamp = roundTo4Digits(frameTimestamps[0]);
|
|
2410
|
+
const lastFrameTimestamp = roundTo4Digits(frameTimestamps[frameTimestamps.length - 1]);
|
|
2411
|
+
if (hasReachedEndOfVideo && roundedTimestamp > lastFrameTimestamp) {
|
|
2412
|
+
return true;
|
|
2503
2413
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
return "cannot-decode-audio";
|
|
2414
|
+
if (roundedTimestamp < firstFrameTimestamp) {
|
|
2415
|
+
return false;
|
|
2507
2416
|
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
};
|
|
2511
|
-
};
|
|
2512
|
-
const getAudioSinksPromise = (index) => {
|
|
2513
|
-
if (audioSinksPromise[index]) {
|
|
2514
|
-
return audioSinksPromise[index];
|
|
2417
|
+
if (roundedTimestamp - BIGGEST_ALLOWED_JUMP_FORWARD_SECONDS > lastFrameTimestamp) {
|
|
2418
|
+
return false;
|
|
2515
2419
|
}
|
|
2516
|
-
|
|
2517
|
-
return audioSinksPromise[index];
|
|
2420
|
+
return true;
|
|
2518
2421
|
};
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2422
|
+
const keyframeBank = {
|
|
2423
|
+
getFrameFromTimestamp: (timestamp) => {
|
|
2424
|
+
queue = queue.then(() => getFrameFromTimestamp(timestamp));
|
|
2425
|
+
return queue;
|
|
2426
|
+
},
|
|
2427
|
+
prepareForDeletion,
|
|
2428
|
+
hasTimestampInSecond: (timestamp) => {
|
|
2429
|
+
queue = queue.then(() => hasTimestampInSecond(timestamp));
|
|
2430
|
+
return queue;
|
|
2431
|
+
},
|
|
2432
|
+
addFrame,
|
|
2433
|
+
deleteFramesBeforeTimestamp,
|
|
2434
|
+
src,
|
|
2435
|
+
getOpenFrameCount,
|
|
2436
|
+
getLastUsed,
|
|
2437
|
+
canSatisfyTimestamp,
|
|
2438
|
+
getRangeOfTimestamps
|
|
2527
2439
|
};
|
|
2528
|
-
};
|
|
2529
|
-
var getFramesSinceKeyframe = async ({
|
|
2530
|
-
packetSink,
|
|
2531
|
-
videoSampleSink,
|
|
2532
|
-
startPacket,
|
|
2533
|
-
logLevel,
|
|
2534
|
-
src
|
|
2535
|
-
}) => {
|
|
2536
|
-
const nextKeyPacket = await packetSink.getNextKeyPacket(startPacket, {
|
|
2537
|
-
verifyKeyPackets: true
|
|
2538
|
-
});
|
|
2539
|
-
const sampleIterator = videoSampleSink.samples(startPacket.timestamp, nextKeyPacket ? nextKeyPacket.timestamp : Infinity);
|
|
2540
|
-
const keyframeBank = makeKeyframeBank({
|
|
2541
|
-
startTimestampInSeconds: startPacket.timestamp,
|
|
2542
|
-
endTimestampInSeconds: nextKeyPacket ? nextKeyPacket.timestamp : Infinity,
|
|
2543
|
-
sampleIterator,
|
|
2544
|
-
logLevel,
|
|
2545
|
-
src
|
|
2546
|
-
});
|
|
2547
2440
|
return keyframeBank;
|
|
2548
2441
|
};
|
|
2549
2442
|
|
|
2550
2443
|
// src/video-extraction/keyframe-manager.ts
|
|
2551
2444
|
var makeKeyframeManager = () => {
|
|
2552
|
-
|
|
2553
|
-
const addKeyframeBank = ({
|
|
2554
|
-
src
|
|
2555
|
-
bank
|
|
2556
|
-
startTimestampInSeconds
|
|
2557
|
-
}) => {
|
|
2558
|
-
sources[src] = sources[src] ?? {};
|
|
2559
|
-
sources[src][startTimestampInSeconds] = bank;
|
|
2445
|
+
let sources = {};
|
|
2446
|
+
const addKeyframeBank = ({ src, bank }) => {
|
|
2447
|
+
sources[src] = sources[src] ?? [];
|
|
2448
|
+
sources[src].push(bank);
|
|
2560
2449
|
};
|
|
2561
|
-
const logCacheStats =
|
|
2450
|
+
const logCacheStats = (logLevel) => {
|
|
2562
2451
|
let count = 0;
|
|
2563
2452
|
let totalSize = 0;
|
|
2564
2453
|
for (const src in sources) {
|
|
2565
|
-
for (const bank
|
|
2566
|
-
const
|
|
2567
|
-
const { size, timestamps } = v.getOpenFrameCount();
|
|
2454
|
+
for (const bank of sources[src]) {
|
|
2455
|
+
const { size, timestamps } = bank.getOpenFrameCount();
|
|
2568
2456
|
count += timestamps.length;
|
|
2569
2457
|
totalSize += size;
|
|
2570
2458
|
if (size === 0) {
|
|
@@ -2575,13 +2463,12 @@ var makeKeyframeManager = () => {
|
|
|
2575
2463
|
}
|
|
2576
2464
|
Internals10.Log.verbose({ logLevel, tag: "@remotion/media" }, `Video cache stats: ${count} open frames, ${totalSize} bytes`);
|
|
2577
2465
|
};
|
|
2578
|
-
const getCacheStats =
|
|
2466
|
+
const getCacheStats = () => {
|
|
2579
2467
|
let count = 0;
|
|
2580
2468
|
let totalSize = 0;
|
|
2581
2469
|
for (const src in sources) {
|
|
2582
|
-
for (const bank
|
|
2583
|
-
const
|
|
2584
|
-
const { timestamps, size } = v.getOpenFrameCount();
|
|
2470
|
+
for (const bank of sources[src]) {
|
|
2471
|
+
const { timestamps, size } = bank.getOpenFrameCount();
|
|
2585
2472
|
count += timestamps.length;
|
|
2586
2473
|
totalSize += size;
|
|
2587
2474
|
if (size === 0) {
|
|
@@ -2591,17 +2478,17 @@ var makeKeyframeManager = () => {
|
|
|
2591
2478
|
}
|
|
2592
2479
|
return { count, totalSize };
|
|
2593
2480
|
};
|
|
2594
|
-
const getTheKeyframeBankMostInThePast =
|
|
2481
|
+
const getTheKeyframeBankMostInThePast = () => {
|
|
2595
2482
|
let mostInThePast = null;
|
|
2596
2483
|
let mostInThePastBank = null;
|
|
2597
2484
|
let numberOfBanks = 0;
|
|
2598
2485
|
for (const src in sources) {
|
|
2599
|
-
for (const
|
|
2600
|
-
const
|
|
2486
|
+
for (const bank of sources[src]) {
|
|
2487
|
+
const index = sources[src].indexOf(bank);
|
|
2601
2488
|
const lastUsed = bank.getLastUsed();
|
|
2602
2489
|
if (mostInThePast === null || lastUsed < mostInThePast) {
|
|
2603
2490
|
mostInThePast = lastUsed;
|
|
2604
|
-
mostInThePastBank = { src, bank };
|
|
2491
|
+
mostInThePastBank = { src, bank, index };
|
|
2605
2492
|
}
|
|
2606
2493
|
numberOfBanks++;
|
|
2607
2494
|
}
|
|
@@ -2613,47 +2500,64 @@ var makeKeyframeManager = () => {
|
|
|
2613
2500
|
};
|
|
2614
2501
|
const deleteOldestKeyframeBank = async (logLevel) => {
|
|
2615
2502
|
const {
|
|
2616
|
-
mostInThePastBank: {
|
|
2503
|
+
mostInThePastBank: {
|
|
2504
|
+
bank: mostInThePastBank,
|
|
2505
|
+
src: mostInThePastSrc,
|
|
2506
|
+
index: mostInThePastIndex
|
|
2507
|
+
},
|
|
2617
2508
|
numberOfBanks
|
|
2618
2509
|
} = await getTheKeyframeBankMostInThePast();
|
|
2619
2510
|
if (numberOfBanks < 2) {
|
|
2620
2511
|
return { finish: true };
|
|
2621
2512
|
}
|
|
2622
2513
|
if (mostInThePastBank) {
|
|
2623
|
-
const
|
|
2624
|
-
|
|
2625
|
-
|
|
2514
|
+
const range = mostInThePastBank.getRangeOfTimestamps();
|
|
2515
|
+
const { framesDeleted } = mostInThePastBank.prepareForDeletion(logLevel, "deleted oldest keyframe bank to stay under max cache size");
|
|
2516
|
+
delete sources[mostInThePastSrc][mostInThePastIndex];
|
|
2517
|
+
if (range) {
|
|
2518
|
+
Internals10.Log.verbose({ logLevel, tag: "@remotion/media" }, `Deleted ${framesDeleted} frames for src ${mostInThePastSrc} from ${range?.firstTimestamp}sec to ${range?.lastTimestamp}sec to free up memory.`);
|
|
2519
|
+
}
|
|
2626
2520
|
}
|
|
2627
2521
|
return { finish: false };
|
|
2628
2522
|
};
|
|
2629
2523
|
const ensureToStayUnderMaxCacheSize = async (logLevel, maxCacheSize) => {
|
|
2630
2524
|
let cacheStats = await getTotalCacheStats();
|
|
2631
|
-
|
|
2525
|
+
let attempts = 0;
|
|
2526
|
+
const maxAttempts = 3;
|
|
2527
|
+
while (cacheStats.totalSize > maxCacheSize && attempts < maxAttempts) {
|
|
2632
2528
|
const { finish } = await deleteOldestKeyframeBank(logLevel);
|
|
2633
2529
|
if (finish) {
|
|
2634
2530
|
break;
|
|
2635
2531
|
}
|
|
2636
2532
|
Internals10.Log.verbose({ logLevel, tag: "@remotion/media" }, "Deleted oldest keyframe bank to stay under max cache size", (cacheStats.totalSize / 1024 / 1024).toFixed(1), "out of", (maxCacheSize / 1024 / 1024).toFixed(1));
|
|
2637
2533
|
cacheStats = await getTotalCacheStats();
|
|
2534
|
+
attempts++;
|
|
2535
|
+
}
|
|
2536
|
+
if (cacheStats.totalSize > maxCacheSize && attempts >= maxAttempts) {
|
|
2537
|
+
Internals10.Log.warn({ logLevel, tag: "@remotion/media" }, `Exceeded max cache size after ${maxAttempts} attempts. Remaining cache size: ${(cacheStats.totalSize / 1024 / 1024).toFixed(1)} MB, target was ${(maxCacheSize / 1024 / 1024).toFixed(1)} MB.`);
|
|
2638
2538
|
}
|
|
2639
2539
|
};
|
|
2640
|
-
const clearKeyframeBanksBeforeTime =
|
|
2540
|
+
const clearKeyframeBanksBeforeTime = ({
|
|
2641
2541
|
timestampInSeconds,
|
|
2642
2542
|
src,
|
|
2643
2543
|
logLevel
|
|
2644
2544
|
}) => {
|
|
2645
|
-
const threshold = timestampInSeconds -
|
|
2545
|
+
const threshold = timestampInSeconds - SAFE_WINDOW_OF_MONOTONICITY;
|
|
2646
2546
|
if (!sources[src]) {
|
|
2647
2547
|
return;
|
|
2648
2548
|
}
|
|
2649
|
-
const banks =
|
|
2650
|
-
for (const
|
|
2651
|
-
const
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2549
|
+
const banks = sources[src];
|
|
2550
|
+
for (const bank of banks) {
|
|
2551
|
+
const range = bank.getRangeOfTimestamps();
|
|
2552
|
+
if (!range) {
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
const { lastTimestamp } = range;
|
|
2556
|
+
if (lastTimestamp < threshold) {
|
|
2557
|
+
bank.prepareForDeletion(logLevel, "cleared before threshold");
|
|
2558
|
+
Internals10.Log.verbose({ logLevel, tag: "@remotion/media" }, `[Video] Cleared frames for src ${src} from ${range.firstTimestamp}sec to ${range.lastTimestamp}sec`);
|
|
2559
|
+
const bankIndex = banks.indexOf(bank);
|
|
2560
|
+
delete sources[src][bankIndex];
|
|
2657
2561
|
} else {
|
|
2658
2562
|
bank.deleteFramesBeforeTimestamp({
|
|
2659
2563
|
timestampInSeconds: threshold,
|
|
@@ -2661,56 +2565,45 @@ var makeKeyframeManager = () => {
|
|
|
2661
2565
|
});
|
|
2662
2566
|
}
|
|
2663
2567
|
}
|
|
2664
|
-
|
|
2568
|
+
sources[src] = sources[src].filter((bank) => bank !== undefined);
|
|
2569
|
+
logCacheStats(logLevel);
|
|
2665
2570
|
};
|
|
2666
2571
|
const getKeyframeBankOrRefetch = async ({
|
|
2667
|
-
packetSink,
|
|
2668
2572
|
timestamp,
|
|
2669
2573
|
videoSampleSink,
|
|
2670
2574
|
src,
|
|
2671
2575
|
logLevel
|
|
2672
2576
|
}) => {
|
|
2673
|
-
const
|
|
2674
|
-
|
|
2675
|
-
}) ?? await packetSink.getFirstPacket({ verifyKeyPackets: true });
|
|
2676
|
-
const hasAlpha = startPacket?.sideData.alpha;
|
|
2677
|
-
if (hasAlpha && !canBrowserUseWebGl2()) {
|
|
2678
|
-
return "has-alpha";
|
|
2679
|
-
}
|
|
2680
|
-
if (!startPacket) {
|
|
2681
|
-
return null;
|
|
2682
|
-
}
|
|
2683
|
-
const startTimestampInSeconds = startPacket.timestamp;
|
|
2684
|
-
const existingBank = sources[src]?.[startTimestampInSeconds];
|
|
2577
|
+
const existingBanks = sources[src] ?? [];
|
|
2578
|
+
const existingBank = existingBanks?.find((bank) => bank.canSatisfyTimestamp(timestamp));
|
|
2685
2579
|
if (!existingBank) {
|
|
2686
|
-
|
|
2687
|
-
|
|
2580
|
+
Internals10.Log.trace({ logLevel, tag: "@remotion/media" }, `Creating new keyframe bank for src ${src} at timestamp ${timestamp}`);
|
|
2581
|
+
const newKeyframeBank = await makeKeyframeBank({
|
|
2688
2582
|
videoSampleSink,
|
|
2689
|
-
startPacket,
|
|
2690
2583
|
logLevel,
|
|
2691
|
-
src
|
|
2584
|
+
src,
|
|
2585
|
+
requestedTimestamp: timestamp
|
|
2692
2586
|
});
|
|
2693
|
-
addKeyframeBank({ src, bank: newKeyframeBank
|
|
2587
|
+
addKeyframeBank({ src, bank: newKeyframeBank });
|
|
2694
2588
|
return newKeyframeBank;
|
|
2695
2589
|
}
|
|
2696
|
-
if (
|
|
2590
|
+
if (existingBank.canSatisfyTimestamp(timestamp)) {
|
|
2591
|
+
Internals10.Log.trace({ logLevel, tag: "@remotion/media" }, `Keyframe bank exists and satisfies timestamp ${timestamp}`);
|
|
2697
2592
|
return existingBank;
|
|
2698
2593
|
}
|
|
2699
2594
|
Internals10.Log.verbose({ logLevel, tag: "@remotion/media" }, `Keyframe bank exists but frame at time ${timestamp} does not exist anymore.`);
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
const replacementKeybank =
|
|
2703
|
-
packetSink,
|
|
2595
|
+
existingBank.prepareForDeletion(logLevel, "already existed but evicted");
|
|
2596
|
+
sources[src] = sources[src].filter((bank) => bank !== existingBank);
|
|
2597
|
+
const replacementKeybank = await makeKeyframeBank({
|
|
2704
2598
|
videoSampleSink,
|
|
2705
|
-
|
|
2599
|
+
requestedTimestamp: timestamp,
|
|
2706
2600
|
logLevel,
|
|
2707
2601
|
src
|
|
2708
2602
|
});
|
|
2709
|
-
addKeyframeBank({ src, bank: replacementKeybank
|
|
2603
|
+
addKeyframeBank({ src, bank: replacementKeybank });
|
|
2710
2604
|
return replacementKeybank;
|
|
2711
2605
|
};
|
|
2712
2606
|
const requestKeyframeBank = async ({
|
|
2713
|
-
packetSink,
|
|
2714
2607
|
timestamp,
|
|
2715
2608
|
videoSampleSink,
|
|
2716
2609
|
src,
|
|
@@ -2718,13 +2611,12 @@ var makeKeyframeManager = () => {
|
|
|
2718
2611
|
maxCacheSize
|
|
2719
2612
|
}) => {
|
|
2720
2613
|
await ensureToStayUnderMaxCacheSize(logLevel, maxCacheSize);
|
|
2721
|
-
|
|
2614
|
+
clearKeyframeBanksBeforeTime({
|
|
2722
2615
|
timestampInSeconds: timestamp,
|
|
2723
2616
|
src,
|
|
2724
2617
|
logLevel
|
|
2725
2618
|
});
|
|
2726
2619
|
const keyframeBank = await getKeyframeBankOrRefetch({
|
|
2727
|
-
packetSink,
|
|
2728
2620
|
timestamp,
|
|
2729
2621
|
videoSampleSink,
|
|
2730
2622
|
src,
|
|
@@ -2732,21 +2624,20 @@ var makeKeyframeManager = () => {
|
|
|
2732
2624
|
});
|
|
2733
2625
|
return keyframeBank;
|
|
2734
2626
|
};
|
|
2735
|
-
const clearAll =
|
|
2627
|
+
const clearAll = (logLevel) => {
|
|
2736
2628
|
const srcs = Object.keys(sources);
|
|
2737
2629
|
for (const src of srcs) {
|
|
2738
|
-
const banks =
|
|
2739
|
-
for (const
|
|
2740
|
-
|
|
2741
|
-
bank.prepareForDeletion(logLevel);
|
|
2742
|
-
delete sources[src][startTimeInSeconds];
|
|
2630
|
+
const banks = sources[src];
|
|
2631
|
+
for (const bank of banks) {
|
|
2632
|
+
bank.prepareForDeletion(logLevel, "clearAll");
|
|
2743
2633
|
}
|
|
2634
|
+
sources[src] = [];
|
|
2744
2635
|
}
|
|
2636
|
+
sources = {};
|
|
2745
2637
|
};
|
|
2746
2638
|
let queue = Promise.resolve(undefined);
|
|
2747
2639
|
return {
|
|
2748
2640
|
requestKeyframeBank: ({
|
|
2749
|
-
packetSink,
|
|
2750
2641
|
timestamp,
|
|
2751
2642
|
videoSampleSink,
|
|
2752
2643
|
src,
|
|
@@ -2754,7 +2645,6 @@ var makeKeyframeManager = () => {
|
|
|
2754
2645
|
maxCacheSize
|
|
2755
2646
|
}) => {
|
|
2756
2647
|
queue = queue.then(() => requestKeyframeBank({
|
|
2757
|
-
packetSink,
|
|
2758
2648
|
timestamp,
|
|
2759
2649
|
videoSampleSink,
|
|
2760
2650
|
src,
|
|
@@ -2769,7 +2659,7 @@ var makeKeyframeManager = () => {
|
|
|
2769
2659
|
};
|
|
2770
2660
|
|
|
2771
2661
|
// src/caches.ts
|
|
2772
|
-
var
|
|
2662
|
+
var SAFE_WINDOW_OF_MONOTONICITY = 0.2;
|
|
2773
2663
|
var keyframeManager = makeKeyframeManager();
|
|
2774
2664
|
var audioManager = makeAudioManager();
|
|
2775
2665
|
var getTotalCacheStats = async () => {
|
|
@@ -3041,6 +2931,154 @@ var combineAudioDataAndClosePrevious = (audioDataArray) => {
|
|
|
3041
2931
|
|
|
3042
2932
|
// src/get-sink.ts
|
|
3043
2933
|
import { Internals as Internals12 } from "remotion";
|
|
2934
|
+
|
|
2935
|
+
// src/video-extraction/get-frames-since-keyframe.ts
|
|
2936
|
+
import {
|
|
2937
|
+
ALL_FORMATS as ALL_FORMATS2,
|
|
2938
|
+
AudioSampleSink,
|
|
2939
|
+
EncodedPacketSink,
|
|
2940
|
+
Input as Input2,
|
|
2941
|
+
MATROSKA,
|
|
2942
|
+
UrlSource as UrlSource2,
|
|
2943
|
+
VideoSampleSink,
|
|
2944
|
+
WEBM
|
|
2945
|
+
} from "mediabunny";
|
|
2946
|
+
|
|
2947
|
+
// src/browser-can-use-webgl2.ts
|
|
2948
|
+
var browserCanUseWebGl2 = null;
|
|
2949
|
+
var browserCanUseWebGl2Uncached = () => {
|
|
2950
|
+
const canvas = new OffscreenCanvas(1, 1);
|
|
2951
|
+
const context = canvas.getContext("webgl2");
|
|
2952
|
+
return context !== null;
|
|
2953
|
+
};
|
|
2954
|
+
var canBrowserUseWebGl2 = () => {
|
|
2955
|
+
if (browserCanUseWebGl2 !== null) {
|
|
2956
|
+
return browserCanUseWebGl2;
|
|
2957
|
+
}
|
|
2958
|
+
browserCanUseWebGl2 = browserCanUseWebGl2Uncached();
|
|
2959
|
+
return browserCanUseWebGl2;
|
|
2960
|
+
};
|
|
2961
|
+
|
|
2962
|
+
// src/video-extraction/remember-actual-matroska-timestamps.ts
|
|
2963
|
+
var rememberActualMatroskaTimestamps = (isMatroska) => {
|
|
2964
|
+
const observations = [];
|
|
2965
|
+
const observeTimestamp = (startTime) => {
|
|
2966
|
+
if (!isMatroska) {
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
observations.push(startTime);
|
|
2970
|
+
};
|
|
2971
|
+
const getRealTimestamp = (observedTimestamp) => {
|
|
2972
|
+
if (!isMatroska) {
|
|
2973
|
+
return observedTimestamp;
|
|
2974
|
+
}
|
|
2975
|
+
return observations.find((observation) => Math.abs(observedTimestamp - observation) < 0.001) ?? null;
|
|
2976
|
+
};
|
|
2977
|
+
return {
|
|
2978
|
+
observeTimestamp,
|
|
2979
|
+
getRealTimestamp
|
|
2980
|
+
};
|
|
2981
|
+
};
|
|
2982
|
+
|
|
2983
|
+
// src/video-extraction/get-frames-since-keyframe.ts
|
|
2984
|
+
var getRetryDelay = () => {
|
|
2985
|
+
return null;
|
|
2986
|
+
};
|
|
2987
|
+
var getFormatOrNullOrNetworkError = async (input) => {
|
|
2988
|
+
try {
|
|
2989
|
+
return await input.getFormat();
|
|
2990
|
+
} catch (err) {
|
|
2991
|
+
if (isNetworkError(err)) {
|
|
2992
|
+
return "network-error";
|
|
2993
|
+
}
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
};
|
|
2997
|
+
var getSinks = async (src) => {
|
|
2998
|
+
const input = new Input2({
|
|
2999
|
+
formats: ALL_FORMATS2,
|
|
3000
|
+
source: new UrlSource2(src, {
|
|
3001
|
+
getRetryDelay
|
|
3002
|
+
})
|
|
3003
|
+
});
|
|
3004
|
+
const format = await getFormatOrNullOrNetworkError(input);
|
|
3005
|
+
const isMatroska = format === MATROSKA || format === WEBM;
|
|
3006
|
+
const getVideoSinks = async () => {
|
|
3007
|
+
if (format === "network-error") {
|
|
3008
|
+
return "network-error";
|
|
3009
|
+
}
|
|
3010
|
+
if (format === null) {
|
|
3011
|
+
return "unknown-container-format";
|
|
3012
|
+
}
|
|
3013
|
+
const videoTrack = await input.getPrimaryVideoTrack();
|
|
3014
|
+
if (!videoTrack) {
|
|
3015
|
+
return "no-video-track";
|
|
3016
|
+
}
|
|
3017
|
+
const canDecode = await videoTrack.canDecode();
|
|
3018
|
+
if (!canDecode) {
|
|
3019
|
+
return "cannot-decode";
|
|
3020
|
+
}
|
|
3021
|
+
const sampleSink = new VideoSampleSink(videoTrack);
|
|
3022
|
+
const packetSink = new EncodedPacketSink(videoTrack);
|
|
3023
|
+
const startPacket = await packetSink.getFirstPacket({
|
|
3024
|
+
verifyKeyPackets: true
|
|
3025
|
+
});
|
|
3026
|
+
const hasAlpha = startPacket?.sideData.alpha;
|
|
3027
|
+
if (hasAlpha && !canBrowserUseWebGl2()) {
|
|
3028
|
+
return "cannot-decode-alpha";
|
|
3029
|
+
}
|
|
3030
|
+
return {
|
|
3031
|
+
sampleSink
|
|
3032
|
+
};
|
|
3033
|
+
};
|
|
3034
|
+
let videoSinksPromise = null;
|
|
3035
|
+
const getVideoSinksPromise = () => {
|
|
3036
|
+
if (videoSinksPromise) {
|
|
3037
|
+
return videoSinksPromise;
|
|
3038
|
+
}
|
|
3039
|
+
videoSinksPromise = getVideoSinks();
|
|
3040
|
+
return videoSinksPromise;
|
|
3041
|
+
};
|
|
3042
|
+
const audioSinksPromise = {};
|
|
3043
|
+
const getAudioSinks = async (index) => {
|
|
3044
|
+
if (format === null) {
|
|
3045
|
+
return "unknown-container-format";
|
|
3046
|
+
}
|
|
3047
|
+
if (format === "network-error") {
|
|
3048
|
+
return "network-error";
|
|
3049
|
+
}
|
|
3050
|
+
const audioTracks = await input.getAudioTracks();
|
|
3051
|
+
const audioTrack = audioTracks[index];
|
|
3052
|
+
if (!audioTrack) {
|
|
3053
|
+
return "no-audio-track";
|
|
3054
|
+
}
|
|
3055
|
+
const canDecode = await audioTrack.canDecode();
|
|
3056
|
+
if (!canDecode) {
|
|
3057
|
+
return "cannot-decode-audio";
|
|
3058
|
+
}
|
|
3059
|
+
return {
|
|
3060
|
+
sampleSink: new AudioSampleSink(audioTrack)
|
|
3061
|
+
};
|
|
3062
|
+
};
|
|
3063
|
+
const getAudioSinksPromise = (index) => {
|
|
3064
|
+
if (audioSinksPromise[index]) {
|
|
3065
|
+
return audioSinksPromise[index];
|
|
3066
|
+
}
|
|
3067
|
+
audioSinksPromise[index] = getAudioSinks(index);
|
|
3068
|
+
return audioSinksPromise[index];
|
|
3069
|
+
};
|
|
3070
|
+
return {
|
|
3071
|
+
getVideo: () => getVideoSinksPromise(),
|
|
3072
|
+
getAudio: (index) => getAudioSinksPromise(index),
|
|
3073
|
+
actualMatroskaTimestamps: rememberActualMatroskaTimestamps(isMatroska),
|
|
3074
|
+
isMatroska,
|
|
3075
|
+
getDuration: () => {
|
|
3076
|
+
return input.computeDuration();
|
|
3077
|
+
}
|
|
3078
|
+
};
|
|
3079
|
+
};
|
|
3080
|
+
|
|
3081
|
+
// src/get-sink.ts
|
|
3044
3082
|
var sinkPromises = {};
|
|
3045
3083
|
var getSink = (src, logLevel) => {
|
|
3046
3084
|
let promise = sinkPromises[src];
|
|
@@ -3215,6 +3253,12 @@ var extractFrameInternal = async ({
|
|
|
3215
3253
|
if (video === "network-error") {
|
|
3216
3254
|
return { type: "network-error" };
|
|
3217
3255
|
}
|
|
3256
|
+
if (video === "cannot-decode-alpha") {
|
|
3257
|
+
return {
|
|
3258
|
+
type: "cannot-decode-alpha",
|
|
3259
|
+
durationInSeconds: mediaDurationInSeconds
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3218
3262
|
const timeInSeconds = getTimeInSeconds({
|
|
3219
3263
|
loop,
|
|
3220
3264
|
mediaDurationInSeconds,
|
|
@@ -3229,36 +3273,29 @@ var extractFrameInternal = async ({
|
|
|
3229
3273
|
if (timeInSeconds === null) {
|
|
3230
3274
|
return {
|
|
3231
3275
|
type: "success",
|
|
3232
|
-
|
|
3276
|
+
sample: null,
|
|
3233
3277
|
durationInSeconds: await sink.getDuration()
|
|
3234
3278
|
};
|
|
3235
3279
|
}
|
|
3236
3280
|
try {
|
|
3237
3281
|
const keyframeBank = await keyframeManager.requestKeyframeBank({
|
|
3238
|
-
packetSink: video.packetSink,
|
|
3239
3282
|
videoSampleSink: video.sampleSink,
|
|
3240
3283
|
timestamp: timeInSeconds,
|
|
3241
3284
|
src,
|
|
3242
3285
|
logLevel,
|
|
3243
3286
|
maxCacheSize
|
|
3244
3287
|
});
|
|
3245
|
-
if (keyframeBank === "has-alpha") {
|
|
3246
|
-
return {
|
|
3247
|
-
type: "cannot-decode-alpha",
|
|
3248
|
-
durationInSeconds: await sink.getDuration()
|
|
3249
|
-
};
|
|
3250
|
-
}
|
|
3251
3288
|
if (!keyframeBank) {
|
|
3252
3289
|
return {
|
|
3253
3290
|
type: "success",
|
|
3254
|
-
|
|
3291
|
+
sample: null,
|
|
3255
3292
|
durationInSeconds: await sink.getDuration()
|
|
3256
3293
|
};
|
|
3257
3294
|
}
|
|
3258
3295
|
const frame = await keyframeBank.getFrameFromTimestamp(timeInSeconds);
|
|
3259
3296
|
return {
|
|
3260
3297
|
type: "success",
|
|
3261
|
-
frame,
|
|
3298
|
+
sample: frame,
|
|
3262
3299
|
durationInSeconds: await sink.getDuration()
|
|
3263
3300
|
};
|
|
3264
3301
|
} catch (err) {
|
|
@@ -3322,7 +3359,7 @@ var extractFrameAndAudio = async ({
|
|
|
3322
3359
|
maxCacheSize
|
|
3323
3360
|
}) => {
|
|
3324
3361
|
try {
|
|
3325
|
-
const [
|
|
3362
|
+
const [video, audio] = await Promise.all([
|
|
3326
3363
|
includeVideo ? extractFrame({
|
|
3327
3364
|
src,
|
|
3328
3365
|
timeInSeconds,
|
|
@@ -3348,59 +3385,42 @@ var extractFrameAndAudio = async ({
|
|
|
3348
3385
|
maxCacheSize
|
|
3349
3386
|
}) : null
|
|
3350
3387
|
]);
|
|
3351
|
-
if (
|
|
3388
|
+
if (video?.type === "cannot-decode") {
|
|
3352
3389
|
return {
|
|
3353
3390
|
type: "cannot-decode",
|
|
3354
|
-
durationInSeconds:
|
|
3391
|
+
durationInSeconds: video.durationInSeconds
|
|
3355
3392
|
};
|
|
3356
3393
|
}
|
|
3357
|
-
if (
|
|
3394
|
+
if (video?.type === "unknown-container-format") {
|
|
3358
3395
|
return { type: "unknown-container-format" };
|
|
3359
3396
|
}
|
|
3360
|
-
if (
|
|
3397
|
+
if (video?.type === "cannot-decode-alpha") {
|
|
3361
3398
|
return {
|
|
3362
3399
|
type: "cannot-decode-alpha",
|
|
3363
|
-
durationInSeconds:
|
|
3400
|
+
durationInSeconds: video.durationInSeconds
|
|
3364
3401
|
};
|
|
3365
3402
|
}
|
|
3366
|
-
if (
|
|
3403
|
+
if (video?.type === "network-error") {
|
|
3367
3404
|
return { type: "network-error" };
|
|
3368
3405
|
}
|
|
3369
3406
|
if (audio === "unknown-container-format") {
|
|
3370
|
-
if (frame !== null) {
|
|
3371
|
-
frame?.frame?.close();
|
|
3372
|
-
}
|
|
3373
3407
|
return { type: "unknown-container-format" };
|
|
3374
3408
|
}
|
|
3375
3409
|
if (audio === "network-error") {
|
|
3376
|
-
if (frame !== null) {
|
|
3377
|
-
frame?.frame?.close();
|
|
3378
|
-
}
|
|
3379
3410
|
return { type: "network-error" };
|
|
3380
3411
|
}
|
|
3381
3412
|
if (audio === "cannot-decode") {
|
|
3382
|
-
if (frame?.type === "success" && frame.frame !== null) {
|
|
3383
|
-
frame?.frame.close();
|
|
3384
|
-
}
|
|
3385
3413
|
return {
|
|
3386
3414
|
type: "cannot-decode",
|
|
3387
|
-
durationInSeconds:
|
|
3388
|
-
};
|
|
3389
|
-
}
|
|
3390
|
-
if (!frame?.frame) {
|
|
3391
|
-
return {
|
|
3392
|
-
type: "success",
|
|
3393
|
-
frame: null,
|
|
3394
|
-
audio: audio?.data ?? null,
|
|
3395
|
-
durationInSeconds: audio?.durationInSeconds ?? null
|
|
3415
|
+
durationInSeconds: video?.type === "success" ? video.durationInSeconds : null
|
|
3396
3416
|
};
|
|
3397
3417
|
}
|
|
3398
3418
|
return {
|
|
3399
3419
|
type: "success",
|
|
3400
|
-
frame: await rotateFrame({
|
|
3401
|
-
frame:
|
|
3402
|
-
rotation:
|
|
3403
|
-
}),
|
|
3420
|
+
frame: video?.sample ? await rotateFrame({
|
|
3421
|
+
frame: video.sample.toVideoFrame(),
|
|
3422
|
+
rotation: video.sample.rotation
|
|
3423
|
+
}) : null,
|
|
3404
3424
|
audio: audio?.data ?? null,
|
|
3405
3425
|
durationInSeconds: audio?.durationInSeconds ?? null
|
|
3406
3426
|
};
|
|
@@ -3667,7 +3687,7 @@ var AudioForRendering = ({
|
|
|
3667
3687
|
loopVolumeCurveBehavior,
|
|
3668
3688
|
delayRenderRetries,
|
|
3669
3689
|
delayRenderTimeoutInMilliseconds,
|
|
3670
|
-
logLevel
|
|
3690
|
+
logLevel: overriddenLogLevel,
|
|
3671
3691
|
loop,
|
|
3672
3692
|
fallbackHtml5AudioProps,
|
|
3673
3693
|
audioStreamIndex,
|
|
@@ -3679,6 +3699,8 @@ var AudioForRendering = ({
|
|
|
3679
3699
|
trimAfter,
|
|
3680
3700
|
trimBefore
|
|
3681
3701
|
}) => {
|
|
3702
|
+
const defaultLogLevel = Internals14.useLogLevel();
|
|
3703
|
+
const logLevel = overriddenLogLevel ?? defaultLogLevel;
|
|
3682
3704
|
const frame = useCurrentFrame3();
|
|
3683
3705
|
const absoluteFrame = Internals14.useTimelinePosition();
|
|
3684
3706
|
const videoConfig = Internals14.useUnsafeVideoConfig();
|
|
@@ -3701,7 +3723,7 @@ var AudioForRendering = ({
|
|
|
3701
3723
|
sequenceContext?.relativeFrom,
|
|
3702
3724
|
sequenceContext?.durationInFrames
|
|
3703
3725
|
]);
|
|
3704
|
-
const maxCacheSize = useMaxMediaCacheSize(logLevel
|
|
3726
|
+
const maxCacheSize = useMaxMediaCacheSize(logLevel);
|
|
3705
3727
|
const audioEnabled = Internals14.useAudioEnabled();
|
|
3706
3728
|
useLayoutEffect2(() => {
|
|
3707
3729
|
const timestamp = frame / fps;
|
|
@@ -3730,7 +3752,7 @@ var AudioForRendering = ({
|
|
|
3730
3752
|
timeInSeconds: timestamp,
|
|
3731
3753
|
durationInSeconds,
|
|
3732
3754
|
playbackRate: playbackRate ?? 1,
|
|
3733
|
-
logLevel
|
|
3755
|
+
logLevel,
|
|
3734
3756
|
includeAudio: shouldRenderAudio,
|
|
3735
3757
|
includeVideo: false,
|
|
3736
3758
|
isClientSideRendering: environment.isClientSideRendering,
|
|
@@ -3750,7 +3772,7 @@ var AudioForRendering = ({
|
|
|
3750
3772
|
cancelRender2(new Error(`Unknown container format ${src}, and 'disallowFallbackToHtml5Audio' was set. Failing the render.`));
|
|
3751
3773
|
}
|
|
3752
3774
|
Internals14.Log.warn({
|
|
3753
|
-
logLevel
|
|
3775
|
+
logLevel,
|
|
3754
3776
|
tag: "@remotion/media"
|
|
3755
3777
|
}, `Unknown container format for ${src} (Supported formats: https://www.remotion.dev/docs/mediabunny/formats), falling back to <Html5Audio>`);
|
|
3756
3778
|
setReplaceWithHtml5Audio(true);
|
|
@@ -3765,7 +3787,7 @@ var AudioForRendering = ({
|
|
|
3765
3787
|
cancelRender2(new Error(`Cannot decode ${src}, and 'disallowFallbackToHtml5Audio' was set. Failing the render.`));
|
|
3766
3788
|
}
|
|
3767
3789
|
Internals14.Log.warn({
|
|
3768
|
-
logLevel
|
|
3790
|
+
logLevel,
|
|
3769
3791
|
tag: "@remotion/media"
|
|
3770
3792
|
}, `Cannot decode ${src}, falling back to <Html5Audio>`);
|
|
3771
3793
|
setReplaceWithHtml5Audio(true);
|
|
@@ -3783,7 +3805,7 @@ var AudioForRendering = ({
|
|
|
3783
3805
|
cancelRender2(new Error(`Cannot decode ${src}, and 'disallowFallbackToHtml5Audio' was set. Failing the render.`));
|
|
3784
3806
|
}
|
|
3785
3807
|
Internals14.Log.warn({
|
|
3786
|
-
logLevel
|
|
3808
|
+
logLevel,
|
|
3787
3809
|
tag: "@remotion/media"
|
|
3788
3810
|
}, `Network error fetching ${src}, falling back to <Html5Audio>`);
|
|
3789
3811
|
setReplaceWithHtml5Audio(true);
|
|
@@ -4026,8 +4048,6 @@ var VideoForPreviewAssertedShowing = ({
|
|
|
4026
4048
|
useEffect3(() => {
|
|
4027
4049
|
if (!sharedAudioContext)
|
|
4028
4050
|
return;
|
|
4029
|
-
if (!sharedAudioContext.audioContext)
|
|
4030
|
-
return;
|
|
4031
4051
|
try {
|
|
4032
4052
|
const player = new MediaPlayer({
|
|
4033
4053
|
canvas: canvasRef.current,
|
|
@@ -4734,6 +4754,7 @@ var Video = ({
|
|
|
4734
4754
|
debugOverlay,
|
|
4735
4755
|
headless
|
|
4736
4756
|
}) => {
|
|
4757
|
+
const fallbackLogLevel = Internals18.useLogLevel();
|
|
4737
4758
|
return /* @__PURE__ */ jsx6(InnerVideo, {
|
|
4738
4759
|
audioStreamIndex: audioStreamIndex ?? 0,
|
|
4739
4760
|
className,
|
|
@@ -4741,7 +4762,7 @@ var Video = ({
|
|
|
4741
4762
|
delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds ?? null,
|
|
4742
4763
|
disallowFallbackToOffthreadVideo: disallowFallbackToOffthreadVideo ?? false,
|
|
4743
4764
|
fallbackOffthreadVideoProps: fallbackOffthreadVideoProps ?? {},
|
|
4744
|
-
logLevel: logLevel ??
|
|
4765
|
+
logLevel: logLevel ?? fallbackLogLevel,
|
|
4745
4766
|
loop: loop ?? false,
|
|
4746
4767
|
loopVolumeCurveBehavior: loopVolumeCurveBehavior ?? "repeat",
|
|
4747
4768
|
muted: muted ?? false,
|