diff --git a/packages/core/src/runtime/init.ts b/packages/core/src/runtime/init.ts index 90b7eca08..cc814b62a 100644 --- a/packages/core/src/runtime/init.ts +++ b/packages/core/src/runtime/init.ts @@ -1356,6 +1356,9 @@ export function initSandboxRuntimeModular(): void { const mediaStart = Number.parseFloat(element.dataset.playbackStart ?? element.dataset.mediaStart ?? "0") || 0; + const rawRate = element.defaultPlaybackRate; + const playbackRate = + Number.isFinite(rawRate) && rawRate > 0 ? Math.max(0.1, Math.min(5, rawRate)) : 1; const hostRemaining = context.inheritedStart != null && context.inheritedDuration != null && @@ -1364,7 +1367,7 @@ export function initSandboxRuntimeModular(): void { : null; const sourceDuration = Number.isFinite(element.duration) && element.duration > mediaStart - ? Math.max(0, element.duration - mediaStart) + ? Math.max(0, (element.duration - mediaStart) / playbackRate) : null; if (sourceDuration != null && hostRemaining != null) { return Math.min(sourceDuration, hostRemaining); diff --git a/packages/core/src/runtime/media.test.ts b/packages/core/src/runtime/media.test.ts index 77eae50e2..48c75342e 100644 --- a/packages/core/src/runtime/media.test.ts +++ b/packages/core/src/runtime/media.test.ts @@ -131,6 +131,28 @@ describe("refreshRuntimeMediaCache", () => { expect(result.mediaClips[0].duration).toBe(20); }); + it("resolveDurationSeconds must account for playbackRate (regression: clip clipped early)", () => { + const el = createVideo({ "data-start": "0", "data-duration": "10" }); + Object.defineProperty(el, "defaultPlaybackRate", { value: 0.5, writable: true }); + Object.defineProperty(el, "duration", { value: 5, writable: true }); + const result = refreshRuntimeMediaCache({ + resolveDurationSeconds: (element) => { + const mediaStart = + Number.parseFloat(element.dataset.playbackStart ?? element.dataset.mediaStart ?? "0") || + 0; + const rawRate = element.defaultPlaybackRate; + const playbackRate = + Number.isFinite(rawRate) && rawRate > 0 ? Math.max(0.1, Math.min(5, rawRate)) : 1; + return Number.isFinite(element.duration) && element.duration > mediaStart + ? Math.max(0, (element.duration - mediaStart) / playbackRate) + : null; + }, + }); + // 5s source at 0.5x = 10s effective; should NOT be capped to 5s + expect(result.mediaClips[0].duration).toBe(10); + expect(result.mediaClips[0].end).toBe(10); + }); + it("reads native loop attribute", () => { createVideo({ "data-start": "0", "data-duration": "15", loop: "" }); const result = refreshRuntimeMediaCache();