@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.
@@ -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 ?? window.remotion_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 currentPlaybackTime = this.getPlaybackTime();
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
- updateAfterPlaybackRateChange() {
1247
+ updateAudioTimeAfterPlaybackRateChange() {
1251
1248
  if (!this.audioIteratorManager) {
1252
1249
  return;
1253
1250
  }
1254
- this.setPlaybackTime(this.getPlaybackTime(), this.playbackRate * this.globalPlaybackRate);
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.updateAfterPlaybackRateChange();
1269
+ this.updateAudioTimeAfterPlaybackRateChange();
1270
1270
  }
1271
1271
  setGlobalPlaybackRate(rate) {
1272
1272
  this.globalPlaybackRate = rate;
1273
- this.updateAfterPlaybackRateChange();
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.getPlaybackTime();
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
- getPlaybackTime() {
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.currentTime,
1328
- audioContextState: this.sharedAudioContext.state,
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 ?? (typeof window !== "undefined" ? window.remotion_logLevel ?? "info" : "info"),
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 - SAFE_BACK_WINDOW_IN_SECONDS);
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 - SAFE_BACK_WINDOW_IN_SECONDS);
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
- while ((await getTotalCacheStats()).totalSize > maxCacheSize) {
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 makeKeyframeBank = ({
2271
- startTimestampInSeconds,
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
- Internals9.Log.verbose({ logLevel: parentLogLevel, tag: "@remotion/media" }, `Creating keyframe bank from ${startTimestampInSeconds}sec to ${endTimestampInSeconds}sec`);
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
- const isLast = frameTimestamp === frameTimestamps[frameTimestamps.length - 1];
2289
- if (isLast) {
2290
- continue;
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
- allocationSize -= getAllocationSize(frames[frameTimestamp]);
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) + 0.001;
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
- allocationSize -= getAllocationSize(frames[frame.timestamp]);
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 - SAFE_BACK_WINDOW_IN_SECONDS
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(timestampInSeconds) < roundTo4Digits(startTimestampInSeconds)) {
2350
- adjustedTimestamp = startTimestampInSeconds;
2343
+ if (hasReachedEndOfVideo && roundTo4Digits(adjustedTimestamp) > roundTo4Digits(frameTimestamps[frameTimestamps.length - 1])) {
2344
+ adjustedTimestamp = frameTimestamps[frameTimestamps.length - 1];
2351
2345
  }
2352
- if (roundTo4Digits(adjustedTimestamp) > roundTo4Digits(endTimestampInSeconds)) {
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 keyframeBank = {
2402
- startTimestampInSeconds,
2403
- endTimestampInSeconds,
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
- var getSinks = async (src) => {
2455
- const input = new Input2({
2456
- formats: ALL_FORMATS2,
2457
- source: new UrlSource2(src, {
2458
- getRetryDelay
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
- sampleSink: new VideoSampleSink(videoTrack),
2480
- packetSink: new EncodedPacketSink(videoTrack)
2383
+ firstTimestamp: frameTimestamps[0],
2384
+ lastTimestamp: frameTimestamps[frameTimestamps.length - 1]
2481
2385
  };
2482
2386
  };
2483
- let videoSinksPromise = null;
2484
- const getVideoSinksPromise = () => {
2485
- if (videoSinksPromise) {
2486
- return videoSinksPromise;
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
- videoSinksPromise = getVideoSinks();
2489
- return videoSinksPromise;
2490
- };
2491
- const audioSinksPromise = {};
2492
- const getAudioSinks = async (index) => {
2493
- if (format === null) {
2494
- return "unknown-container-format";
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
- if (format === "network-error") {
2497
- return "network-error";
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 audioTracks = await input.getAudioTracks();
2500
- const audioTrack = audioTracks[index];
2501
- if (!audioTrack) {
2502
- return "no-audio-track";
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
- const canDecode = await audioTrack.canDecode();
2505
- if (!canDecode) {
2506
- return "cannot-decode-audio";
2414
+ if (roundedTimestamp < firstFrameTimestamp) {
2415
+ return false;
2507
2416
  }
2508
- return {
2509
- sampleSink: new AudioSampleSink(audioTrack)
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
- audioSinksPromise[index] = getAudioSinks(index);
2517
- return audioSinksPromise[index];
2420
+ return true;
2518
2421
  };
2519
- return {
2520
- getVideo: () => getVideoSinksPromise(),
2521
- getAudio: (index) => getAudioSinksPromise(index),
2522
- actualMatroskaTimestamps: rememberActualMatroskaTimestamps(isMatroska),
2523
- isMatroska,
2524
- getDuration: () => {
2525
- return input.computeDuration();
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
- const sources = {};
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 = async (logLevel) => {
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 in sources[src]) {
2566
- const v = await sources[src][bank];
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 = async () => {
2466
+ const getCacheStats = () => {
2579
2467
  let count = 0;
2580
2468
  let totalSize = 0;
2581
2469
  for (const src in sources) {
2582
- for (const bank in sources[src]) {
2583
- const v = await sources[src][bank];
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 = async () => {
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 b in sources[src]) {
2600
- const bank = await sources[src][b];
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: { bank: mostInThePastBank, src: mostInThePastSrc },
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 { framesDeleted } = mostInThePastBank.prepareForDeletion(logLevel);
2624
- delete sources[mostInThePastSrc][mostInThePastBank.startTimestampInSeconds];
2625
- Internals10.Log.verbose({ logLevel, tag: "@remotion/media" }, `Deleted ${framesDeleted} frames for src ${mostInThePastSrc} from ${mostInThePastBank.startTimestampInSeconds}sec to ${mostInThePastBank.endTimestampInSeconds}sec to free up memory.`);
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
- while (cacheStats.totalSize > maxCacheSize) {
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 = async ({
2540
+ const clearKeyframeBanksBeforeTime = ({
2641
2541
  timestampInSeconds,
2642
2542
  src,
2643
2543
  logLevel
2644
2544
  }) => {
2645
- const threshold = timestampInSeconds - SAFE_BACK_WINDOW_IN_SECONDS;
2545
+ const threshold = timestampInSeconds - SAFE_WINDOW_OF_MONOTONICITY;
2646
2546
  if (!sources[src]) {
2647
2547
  return;
2648
2548
  }
2649
- const banks = Object.keys(sources[src]);
2650
- for (const startTimeInSeconds of banks) {
2651
- const bank = await sources[src][startTimeInSeconds];
2652
- const { endTimestampInSeconds, startTimestampInSeconds } = bank;
2653
- if (endTimestampInSeconds < threshold) {
2654
- bank.prepareForDeletion(logLevel);
2655
- Internals10.Log.verbose({ logLevel, tag: "@remotion/media" }, `[Video] Cleared frames for src ${src} from ${startTimestampInSeconds}sec to ${endTimestampInSeconds}sec`);
2656
- delete sources[src][startTimeInSeconds];
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
- await logCacheStats(logLevel);
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 startPacket = await packetSink.getKeyPacket(timestamp, {
2674
- verifyKeyPackets: true
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
- const newKeyframeBank = getFramesSinceKeyframe({
2687
- packetSink,
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, startTimestampInSeconds });
2587
+ addKeyframeBank({ src, bank: newKeyframeBank });
2694
2588
  return newKeyframeBank;
2695
2589
  }
2696
- if (await (await existingBank).hasTimestampInSecond(timestamp)) {
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
- await (await existingBank).prepareForDeletion(logLevel);
2701
- delete sources[src][startTimestampInSeconds];
2702
- const replacementKeybank = getFramesSinceKeyframe({
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
- startPacket,
2599
+ requestedTimestamp: timestamp,
2706
2600
  logLevel,
2707
2601
  src
2708
2602
  });
2709
- addKeyframeBank({ src, bank: replacementKeybank, startTimestampInSeconds });
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
- await clearKeyframeBanksBeforeTime({
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 = async (logLevel) => {
2627
+ const clearAll = (logLevel) => {
2736
2628
  const srcs = Object.keys(sources);
2737
2629
  for (const src of srcs) {
2738
- const banks = Object.keys(sources[src]);
2739
- for (const startTimeInSeconds of banks) {
2740
- const bank = await sources[src][startTimeInSeconds];
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 SAFE_BACK_WINDOW_IN_SECONDS = 1;
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
- frame: null,
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
- frame: null,
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 [frame, audio] = await Promise.all([
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 (frame?.type === "cannot-decode") {
3388
+ if (video?.type === "cannot-decode") {
3352
3389
  return {
3353
3390
  type: "cannot-decode",
3354
- durationInSeconds: frame.durationInSeconds
3391
+ durationInSeconds: video.durationInSeconds
3355
3392
  };
3356
3393
  }
3357
- if (frame?.type === "unknown-container-format") {
3394
+ if (video?.type === "unknown-container-format") {
3358
3395
  return { type: "unknown-container-format" };
3359
3396
  }
3360
- if (frame?.type === "cannot-decode-alpha") {
3397
+ if (video?.type === "cannot-decode-alpha") {
3361
3398
  return {
3362
3399
  type: "cannot-decode-alpha",
3363
- durationInSeconds: frame.durationInSeconds
3400
+ durationInSeconds: video.durationInSeconds
3364
3401
  };
3365
3402
  }
3366
- if (frame?.type === "network-error") {
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: frame?.type === "success" ? frame.durationInSeconds : null
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: frame.frame.toVideoFrame(),
3402
- rotation: frame.frame.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 = window.remotion_logLevel ?? "info",
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 ?? window.remotion_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: logLevel ?? window.remotion_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: logLevel ?? window.remotion_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: logLevel ?? window.remotion_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: logLevel ?? window.remotion_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 ?? (typeof window !== "undefined" ? window.remotion_logLevel ?? "info" : "info"),
4765
+ logLevel: logLevel ?? fallbackLogLevel,
4745
4766
  loop: loop ?? false,
4746
4767
  loopVolumeCurveBehavior: loopVolumeCurveBehavior ?? "repeat",
4747
4768
  muted: muted ?? false,