@remotion/media 4.0.372 → 4.0.373

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ const AudioForPreviewAssertedShowing = ({ src, playbackRate, logLevel, muted, vo
13
13
  const [mediaPlayerReady, setMediaPlayerReady] = useState(false);
14
14
  const [shouldFallbackToNativeAudio, setShouldFallbackToNativeAudio] = useState(false);
15
15
  const [playing] = Timeline.usePlayingState();
16
- const timelineContext = useContext(Timeline.TimelineContext);
16
+ const timelineContext = useContext(Internals.TimelineContext);
17
17
  const globalPlaybackRate = timelineContext.playbackRate;
18
18
  const sharedAudioContext = useContext(SharedAudioContext);
19
19
  const buffer = useBufferState();
@@ -44,6 +44,7 @@ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
44
44
  const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
45
45
  const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
46
46
  audioManager.logOpenFrames();
47
+ const trimStartToleranceInSeconds = 0.002;
47
48
  const audioDataArray = [];
48
49
  for (let i = 0; i < samples.length; i++) {
49
50
  const sample = samples[i];
@@ -65,7 +66,8 @@ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
65
66
  let trimEndInSeconds = 0;
66
67
  if (isFirstSample) {
67
68
  trimStartInSeconds = timeInSeconds - sample.timestamp;
68
- if (trimStartInSeconds < 0 && trimStartInSeconds > -1e-10) {
69
+ if (trimStartInSeconds < 0 &&
70
+ trimStartInSeconds > -trimStartToleranceInSeconds) {
69
71
  trimStartInSeconds = 0;
70
72
  }
71
73
  if (trimStartInSeconds < 0) {
@@ -1362,7 +1362,7 @@ var AudioForPreviewAssertedShowing = ({
1362
1362
  const [mediaPlayerReady, setMediaPlayerReady] = useState2(false);
1363
1363
  const [shouldFallbackToNativeAudio, setShouldFallbackToNativeAudio] = useState2(false);
1364
1364
  const [playing] = Timeline.usePlayingState();
1365
- const timelineContext = useContext2(Timeline.TimelineContext);
1365
+ const timelineContext = useContext2(Internals6.TimelineContext);
1366
1366
  const globalPlaybackRate = timelineContext.playbackRate;
1367
1367
  const sharedAudioContext = useContext2(SharedAudioContext);
1368
1368
  const buffer = useBufferState();
@@ -2270,19 +2270,26 @@ var makeKeyframeBank = ({
2270
2270
  };
2271
2271
  const getFrameFromTimestamp = async (timestampInSeconds) => {
2272
2272
  lastUsed = Date.now();
2273
- if (timestampInSeconds < startTimestampInSeconds) {
2274
- return Promise.reject(new Error(`Timestamp is before start timestamp (requested: ${timestampInSeconds}sec, start: ${startTimestampInSeconds})`));
2273
+ const maxClampToleranceInSeconds = 0.1;
2274
+ let adjustedTimestamp = timestampInSeconds;
2275
+ if (roundTo4Digits(timestampInSeconds) < roundTo4Digits(startTimestampInSeconds)) {
2276
+ const differenceInSeconds = startTimestampInSeconds - timestampInSeconds;
2277
+ if (differenceInSeconds <= maxClampToleranceInSeconds) {
2278
+ adjustedTimestamp = startTimestampInSeconds;
2279
+ } else {
2280
+ return Promise.reject(new Error(`Timestamp is before start timestamp (requested: ${timestampInSeconds}sec, start: ${startTimestampInSeconds}sec, difference: ${differenceInSeconds.toFixed(3)}sec exceeds tolerance of ${maxClampToleranceInSeconds}sec)`));
2281
+ }
2275
2282
  }
2276
- if (timestampInSeconds > endTimestampInSeconds) {
2277
- return Promise.reject(new Error(`Timestamp is after end timestamp (requested: ${timestampInSeconds}sec, end: ${endTimestampInSeconds})`));
2283
+ if (roundTo4Digits(adjustedTimestamp) > roundTo4Digits(endTimestampInSeconds)) {
2284
+ return Promise.reject(new Error(`Timestamp is after end timestamp (requested: ${timestampInSeconds}sec, end: ${endTimestampInSeconds}sec)`));
2278
2285
  }
2279
- await ensureEnoughFramesForTimestamp(timestampInSeconds);
2286
+ await ensureEnoughFramesForTimestamp(adjustedTimestamp);
2280
2287
  for (let i = frameTimestamps.length - 1;i >= 0; i--) {
2281
2288
  const sample = frames[frameTimestamps[i]];
2282
2289
  if (!sample) {
2283
2290
  return null;
2284
2291
  }
2285
- if (roundTo4Digits(sample.timestamp) <= roundTo4Digits(timestampInSeconds) || Math.abs(sample.timestamp - timestampInSeconds) <= 0.001) {
2292
+ if (roundTo4Digits(sample.timestamp) <= roundTo4Digits(adjustedTimestamp) || Math.abs(sample.timestamp - adjustedTimestamp) <= 0.001) {
2286
2293
  return sample;
2287
2294
  }
2288
2295
  }
@@ -2588,7 +2595,7 @@ var makeKeyframeManager = () => {
2588
2595
  }) => {
2589
2596
  const startPacket = await packetSink.getKeyPacket(timestamp, {
2590
2597
  verifyKeyPackets: true
2591
- });
2598
+ }) ?? await packetSink.getFirstPacket({ verifyKeyPackets: true });
2592
2599
  const hasAlpha = startPacket?.sideData.alpha;
2593
2600
  if (hasAlpha && !canBrowserUseWebGl2()) {
2594
2601
  return "has-alpha";
@@ -2890,6 +2897,7 @@ var extractAudioInternal = async ({
2890
2897
  const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
2891
2898
  const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
2892
2899
  audioManager.logOpenFrames();
2900
+ const trimStartToleranceInSeconds = 0.002;
2893
2901
  const audioDataArray = [];
2894
2902
  for (let i = 0;i < samples.length; i++) {
2895
2903
  const sample = samples[i];
@@ -2906,7 +2914,7 @@ var extractAudioInternal = async ({
2906
2914
  let trimEndInSeconds = 0;
2907
2915
  if (isFirstSample) {
2908
2916
  trimStartInSeconds = timeInSeconds - sample.timestamp;
2909
- if (trimStartInSeconds < 0 && trimStartInSeconds > -0.0000000001) {
2917
+ if (trimStartInSeconds < 0 && trimStartInSeconds > -trimStartToleranceInSeconds) {
2910
2918
  trimStartInSeconds = 0;
2911
2919
  }
2912
2920
  if (trimStartInSeconds < 0) {
@@ -3583,7 +3591,7 @@ var VideoForPreviewAssertedShowing = ({
3583
3591
  const [mediaPlayerReady, setMediaPlayerReady] = useState4(false);
3584
3592
  const [shouldFallbackToNativeVideo, setShouldFallbackToNativeVideo] = useState4(false);
3585
3593
  const [playing] = Timeline2.usePlayingState();
3586
- const timelineContext = useContext4(Timeline2.TimelineContext);
3594
+ const timelineContext = useContext4(Internals15.TimelineContext);
3587
3595
  const globalPlaybackRate = timelineContext.playbackRate;
3588
3596
  const sharedAudioContext = useContext4(SharedAudioContext2);
3589
3597
  const buffer = useBufferState2();
@@ -15,7 +15,7 @@ const VideoForPreviewAssertedShowing = ({ src: unpreloadedSrc, style, playbackRa
15
15
  const [mediaPlayerReady, setMediaPlayerReady] = useState(false);
16
16
  const [shouldFallbackToNativeVideo, setShouldFallbackToNativeVideo] = useState(false);
17
17
  const [playing] = Timeline.usePlayingState();
18
- const timelineContext = useContext(Timeline.TimelineContext);
18
+ const timelineContext = useContext(Internals.TimelineContext);
19
19
  const globalPlaybackRate = timelineContext.playbackRate;
20
20
  const sharedAudioContext = useContext(SharedAudioContext);
21
21
  const buffer = useBufferState();
@@ -68,23 +68,35 @@ export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSecond
68
68
  };
69
69
  const getFrameFromTimestamp = async (timestampInSeconds) => {
70
70
  lastUsed = Date.now();
71
- if (timestampInSeconds < startTimestampInSeconds) {
72
- return Promise.reject(new Error(`Timestamp is before start timestamp (requested: ${timestampInSeconds}sec, start: ${startTimestampInSeconds})`));
71
+ // Videos may start slightly after timestamp 0 due to encoding, but if the requested timestamp is too far before the bank start, something is likely wrong.
72
+ const maxClampToleranceInSeconds = 0.1;
73
+ // If the requested timestamp is before the start of this bank, clamp it to the start if within tolerance. This handles videos that don't start at timestamp 0.
74
+ // For example, requesting frame at 0sec when video starts at 0.04sec should return the frame at 0.04sec.
75
+ // Test case: https://github.com/remotion-dev/remotion/issues/5915
76
+ let adjustedTimestamp = timestampInSeconds;
77
+ if (roundTo4Digits(timestampInSeconds) <
78
+ roundTo4Digits(startTimestampInSeconds)) {
79
+ const differenceInSeconds = startTimestampInSeconds - timestampInSeconds;
80
+ if (differenceInSeconds <= maxClampToleranceInSeconds) {
81
+ adjustedTimestamp = startTimestampInSeconds;
82
+ }
83
+ else {
84
+ return Promise.reject(new Error(`Timestamp is before start timestamp (requested: ${timestampInSeconds}sec, start: ${startTimestampInSeconds}sec, difference: ${differenceInSeconds.toFixed(3)}sec exceeds tolerance of ${maxClampToleranceInSeconds}sec)`));
85
+ }
73
86
  }
74
- if (timestampInSeconds > endTimestampInSeconds) {
75
- return Promise.reject(new Error(`Timestamp is after end timestamp (requested: ${timestampInSeconds}sec, end: ${endTimestampInSeconds})`));
87
+ if (roundTo4Digits(adjustedTimestamp) > roundTo4Digits(endTimestampInSeconds)) {
88
+ return Promise.reject(new Error(`Timestamp is after end timestamp (requested: ${timestampInSeconds}sec, end: ${endTimestampInSeconds}sec)`));
76
89
  }
77
- await ensureEnoughFramesForTimestamp(timestampInSeconds);
90
+ await ensureEnoughFramesForTimestamp(adjustedTimestamp);
78
91
  for (let i = frameTimestamps.length - 1; i >= 0; i--) {
79
92
  const sample = frames[frameTimestamps[i]];
80
93
  if (!sample) {
81
94
  return null;
82
95
  }
83
- if (roundTo4Digits(sample.timestamp) <=
84
- roundTo4Digits(timestampInSeconds) ||
96
+ if (roundTo4Digits(sample.timestamp) <= roundTo4Digits(adjustedTimestamp) ||
85
97
  // Match 0.3333333333 to 0.33355555
86
98
  // this does not satisfy the previous condition, since one rounds up and one rounds down
87
- Math.abs(sample.timestamp - timestampInSeconds) <= 0.001) {
99
+ Math.abs(sample.timestamp - adjustedTimestamp) <= 0.001) {
88
100
  return sample;
89
101
  }
90
102
  }
@@ -111,9 +111,12 @@ export const makeKeyframeManager = () => {
111
111
  await logCacheStats(logLevel);
112
112
  };
113
113
  const getKeyframeBankOrRefetch = async ({ packetSink, timestamp, videoSampleSink, src, logLevel, }) => {
114
- const startPacket = await packetSink.getKeyPacket(timestamp, {
114
+ // Try to get the keypacket at the requested timestamp.
115
+ // If it returns null (timestamp is before the first keypacket), fall back to the first packet.
116
+ // This matches mediabunny's internal behavior and handles videos that don't start at timestamp 0.
117
+ const startPacket = (await packetSink.getKeyPacket(timestamp, {
115
118
  verifyKeyPackets: true,
116
- });
119
+ })) ?? (await packetSink.getFirstPacket({ verifyKeyPackets: true }));
117
120
  const hasAlpha = startPacket?.sideData.alpha;
118
121
  if (hasAlpha && !canBrowserUseWebGl2()) {
119
122
  return 'has-alpha';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.372",
3
+ "version": "4.0.373",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -22,20 +22,20 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "mediabunny": "1.24.3",
25
- "remotion": "4.0.372",
26
- "webdriverio": "9.19.2"
25
+ "remotion": "4.0.373"
27
26
  },
28
27
  "peerDependencies": {
29
28
  "react": ">=16.8.0",
30
29
  "react-dom": ">=16.8.0"
31
30
  },
32
31
  "devDependencies": {
33
- "@remotion/eslint-config-internal": "4.0.372",
34
- "@vitest/browser": "^3.2.4",
32
+ "@remotion/eslint-config-internal": "4.0.373",
33
+ "@vitest/browser-webdriverio": "4.0.7",
35
34
  "eslint": "9.19.0",
36
35
  "react": "19.0.0",
37
36
  "react-dom": "19.0.0",
38
- "vitest": "3.2.4"
37
+ "vitest": "4.0.7",
38
+ "webdriverio": "9.19.2"
39
39
  },
40
40
  "keywords": [],
41
41
  "publishConfig": {