mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(player): handle Infinity duration; add lint rules for data-duration and Math.ceil overshoot (#243)
* fix(player): handle Infinity duration from runtime gracefully When compositions have repeating animations without data-duration, the runtime sends durationInFrames: Infinity. The player now ignores non-finite duration values instead of displaying "Infinity:NaN" in the controls. formatTime also returns "0:00" for non-finite inputs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(lint): add data-duration and Math.ceil overshoot rules - Add root_composition_missing_data_duration warning when the root composition element is missing data-duration, which causes the runtime to infer Infinity for loop-inflated timelines. - Add gsap_repeat_ceil_overshoot warning that catches repeat: Math.ceil(d/c)-1 patterns which overshoot the intended duration. Recommends Math.floor instead. - Fix gsap_infinite_repeat fixHint to suggest Math.floor (not Math.ceil). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(player): wait for injected runtime before declaring ready When the player auto-injects the runtime script (because the composition has GSAP timelines but no runtime), it would immediately declare ready on the next probe cycle — before the runtime script finished loading from CDN. This caused play() to send a postMessage that nobody received, making autoplay silently fail. Now the probe waits for the runtime bridge (__hf or __player) to appear before proceeding to the ready state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
794b02153d
commit
18de86e4bd
@@ -210,6 +210,26 @@ export const compositionRules: Array<(ctx: LintContext) => HyperframeLintFinding
|
||||
return findings;
|
||||
},
|
||||
|
||||
// root_composition_missing_data_duration
|
||||
({ rootTag }) => {
|
||||
const findings: HyperframeLintFinding[] = [];
|
||||
if (!rootTag) return findings;
|
||||
const compId = readAttr(rootTag.raw, "data-composition-id");
|
||||
if (!compId) return findings;
|
||||
const hasDuration = readAttr(rootTag.raw, "data-duration") !== null;
|
||||
if (!hasDuration) {
|
||||
findings.push({
|
||||
code: "root_composition_missing_data_duration",
|
||||
severity: "warning",
|
||||
message: `Root composition "${compId}" is missing data-duration. Without an explicit duration, the runtime may infer Infinity for compositions with repeating animations, causing playback issues.`,
|
||||
fixHint:
|
||||
'Add data-duration="X" to the root composition element, where X is the total duration in seconds.',
|
||||
snippet: truncateSnippet(rootTag.raw),
|
||||
});
|
||||
}
|
||||
return findings;
|
||||
},
|
||||
|
||||
// standalone_composition_wrapped_in_template
|
||||
({ rawSource, options }) => {
|
||||
const findings: HyperframeLintFinding[] = [];
|
||||
|
||||
@@ -500,9 +500,40 @@ export const gsapRules: Array<(ctx: LintContext) => HyperframeLintFinding[]> = [
|
||||
message:
|
||||
"GSAP tween uses `repeat: -1` (infinite). Infinite repeats break the deterministic " +
|
||||
"capture engine which seeks to exact frame times. Use a finite repeat count calculated " +
|
||||
"from the composition duration: `repeat: Math.ceil(duration / cycleDuration) - 1`.",
|
||||
"from the composition duration: `repeat: Math.floor(duration / cycleDuration) - 1`.",
|
||||
fixHint:
|
||||
"Replace `repeat: -1` with a finite count, e.g. `repeat: Math.ceil(totalDuration / singleCycleDuration) - 1`.",
|
||||
"Replace `repeat: -1` with a finite count, e.g. `repeat: Math.floor(totalDuration / singleCycleDuration) - 1`. " +
|
||||
"Use Math.floor (not Math.ceil) to ensure the animation fits within the total duration.",
|
||||
snippet: truncateSnippet(snippet),
|
||||
});
|
||||
}
|
||||
}
|
||||
return findings;
|
||||
},
|
||||
|
||||
// gsap_repeat_ceil_overshoot
|
||||
({ scripts }) => {
|
||||
const findings: HyperframeLintFinding[] = [];
|
||||
for (const script of scripts) {
|
||||
const content = script.content;
|
||||
// Match patterns like: repeat: Math.ceil(duration / X) - 1
|
||||
// or repeat: Math.ceil(totalDuration / cycleDuration) - 1
|
||||
const pattern = /repeat\s*:\s*Math\.ceil\s*\([^)]+\)\s*-\s*1/g;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(content)) !== null) {
|
||||
const contextStart = Math.max(0, match.index - 40);
|
||||
const contextEnd = Math.min(content.length, match.index + match[0].length + 40);
|
||||
const snippet = content.slice(contextStart, contextEnd).trim();
|
||||
findings.push({
|
||||
code: "gsap_repeat_ceil_overshoot",
|
||||
severity: "warning",
|
||||
message:
|
||||
"GSAP repeat calculation uses `Math.ceil` which can overshoot the composition duration. " +
|
||||
"For example, Math.ceil(10.5 / 2) - 1 = 5 repeats → 6 cycles × 2s = 12s, exceeding 10.5s.",
|
||||
fixHint:
|
||||
"Use `Math.floor` instead of `Math.ceil` to ensure the animation fits within the duration: " +
|
||||
"`repeat: Math.floor(totalDuration / cycleDuration) - 1`. " +
|
||||
"Math.floor(10.5 / 2) - 1 = 4 repeats → 5 cycles × 2s = 10s ✓",
|
||||
snippet: truncateSnippet(snippet),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user