remotion-dev/remotion · error · Error

Video frames must be in presentation order.

Error message

Video frames must be in presentation order.

What it means

After rebasing (timestamp - firstVideoTimestamp), rebaseVideoTimestamp rejects frames that precede the video start by more than a tiny epsilon (-Number.EPSILON * 16), throwing 'Video frames must be in presentation order.' A small negative slack is tolerated to absorb floating-point jitter. This enforces monotonically non-decreasing frame order expected by the encoder/processor.

Source

Thrown at packages/video-matting/src/video-timing.ts:14

export const rebaseVideoTimestamp = ({
	timestamp,
	firstVideoTimestamp,
}: {
	timestamp: number;
	firstVideoTimestamp: number;
}): number => {
	if (!Number.isFinite(timestamp) || !Number.isFinite(firstVideoTimestamp)) {
		throw new TypeError('Video timestamps must be finite numbers.');
	}

	const rebased = timestamp - firstVideoTimestamp;
	if (rebased < -Number.EPSILON * 16) {
		throw new Error('Video frames must be in presentation order.');
	}

	return Math.max(0, rebased);
};

export const getClippedVideoFrameTiming = ({
	timestamp,
	duration,
	videoStartTimestamp,
	videoEndTimestamp,
}: {
	timestamp: number;
	duration: number;
	videoStartTimestamp: number;
	videoEndTimestamp: number;
}): {timestamp: number; duration: number} | null => {
	if (!Number.isFinite(timestamp)) {
		throw new TypeError('The video frame timestamp must be finite.');

View on GitHub (pinned to b2f4e34732)

Solutions

  1. Sort frames by timestamp (stable sort) before processing
  2. Set firstVideoTimestamp to the minimum frame timestamp of the batch, not the first decoded frame
  3. Check whether you are using DTS instead of PTS and switch to presentation timestamps
  4. Handle seeking explicitly: reset processing state and firstVideoTimestamp after a seek
  5. If jitter is legitimate, clamp tiny negatives instead of reordering frames

Example fix

// before
frames.forEach((f) => process(rebasedTimestamp({timestamp: f.dts, firstVideoTimestamp})));
// after
const ordered = [...frames].sort((a, b) => a.pts - b.pts);
ordered.forEach((f) => process(rebasedTimestamp({timestamp: f.pts, firstVideoTimestamp: ordered[0].pts})));
Defensive patterns

Strategy: validation

Validate before calling

let last = -Infinity;
for (const f of frames) {
  if (f.pts < last) throw new Error('Frames not in presentation order');
  last = f.pts;
}

Type guard

const isInPresentationOrder = (frames: {pts: number}[]): boolean =>
  frames.every((f, i) => i === 0 || f.pts >= frames[i - 1].pts);

Try / catch

try {
  const ts = rebasedTimestamp({timestamp: frame.pts, firstVideoTimestamp});
} catch (err) {
  if (err instanceof Error && err.message === 'Video frames must be in presentation order.') {
    console.warn('Out-of-order frame dropped');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Feeding frames whose timestamps go backwards relative to firstVideoTimestamp beyond floating-point jitter — e.g. interleaved tracks processed out of order, B-frame/PTS-vs-DTS confusion, or frames from a seek arriving before the seek point.

Common situations: Manually sorting frames with an unstable sort, mixing keyframe-first decoding with presentation-order playback, or concatenating frames from multiple video segments without resetting firstVideoTimestamp.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of remotion-dev/remotion@b2f4e34732 (2026-09-09). Data as JSON: /api/errors/1058452d7320291f. Report an issue: GitHub.