Repository navigation
fix(core): the runtime never reports a film's length as Infinity - #4372
Conversation
…length The runtime treated any root timeline running more than 1 s past its media as loop-inflated and, with no data-duration on the root, reported its length as Infinity. Hosts that read only the runtime message, such as an opaque-sandboxed preview, then showed a total of 00:00. Only a timeline at endless-loop scale (7200 s, the length Studio already rejects) now counts as loop-inflated.
When the root timeline looked loop-inflated and the root had no data-duration, the timeline message said Infinity even though the runtime had already chosen the media window as the root's length. Hosts that read only this message (Studio's player, opaque-sandboxed previews) then showed a total of 00:00. The message now carries the length the runtime resolved.
jrusso1020
left a comment
There was a problem hiding this comment.
Approving at d8689fa2.
I checked the PR's claim that the player plays max(timeline, media) against the source. getSafeTimelineDurationSeconds (init.ts:1237) takes Math.max(timelineDuration, durationFloor, fallback). isUsableTimelineDuration has no upper bound, so below 7200 s the message now reports what the clock actually plays. That includes the finite ambient-loop case (300 s over 60 s of media).
Blast radius: timelineLooksLoopInflated has one reader, the preferredRootDuration choice. Changing it moves rootCompositionDuration, and so also timelineWindowEnd clip clamping and the root scene at :610. All of those now follow the same length the clock plays. An authored root data-duration still wins first, and with no finite floor a 1e10 timeline is unchanged, as the PR says.
Tests: timeline.test.ts passes 66/66 at this head (after building packages/parsers). I also broke the change on purpose three ways:
| Change | Result |
|---|---|
Put back the > finiteWindowFloor + 1 rule |
the 156 s test fails |
Put back the Infinity emission |
the endless-loop test fails |
>= → > at 7200 |
66/66 still pass: the exact boundary isn't pinned |
Notes, none blocking:
- The third row: a test at exactly 7200 would lock this to the same boundary Studio's
sanitizeDurationSecondsuses (value < 7200). Worth adding in the shared-constant follow-up you already listed. - For an endless loop with media, the message now says the media window (154.8) while the clock still runs to 1e10. That's strictly better than
Infinity/0:00 for both hosts, but the message and the clock still disagree in that case. Your "did not exercise" list covers the no-media case, not this one.
— Rames
The runtime's timeline message no longer reports a film's length as
Infinity. A film whose animations end a little after its voiceover now reports its real length, and a genuinely looping timeline reports the finite length the runtime already chose for it.The bug
collectRuntimeTimelinePayloadflagged the root GSAP timeline as loop-inflated whenever it ran more than 1 s past the media or sub-composition window. With nodata-durationon the root, the message then saiddurationSeconds: Infinity,durationInFrames: Infinity. That happened even though the runtime had already resolved the root's length to the media window.A real film hit this: animations ending at 156 s, voiceover at 154.8 s, no root
data-duration. The runtime player plays 156 s. Studio in same-origin mode recovers by reading the root'sdata-durationfrom the document. A host that sees only this message, such as@hyperframes/player(its message handler ignores a non-finite duration) or a preview in an opaque sandbox, has nothing to fall back on, and its total reads 00:00 while the film plays.The fix, two commits
LOOP_INFLATED_TIMELINE_SECONDS = 7200, the length Studio'ssanitizeDurationSecondsalready rejects. GSAP reports 1e10 s forrepeat: -1. Below it, the message uses the timeline, which is what the player's clock plays (max(timeline, media)).Infinityin the message. When a timeline is loop-inflated, the message carries the length the runtime resolved (the media window), instead of throwing it away.What I measured
timeline.test.tsexpected Infinity to be 156expected Infinity to be 154.8tsc --noEmit(packages/core, after build) /oxlinton the changed filesReview
Independent adversarial review, one round plus a delta on this head (delta: no BLOCKERs, no MAJORs):
Infinity, so the same 00:00InfinityNo reader of this message's duration was found outside the preview hosts (producer and thumbnails don't consume it).
What I did NOT exercise
useTimelineSyncCallbacks.tssaying the runtime can emitInfinityis now stale; its clamp stays correct for older runtimes. It's left for the Studio follow-up.Before / After
A look-alike film with the same shape (animations end at 2:36, narration at 2:34.8, no root
data-duration), played by<hyperframes-player controls sandbox-origin="opaque">. The film carries the runtime built frommain(Before) or from this branch (After). Both are captured 4 s after Play. Before, the runtime reportsInfinityand the player reads 0:04 / 0:00; after, it reports 156 and reads 0:04 / 2:36.