mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 17:30:50 +00:00
fix(core): account for playbackRate in media duration resolution (#1287)
* fix(core): account for playbackRate in media duration resolution resolveDurationSeconds computed sourceDuration as (element.duration - mediaStart) without dividing by playbackRate. A 5s source at 0.5x should span 10s on the timeline, but was capped at 5s — causing the video to go black once the raw source was exhausted. Read defaultPlaybackRate from the element (same clamping as refreshRuntimeMediaCache) and divide sourceDuration by it so the effective timeline window matches the actual playback speed. * test(core): add regression test for playbackRate in resolveDurationSeconds Pins the fix: a 5s source at 0.5x playbackRate must resolve to 10s effective duration when resolveDurationSeconds is provided (mirroring the init.ts callback pattern). Without the rate division, this would return 5s and clip early.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user