mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
fix(core): a GSAP call added after the first seek fires once, not twice (#4948)
The seek path skips its silent re-render on timelines with a zero-duration callback, because that re-render re-arms the callback and the next forward seek fires it again. The answer was cached per timeline on the first seek, so a tl.call added later was never seen: the re-render ran and the call fired twice. The check now reads the timeline's current children on every seek.
This commit is contained in:
@@ -2897,6 +2897,29 @@ describe("initSandboxRuntimeModular", () => {
|
||||
expect(seekCalls).toEqual([{ time: 2, suppressEvents: false }]);
|
||||
});
|
||||
|
||||
it("fires a call added after the first seek exactly once", () => {
|
||||
const root = document.createElement("div");
|
||||
root.setAttribute("data-composition-id", "main");
|
||||
root.setAttribute("data-root", "true");
|
||||
root.setAttribute("data-start", "0");
|
||||
root.setAttribute("data-duration", "10");
|
||||
root.setAttribute("data-width", "1920");
|
||||
root.setAttribute("data-height", "1080");
|
||||
document.body.appendChild(root);
|
||||
|
||||
const main = gsap.timeline({ paused: true }).to({ x: 0 }, { x: 1, duration: 10 });
|
||||
window.__timelines = { main };
|
||||
initSandboxRuntimeModular();
|
||||
window.__player?.renderSeek(1);
|
||||
|
||||
const fired = vi.fn();
|
||||
main.call(fired, [], 2);
|
||||
window.__player?.renderSeek(2);
|
||||
window.__player?.renderSeek(3);
|
||||
|
||||
expect(fired).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("shows pip video at global start time even when host composition starts late", () => {
|
||||
// Regression: resolveStartForElement used to add the host composition's start on top of
|
||||
// the video's own data-start, causing double-offset. A pip video with data-start="45.40"
|
||||
|
||||
@@ -4249,7 +4249,6 @@ export function initSandboxRuntimeModular(): void {
|
||||
const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null;
|
||||
|
||||
const gsapCallbackTweenCache = new WeakMap<RuntimeTimelineLike, boolean>();
|
||||
const GSAP_CALLBACK_NAMES = [
|
||||
"onStart",
|
||||
"onUpdate",
|
||||
@@ -4271,9 +4270,6 @@ export function initSandboxRuntimeModular(): void {
|
||||
};
|
||||
|
||||
const hasZeroDurationCallbackTween = (timeline: RuntimeTimelineLike): boolean => {
|
||||
const cached = gsapCallbackTweenCache.get(timeline);
|
||||
if (cached != null) return cached;
|
||||
|
||||
if (!("getChildren" in timeline) || typeof timeline.getChildren !== "function") {
|
||||
return false;
|
||||
}
|
||||
@@ -4283,13 +4279,9 @@ export function initSandboxRuntimeModular(): void {
|
||||
children = timeline.getChildren(true, true, true);
|
||||
} catch (err) {
|
||||
swallow("runtime.init.gsapCallbackChildren", err);
|
||||
gsapCallbackTweenCache.set(timeline, false);
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(children)) {
|
||||
gsapCallbackTweenCache.set(timeline, false);
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(children)) return false;
|
||||
|
||||
for (const child of children) {
|
||||
if (!isObjectRecord(child)) continue;
|
||||
@@ -4300,13 +4292,8 @@ export function initSandboxRuntimeModular(): void {
|
||||
|
||||
const totalDuration = readGsapDuration(child, "totalDuration");
|
||||
const duration = totalDuration ?? readGsapDuration(child, "duration");
|
||||
if (duration != null && duration <= 0.000001) {
|
||||
gsapCallbackTweenCache.set(timeline, true);
|
||||
return true;
|
||||
}
|
||||
if (duration != null && duration <= 0.000001) return true;
|
||||
}
|
||||
|
||||
gsapCallbackTweenCache.set(timeline, false);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user