Skip to content

remotion: Fix media duration calculation with playbackRate#6961

Merged
JonnyBurger merged 3 commits intomainfrom
test/playback-rate-duration
Apr 1, 2026
Merged

remotion: Fix media duration calculation with playbackRate#6961
JonnyBurger merged 3 commits intomainfrom
test/playback-rate-duration

Conversation

@JonnyBurger
Copy link
Copy Markdown
Member

Summary

  • Extract media duration calculation from useBasicMediaInTimeline into a pure getMediaDuration function
  • Fix duration calculation to correctly account for playbackRate (fixes Remotion Studio: Video component shows as unmounted even though it is still mounted. #6783)
  • Stop dividing maxMediaDuration by playbackRate in useMaxMediaDuration since upstream now handles it
  • Add unit tests verifying correct durations with playbackRate=2.45 and 300 composition frames

Test plan

  • bun test src/test/get-media-duration.test.ts — 6 tests pass
  • bun test src/test/use-media-in-timeline.test.tsx — existing test passes
  • TypeScript compilation passes

🤖 Generated with Claude Code

JonnyBurger and others added 2 commits April 1, 2026 15:23
Extracts the media duration calculation from useBasicMediaInTimeline
into a pure getMediaDuration function and adds tests to verify correct
duration with playbackRate=2.45 and 300 composition frames (#6783).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stop dividing maxMediaDuration by playbackRate since the duration
calculation now correctly accounts for playbackRate upstream (#6783).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog bot commented Apr 1, 2026

Reviewed PR #6961. The base-case fix for getMediaDuration (no parent sequence, no trims) correctly resolves the timeline showing media as unmounted. Found two issues: the parent-sequence capping logic in getMediaDuration multiplies by playbackRate producing values in the wrong unit system, and the useMaxMediaDuration change removes the / playbackRate divisor creating a unit mismatch with s.duration.

Task list (6/6 completed)
  • Read the diff and understand the changes
  • Read upstream calculateMediaDuration to verify the logic
  • Read use-max-media-duration.ts to understand impact of removing playbackRate division
  • Check for other consumers of calculateMediaDuration or getMediaDuration
  • Trace the math through getMediaDuration to verify correctness
  • Submit review or report progress

Pullfrog  | View workflow run | Triggered by Pullfrog | Using Big Pickle (free) | 𝕏

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bugs Ready Ready Preview, Comment Apr 1, 2026 1:55pm
remotion Ready Ready Preview, Comment Apr 1, 2026 1:55pm

Request Review

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base case fix (no parent sequence, no trims) is correct — getMediaDuration properly returns the composition duration regardless of playbackRate. However, the parent sequence capping logic produces wrong results, and the useMaxMediaDuration change has a subtle inconsistency worth verifying.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Big Pickle (free) | 𝕏

Comment on lines +24 to +27
if (parentSequenceDurationInFrames !== null) {
return Math.floor(
Math.min(parentSequenceDurationInFrames * playbackRate, mediaDuration),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When parentSequenceDurationInFrames is not null, this returns floor(min(parentSeqDur * playbackRate, mediaDuration)). With parentSeqDur=100 and playbackRate=2.45, that's floor(min(245, 300)) = 245.

But the parent sequence constrains the media to 100 composition frames — the duration should be capped at 100, not 245. The multiplication by playbackRate converts to media-frame units, but mediaDuration (the output of calculateMediaDuration) is already in composition-frame units (it divides by playbackRate internally).

This should be:

return Math.floor(
  Math.min(parentSequenceDurationInFrames, mediaDuration),
);

The corresponding test 'parentSequence with playbackRate 2.45 caps correctly' expects 245, but the correct expectation should be 100.


if (maxMediaDuration !== null && (s.type === 'audio' || s.type === 'video')) {
return maxMediaDuration / s.playbackRate;
return maxMediaDuration;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, this returned maxMediaDuration / s.playbackRate — i.e., the number of composition frames the media file can fill at the given rate. Now it returns raw media frames.

Since s.duration (from getMediaDuration) is now in composition frames (correct after fixing the parent-sequence bug above), but maxMediaDuration is now in raw media frames, these are in different units. getTimelineSequenceLayout uses maxMediaDuration to cap the visual width via spatialDuration = min(maxMediaSeqDur, durationInFrames - 1, ...) — mixing units here would cause incorrect timeline widths.

I believe the / s.playbackRate should be kept here, since s.duration is (correctly) in composition-frame units.

@JonnyBurger JonnyBurger merged commit 03066cd into main Apr 1, 2026
30 of 31 checks passed
@JonnyBurger JonnyBurger deleted the test/playback-rate-duration branch April 1, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remotion Studio: Video component shows as unmounted even though it is still mounted.

1 participant