diff --git a/packages/core/src/runtime/timeline.test.ts b/packages/core/src/runtime/timeline.test.ts index 333e0a08c..374686131 100644 --- a/packages/core/src/runtime/timeline.test.ts +++ b/packages/core/src/runtime/timeline.test.ts @@ -41,6 +41,26 @@ describe("collectRuntimeTimelinePayload", () => { expect(result.clips[0].kind).toBe("element"); }); + it("derives a clip's duration from data-end when data-duration is absent", () => { + const root = document.createElement("div"); + root.setAttribute("data-composition-id", "main"); + root.setAttribute("data-duration", "20"); + document.body.appendChild(root); + + const clip = document.createElement("div"); + clip.id = "text-1"; + clip.setAttribute("data-start", "2"); + clip.setAttribute("data-end", "8"); + clip.setAttribute("data-track-index", "0"); + root.appendChild(clip); + + const result = collectRuntimeTimelinePayload(defaultParams); + expect(result.clips).toHaveLength(1); + expect(result.clips[0].start).toBe(2); + // 8 - 2 = 6, not the inherited root duration (20 - 2 = 18) + expect(result.clips[0].duration).toBe(6); + }); + it("identifies video clips by tag", () => { const root = document.createElement("div"); root.setAttribute("data-composition-id", "main"); diff --git a/packages/core/src/runtime/timeline.ts b/packages/core/src/runtime/timeline.ts index e6328d73a..57366cd1a 100644 --- a/packages/core/src/runtime/timeline.ts +++ b/packages/core/src/runtime/timeline.ts @@ -389,6 +389,14 @@ export function collectRuntimeTimelinePayload(params: { ); const nodeCompositionId = node.getAttribute("data-composition-id"); let duration = parseElementDurationAttr(node); + if (duration == null || duration <= 0) { + // Mirror the scene loop: an explicit data-end sets the clip's duration + // when no data-duration is present. Without this the clip fell through to + // the inherited (composition) duration and was sized start..rootEnd, so a + // 2s..8s clip rendered as start..20s on the timeline. + const endAttr = parseElementEndAttr(node); + if (endAttr != null) duration = Math.max(0, endAttr - start); + } if ( (duration == null || duration <= 0) && nodeCompositionId &&