@remotion/media 4.0.496 → 4.0.498

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.
@@ -1,4 +1,6 @@
1
1
  import React from 'react';
2
+ import { type InteractivitySchema } from 'remotion';
3
+ export declare const audioSchema: InteractivitySchema;
2
4
  export declare const Audio: React.ComponentType<{
3
5
  src: string;
4
6
  trimBefore?: number | undefined;
@@ -5,6 +5,8 @@ import { type AudioBufferSlice } from './make-iterator-with-priming';
5
5
  import type { Nonce } from './nonce-manager';
6
6
  import type { SharedAudioContextForMediaPlayer } from './shared-audio-context-for-media-player';
7
7
  type ScheduleAudioNode = (node: AudioBufferSourceNode, mediaTimestamp: number, originalUnloopedMediaTimestamp: number, sourceOffsetInSeconds: number, sourceDurationInSeconds: number) => ScheduleAudioNodeResult;
8
+ export declare const MINIMUM_AUDIO_BUFFERING_TIME_SECONDS = 0.1;
9
+ export declare const hasEnoughAudioToStartPlayback: (bufferedDuration: number) => boolean;
8
10
  export type AudioIteratorAnchor = {
9
11
  unloopedStartInSeconds: number;
10
12
  mediaStartInSeconds: number;
@@ -68,16 +68,22 @@ export declare const drawPreviewOverlay: ({ context, audioTime, audioContextStat
68
68
  videoIteratorManager: {
69
69
  startVideoIterator: (timeToSeek: number, nonce: import("../nonce-manager").Nonce) => Promise<void>;
70
70
  getVideoIteratorsCreated: () => number;
71
- seek: ({ newTime, nonce }: {
71
+ seek: ({ newTime, nonce, fps, playbackRate, isPlaying, }: {
72
72
  newTime: number;
73
73
  nonce: import("../nonce-manager").Nonce;
74
+ fps: number;
75
+ playbackRate: number;
76
+ isPlaying: boolean;
74
77
  }) => Promise<void>;
75
78
  destroy: () => void;
76
79
  getVideoFrameIterator: () => {
77
80
  destroy: () => void;
78
81
  initialFrame: import("mediabunny").WrappedCanvas | null;
79
82
  isDestroyed: () => boolean;
80
- tryToSatisfySeek: (time: number) => Promise<{
83
+ tryToSatisfySeek: (time: number, options: {
84
+ pendingFrameBehavior: "restart-iterator" | "wait";
85
+ shouldContinue: () => boolean;
86
+ }) => Promise<{
81
87
  type: "not-satisfied";
82
88
  reason: string;
83
89
  } | {
@@ -415,6 +415,10 @@ async function* makeLoopingIterator({
415
415
  }
416
416
 
417
417
  // src/audio-iterator-manager.ts
418
+ var MINIMUM_AUDIO_BUFFERING_TIME_SECONDS = 0.1;
419
+ var hasEnoughAudioToStartPlayback = (bufferedDuration) => {
420
+ return bufferedDuration >= MINIMUM_AUDIO_BUFFERING_TIME_SECONDS;
421
+ };
418
422
  var anchorToContinuousTime = ({
419
423
  anchor,
420
424
  unloopedTimeInSeconds,
@@ -597,7 +601,7 @@ var audioIteratorManager = ({
597
601
  onDone();
598
602
  return;
599
603
  }
600
- onScheduled(result.value.timelineTimestamp);
604
+ onScheduled(result.value.sourceDurationInSeconds);
601
605
  notifyNodeScheduled();
602
606
  onAudioChunk({
603
607
  buffer: result.value,
@@ -679,24 +683,32 @@ var audioIteratorManager = ({
679
683
  });
680
684
  audioIteratorsCreated++;
681
685
  audioBufferIterator = iterator;
682
- let chunksScheduled = 0;
686
+ let bufferedDuration = 0;
687
+ let hasUnblockedPlayback = false;
688
+ const unblockPlayback = () => {
689
+ if (hasUnblockedPlayback) {
690
+ return;
691
+ }
692
+ hasUnblockedPlayback = true;
693
+ delayHandle.unblock();
694
+ };
683
695
  proceedScheduling({
684
696
  iterator,
685
697
  nonce,
686
698
  getTargetTime,
687
699
  playbackRate,
688
700
  scheduleAudioNode,
689
- onScheduled: () => {
690
- chunksScheduled++;
691
- if (chunksScheduled === 6) {
692
- delayHandle.unblock();
701
+ onScheduled: (sourceDurationInSeconds) => {
702
+ bufferedDuration += sourceDurationInSeconds;
703
+ if (hasEnoughAudioToStartPlayback(bufferedDuration)) {
704
+ unblockPlayback();
693
705
  }
694
706
  },
695
707
  onDestroyed: () => {
696
- delayHandle.unblock();
708
+ unblockPlayback();
697
709
  },
698
710
  onDone: () => {
699
- delayHandle.unblock();
711
+ unblockPlayback();
700
712
  },
701
713
  logLevel,
702
714
  currentTime: sharedAudioContext.audioContext.currentTime,
@@ -1265,7 +1277,6 @@ var makePrewarmedVideoIteratorCache = (videoSink) => {
1265
1277
  };
1266
1278
 
1267
1279
  // src/video/video-preview-iterator.ts
1268
- var MAXIMUM_AWAITED_PEEK_DISTANCE_SECONDS = 0.05;
1269
1280
  var createVideoIterator = async (timeToSeek, cache) => {
1270
1281
  let destroyed = false;
1271
1282
  const iterator = cache.makeIteratorOrUsePrewarmed(timeToSeek);
@@ -1303,22 +1314,12 @@ var createVideoIterator = async (timeToSeek, cache) => {
1303
1314
  wait: next.wait
1304
1315
  };
1305
1316
  };
1306
- const peek = async () => {
1307
- const peeked = peekIfReady();
1308
- if (peeked.type === "ready") {
1309
- return peeked.frame;
1310
- }
1311
- return setPeekedFrame(await peeked.wait());
1312
- };
1313
1317
  const getFrameEndTimestampFromPeek = (frame) => {
1314
1318
  return frame ? roundTo4Digits(frame.timestamp) : Infinity;
1315
1319
  };
1316
- const getFrameEndTimestamp = async () => {
1317
- return getFrameEndTimestampFromPeek(await peek());
1318
- };
1319
- const getFrameEndTimestampIfCloseEnough = async ({
1320
- timestamp,
1321
- frameTimestamp
1320
+ const getFrameEndTimestamp = async ({
1321
+ pendingFrameBehavior,
1322
+ shouldContinue
1322
1323
  }) => {
1323
1324
  const peeked = peekIfReady();
1324
1325
  if (peeked.type === "ready") {
@@ -1327,10 +1328,16 @@ var createVideoIterator = async (timeToSeek, cache) => {
1327
1328
  timestamp: getFrameEndTimestampFromPeek(peeked.frame)
1328
1329
  };
1329
1330
  }
1330
- if (timestamp - frameTimestamp > MAXIMUM_AWAITED_PEEK_DISTANCE_SECONDS) {
1331
+ if (pendingFrameBehavior === "restart-iterator") {
1331
1332
  return { type: "pending" };
1332
1333
  }
1334
+ if (!shouldContinue()) {
1335
+ return { type: "cancelled" };
1336
+ }
1333
1337
  const awaitedPeeked = setPeekedFrame(await peeked.wait());
1338
+ if (!shouldContinue()) {
1339
+ return { type: "cancelled" };
1340
+ }
1334
1341
  return {
1335
1342
  type: "ready",
1336
1343
  timestamp: getFrameEndTimestampFromPeek(awaitedPeeked)
@@ -1384,7 +1391,13 @@ var createVideoIterator = async (timeToSeek, cache) => {
1384
1391
  return;
1385
1392
  });
1386
1393
  };
1387
- const tryToSatisfySeek = async (time) => {
1394
+ const tryToSatisfySeek = async (time, options) => {
1395
+ if (!options.shouldContinue()) {
1396
+ return {
1397
+ type: "not-satisfied",
1398
+ reason: "seek was superseded"
1399
+ };
1400
+ }
1388
1401
  const timestamp = roundTo4Digits(time);
1389
1402
  if (lastReturnedFrame) {
1390
1403
  const frameTimestamp = roundTo4Digits(lastReturnedFrame.timestamp);
@@ -1402,8 +1415,23 @@ var createVideoIterator = async (timeToSeek, cache) => {
1402
1415
  reason: `iterator is too far, most recently returned ${frameTimestamp}`
1403
1416
  };
1404
1417
  }
1405
- const frameEndTimestamp = await getFrameEndTimestamp();
1406
- if (frameTimestamp <= timestamp && frameEndTimestamp > timestamp) {
1418
+ const frameEndTimestamp = await getFrameEndTimestamp({
1419
+ pendingFrameBehavior: options.pendingFrameBehavior,
1420
+ shouldContinue: options.shouldContinue
1421
+ });
1422
+ if (frameEndTimestamp.type === "cancelled") {
1423
+ return {
1424
+ type: "not-satisfied",
1425
+ reason: "seek was superseded"
1426
+ };
1427
+ }
1428
+ if (frameEndTimestamp.type === "pending") {
1429
+ return {
1430
+ type: "not-satisfied",
1431
+ reason: "iterator did not have next frame ready"
1432
+ };
1433
+ }
1434
+ if (frameTimestamp <= timestamp && frameEndTimestamp.timestamp > timestamp) {
1407
1435
  return {
1408
1436
  type: "satisfied",
1409
1437
  frame: lastReturnedFrame
@@ -1423,6 +1451,12 @@ var createVideoIterator = async (timeToSeek, cache) => {
1423
1451
  };
1424
1452
  }
1425
1453
  while (true) {
1454
+ if (!options.shouldContinue()) {
1455
+ return {
1456
+ type: "not-satisfied",
1457
+ reason: "seek was superseded"
1458
+ };
1459
+ }
1426
1460
  const frame = getNextOrNullIfNotAvailable();
1427
1461
  if (frame.type === "need-to-wait-for-it") {
1428
1462
  return {
@@ -1445,10 +1479,16 @@ var createVideoIterator = async (timeToSeek, cache) => {
1445
1479
  };
1446
1480
  }
1447
1481
  const frameTimestamp = roundTo4Digits(frame.frame.timestamp);
1448
- const frameEndTimestamp = await getFrameEndTimestampIfCloseEnough({
1449
- frameTimestamp,
1450
- timestamp
1482
+ const frameEndTimestamp = await getFrameEndTimestamp({
1483
+ pendingFrameBehavior: options.pendingFrameBehavior,
1484
+ shouldContinue: options.shouldContinue
1451
1485
  });
1486
+ if (frameEndTimestamp.type === "cancelled") {
1487
+ return {
1488
+ type: "not-satisfied",
1489
+ reason: "seek was superseded"
1490
+ };
1491
+ }
1452
1492
  if (frameEndTimestamp.type === "pending") {
1453
1493
  return {
1454
1494
  type: "not-satisfied",
@@ -1478,6 +1518,19 @@ var createVideoIterator = async (timeToSeek, cache) => {
1478
1518
 
1479
1519
  // src/video-iterator-manager.ts
1480
1520
  var { runEffectChain } = Internals4;
1521
+ var isSequentialMediaTimeAdvance = ({
1522
+ previousTime,
1523
+ newTime,
1524
+ fps,
1525
+ playbackRate,
1526
+ isPlaying
1527
+ }) => {
1528
+ if (!isPlaying || newTime < previousTime) {
1529
+ return false;
1530
+ }
1531
+ const maximumSequentialAdvance = Math.abs(playbackRate) / fps;
1532
+ return roundTo4Digits(newTime - previousTime) <= roundTo4Digits(maximumSequentialAdvance);
1533
+ };
1481
1534
  var videoIteratorManager = async ({
1482
1535
  delayPlaybackHandleIfNotPremounting,
1483
1536
  canvas,
@@ -1587,13 +1640,20 @@ var videoIteratorManager = async ({
1587
1640
  __callDispose(__stack, _err, _hasErr);
1588
1641
  }
1589
1642
  };
1590
- const seek = async ({ newTime, nonce }) => {
1643
+ const seek = async ({
1644
+ newTime,
1645
+ nonce,
1646
+ fps,
1647
+ playbackRate,
1648
+ isPlaying
1649
+ }) => {
1591
1650
  if (!videoFrameIterator) {
1592
1651
  return;
1593
1652
  }
1594
1653
  if (currentSeek !== null && roundTo4Digits(currentSeek) === roundTo4Digits(newTime)) {
1595
1654
  return;
1596
1655
  }
1656
+ const previousTime = currentSeek;
1597
1657
  currentSeek = newTime;
1598
1658
  if (getIsLooping()) {
1599
1659
  if (getLoopSegmentMediaEndTimestamp() - newTime < 1) {
@@ -1602,7 +1662,17 @@ var videoIteratorManager = async ({
1602
1662
  });
1603
1663
  }
1604
1664
  }
1605
- const videoSatisfyResult = await videoFrameIterator.tryToSatisfySeek(newTime);
1665
+ const pendingFrameBehavior = previousTime !== null && isSequentialMediaTimeAdvance({
1666
+ previousTime,
1667
+ newTime,
1668
+ fps,
1669
+ playbackRate,
1670
+ isPlaying
1671
+ }) ? "wait" : "restart-iterator";
1672
+ const videoSatisfyResult = await videoFrameIterator.tryToSatisfySeek(newTime, {
1673
+ pendingFrameBehavior,
1674
+ shouldContinue: () => !nonce.isStale()
1675
+ });
1606
1676
  if (videoSatisfyResult.type === "satisfied") {
1607
1677
  await drawFrame(videoSatisfyResult.frame);
1608
1678
  return;
@@ -1937,7 +2007,10 @@ class MediaPlayer {
1937
2007
  await Promise.all([
1938
2008
  this.videoIteratorManager?.seek({
1939
2009
  newTime,
1940
- nonce
2010
+ nonce,
2011
+ fps: this.fps,
2012
+ playbackRate: this.playbackRate,
2013
+ isPlaying: this.playing
1941
2014
  }),
1942
2015
  this.audioIteratorManager?.seek({
1943
2016
  newTime,
@@ -5893,7 +5966,6 @@ var videoSchema = {
5893
5966
  },
5894
5967
  ...Internals22.baseSchema,
5895
5968
  ...Internals22.premountSchema,
5896
- ...Internals22.premountStyleSchema,
5897
5969
  volume: {
5898
5970
  type: "number",
5899
5971
  min: 0,
@@ -5914,7 +5986,9 @@ var videoSchema = {
5914
5986
  },
5915
5987
  muted: { type: "boolean", default: false, description: "Muted" },
5916
5988
  loop: { type: "boolean", default: false, description: "Loop" },
5917
- ...Internals22.transformSchema
5989
+ ...Internals22.transformSchema,
5990
+ ...Interactive2.backgroundSchema,
5991
+ ...Interactive2.borderSchema
5918
5992
  };
5919
5993
  var InnerVideo = ({
5920
5994
  src,
@@ -12,7 +12,10 @@ export declare const createVideoIterator: (timeToSeek: number, cache: {
12
12
  destroy: () => void;
13
13
  initialFrame: WrappedCanvas | null;
14
14
  isDestroyed: () => boolean;
15
- tryToSatisfySeek: (time: number) => Promise<{
15
+ tryToSatisfySeek: (time: number, options: {
16
+ pendingFrameBehavior: "restart-iterator" | "wait";
17
+ shouldContinue: () => boolean;
18
+ }) => Promise<{
16
19
  type: "not-satisfied";
17
20
  reason: string;
18
21
  } | {
@@ -1,4 +1,6 @@
1
1
  import React from 'react';
2
+ import { type InteractivitySchema } from 'remotion';
3
+ export declare const videoSchema: InteractivitySchema;
2
4
  export declare const Video: React.ComponentType<{
3
5
  src: string;
4
6
  } & Partial<{
@@ -2,6 +2,13 @@ import type { InputVideoTrack, WrappedCanvas } from 'mediabunny';
2
2
  import type { EffectChainState, EffectDefinitionAndStack } from 'remotion';
3
3
  import type { DelayPlaybackIfNotPremounting } from './delay-playback-if-not-premounting';
4
4
  import type { Nonce } from './nonce-manager';
5
+ export declare const isSequentialMediaTimeAdvance: ({ previousTime, newTime, fps, playbackRate, isPlaying, }: {
6
+ previousTime: number;
7
+ newTime: number;
8
+ fps: number;
9
+ playbackRate: number;
10
+ isPlaying: boolean;
11
+ }) => boolean;
5
12
  export declare const videoIteratorManager: ({ delayPlaybackHandleIfNotPremounting, canvas, context, drawDebugOverlay, logLevel, getOnVideoFrameCallback, videoTrack, getLoopSegmentMediaEndTimestamp, getStartTime, getIsLooping, getEffects, getEffectChainState, }: {
6
13
  videoTrack: InputVideoTrack;
7
14
  delayPlaybackHandleIfNotPremounting: () => DelayPlaybackIfNotPremounting;
@@ -18,16 +25,22 @@ export declare const videoIteratorManager: ({ delayPlaybackHandleIfNotPremountin
18
25
  }) => Promise<{
19
26
  startVideoIterator: (timeToSeek: number, nonce: Nonce) => Promise<void>;
20
27
  getVideoIteratorsCreated: () => number;
21
- seek: ({ newTime, nonce }: {
28
+ seek: ({ newTime, nonce, fps, playbackRate, isPlaying, }: {
22
29
  newTime: number;
23
30
  nonce: Nonce;
31
+ fps: number;
32
+ playbackRate: number;
33
+ isPlaying: boolean;
24
34
  }) => Promise<void>;
25
35
  destroy: () => void;
26
36
  getVideoFrameIterator: () => {
27
37
  destroy: () => void;
28
38
  initialFrame: WrappedCanvas | null;
29
39
  isDestroyed: () => boolean;
30
- tryToSatisfySeek: (time: number) => Promise<{
40
+ tryToSatisfySeek: (time: number, options: {
41
+ pendingFrameBehavior: "restart-iterator" | "wait";
42
+ shouldContinue: () => boolean;
43
+ }) => Promise<{
31
44
  type: "not-satisfied";
32
45
  reason: string;
33
46
  } | {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.496",
3
+ "version": "4.0.498",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "mediabunny": "1.50.8",
26
- "remotion": "4.0.496",
26
+ "remotion": "4.0.498",
27
27
  "zod": "4.3.6"
28
28
  },
29
29
  "peerDependencies": {
@@ -31,8 +31,8 @@
31
31
  "react-dom": ">=16.8.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@remotion/eslint-config-internal": "4.0.496",
35
- "@remotion/player": "4.0.496",
34
+ "@remotion/eslint-config-internal": "4.0.498",
35
+ "@remotion/player": "4.0.498",
36
36
  "@vitest/browser-webdriverio": "4.0.9",
37
37
  "eslint": "9.19.0",
38
38
  "react": "19.2.3",