mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
Merge pull request #940 from heygen-com/worktree-fix+seek-slider-duration
fix(studio): seek slider respects effective timeline duration with appended blocks
This commit is contained in:
@@ -106,6 +106,28 @@ describe("readTimelineDurationFromDocument", () => {
|
||||
|
||||
expect(readTimelineDurationFromDocument(doc)).toBe(5.5);
|
||||
});
|
||||
|
||||
it("reads data-hf-authored-duration when data-duration is stripped", () => {
|
||||
const doc = createDocument(`
|
||||
<div data-composition-id="main">
|
||||
<div data-composition-id="sub-a" data-start="0" data-hf-authored-duration="8"></div>
|
||||
<div data-composition-id="sub-b" data-start="60" data-hf-authored-duration="10"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
expect(readTimelineDurationFromDocument(doc)).toBe(70);
|
||||
});
|
||||
|
||||
it("picks the larger of data-duration and data-hf-authored-duration children", () => {
|
||||
const doc = createDocument(`
|
||||
<div data-composition-id="main">
|
||||
<div data-start="0" data-duration="5"></div>
|
||||
<div data-composition-id="ext" data-start="74" data-hf-authored-duration="8"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
expect(readTimelineDurationFromDocument(doc)).toBe(82);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createStaticSeekPlaybackAdapter", () => {
|
||||
@@ -153,6 +175,39 @@ describe("createStaticSeekPlaybackAdapter", () => {
|
||||
expect(renderedTimes).toEqual([2]);
|
||||
expect(adapter.getTime()).toBe(2);
|
||||
});
|
||||
|
||||
it("works with a seek-only adapter (no renderSeek)", () => {
|
||||
const clock = createManualAnimationClock();
|
||||
const seekedTimes: number[] = [];
|
||||
const adapter = createStaticSeekPlaybackAdapter(
|
||||
{
|
||||
getTime: () => 0,
|
||||
seek: (time: number) => {
|
||||
seekedTimes.push(time);
|
||||
},
|
||||
},
|
||||
82,
|
||||
clock,
|
||||
);
|
||||
|
||||
adapter.seek(77);
|
||||
expect(seekedTimes).toEqual([77]);
|
||||
expect(adapter.getTime()).toBe(77);
|
||||
expect(adapter.getDuration()).toBe(82);
|
||||
});
|
||||
|
||||
it("pauses old adapter before replacing with new duration", () => {
|
||||
const clock = createManualAnimationClock();
|
||||
const adapter = createStaticSeekPlaybackAdapter(
|
||||
{ getTime: () => 0, renderSeek: () => {} },
|
||||
10,
|
||||
clock,
|
||||
);
|
||||
adapter.play();
|
||||
expect(adapter.isPlaying()).toBe(true);
|
||||
adapter.pause();
|
||||
expect(adapter.isPlaying()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildStandaloneRootTimelineElement", () => {
|
||||
|
||||
@@ -59,7 +59,7 @@ export function useTimelinePlayer() {
|
||||
const iframeShortcutCleanupRef = useRef<(() => void) | null>(null);
|
||||
const lastTimelineMessageRef = useRef<number>(0);
|
||||
const staticSeekAdapterRef = useRef<{
|
||||
player: RuntimePlaybackAdapter;
|
||||
player: RuntimePlaybackAdapter | PlaybackAdapter;
|
||||
duration: number;
|
||||
adapter: PlaybackAdapter;
|
||||
} | null>(null);
|
||||
@@ -118,13 +118,19 @@ export function useTimelinePlayer() {
|
||||
|
||||
const playerAdapter =
|
||||
win.__player && typeof win.__player.play === "function" ? win.__player : null;
|
||||
if (getAdapterDuration(playerAdapter) > 0) {
|
||||
const docDuration = readTimelineDurationFromDocument(iframe.contentDocument);
|
||||
const adapterDur = getAdapterDuration(playerAdapter);
|
||||
|
||||
if (adapterDur > 0 && docDuration <= adapterDur) {
|
||||
return playerAdapter;
|
||||
}
|
||||
|
||||
let timelineAdapter: PlaybackAdapter | null = null;
|
||||
if (win.__timeline) {
|
||||
const adapter = wrapTimeline(win.__timeline);
|
||||
if (getAdapterDuration(adapter) > 0) return adapter;
|
||||
const dur = getAdapterDuration(adapter);
|
||||
if (dur > 0 && docDuration <= dur) return adapter;
|
||||
if (dur > 0) timelineAdapter ??= adapter;
|
||||
}
|
||||
|
||||
if (win.__timelines) {
|
||||
@@ -139,39 +145,46 @@ export function useTimelinePlayer() {
|
||||
?.getAttribute("data-composition-id");
|
||||
const key = rootId && rootId in win.__timelines ? rootId : keys[keys.length - 1];
|
||||
const adapter = wrapTimeline(win.__timelines[key]);
|
||||
if (getAdapterDuration(adapter) > 0) return adapter;
|
||||
const dur = getAdapterDuration(adapter);
|
||||
if (dur > 0 && docDuration <= dur) return adapter;
|
||||
if (dur > 0) timelineAdapter ??= adapter;
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackDuration = Math.max(
|
||||
// The document timeline extends past every native adapter's duration.
|
||||
// Wrap the best available adapter with the effective duration so the
|
||||
// seek slider, seek clamping, and duration display cover the full range.
|
||||
const bestAdapter = playerAdapter ?? timelineAdapter;
|
||||
const effectiveDuration = Math.max(
|
||||
usePlayerStore.getState().duration,
|
||||
readTimelineDurationFromDocument(iframe.contentDocument),
|
||||
docDuration,
|
||||
adapterDur,
|
||||
);
|
||||
if (
|
||||
playerAdapter &&
|
||||
fallbackDuration > 0 &&
|
||||
(typeof playerAdapter.renderSeek === "function" || typeof playerAdapter.seek === "function")
|
||||
bestAdapter &&
|
||||
effectiveDuration > 0 &&
|
||||
("renderSeek" in bestAdapter || typeof bestAdapter.seek === "function")
|
||||
) {
|
||||
const cached = staticSeekAdapterRef.current;
|
||||
if (cached?.player === playerAdapter && cached.duration === fallbackDuration) {
|
||||
if (cached?.player === bestAdapter && cached.duration === effectiveDuration) {
|
||||
return cached.adapter;
|
||||
}
|
||||
cached?.adapter.pause();
|
||||
const adapter = createStaticSeekPlaybackAdapter(
|
||||
playerAdapter,
|
||||
fallbackDuration,
|
||||
bestAdapter,
|
||||
effectiveDuration,
|
||||
getDefaultStaticSeekPlaybackClock(win),
|
||||
() => usePlayerStore.getState().playbackRate,
|
||||
);
|
||||
staticSeekAdapterRef.current = {
|
||||
player: playerAdapter,
|
||||
duration: fallbackDuration,
|
||||
player: bestAdapter,
|
||||
duration: effectiveDuration,
|
||||
adapter,
|
||||
};
|
||||
return adapter;
|
||||
}
|
||||
|
||||
return playerAdapter;
|
||||
return bestAdapter;
|
||||
} catch (err) {
|
||||
console.warn("[useTimelinePlayer] Could not get playback adapter (cross-origin)", err);
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user