fix(player): drive src URL timelines without runtime (#673)

* fix(player): drive src URL timelines without runtime

* fix(player): pause direct timeline after seek
This commit is contained in:
Miguel Ángel
2026-05-08 08:19:29 +02:00
committed by GitHub
parent b009288df7
commit f761ee2624
2 changed files with 290 additions and 28 deletions
@@ -790,12 +790,22 @@ describe("HyperframesPlayer seek() sync path", () => {
play?: () => void;
pause?: () => void;
};
type TimelineStub = {
duration: () => number;
time: () => number;
seek: (t: number) => void;
play: () => void;
pause: () => void;
};
type FakeContentWindow = {
__player?: SyncPlayerStub;
__timelines?: Record<string, TimelineStub>;
postMessage?: ReturnType<typeof vi.fn>;
};
type PlayerInternal = HTMLElement & {
seek: (t: number) => void;
play: () => void;
pause: () => void;
iframe: HTMLIFrameElement;
_currentTime: number;
};
@@ -874,6 +884,88 @@ describe("HyperframesPlayer seek() sync path", () => {
);
});
it("seeks same-origin __timelines when no runtime bridge exists", () => {
const timeline: TimelineStub = {
duration: vi.fn(() => 5),
time: vi.fn(() => 0),
seek: vi.fn(),
play: vi.fn(),
pause: vi.fn(),
};
const post = vi.fn();
stubContentWindow({ __timelines: { main: timeline }, postMessage: post });
player.seek(2);
expect(timeline.seek).toHaveBeenCalledTimes(1);
expect(timeline.seek).toHaveBeenCalledWith(2);
expect(post).not.toHaveBeenCalled();
});
it("plays and pauses same-origin __timelines when no runtime bridge exists", () => {
const timeline: TimelineStub = {
duration: vi.fn(() => 5),
time: vi.fn(() => 0),
seek: vi.fn(),
play: vi.fn(),
pause: vi.fn(),
};
const post = vi.fn();
stubContentWindow({ __timelines: { main: timeline }, postMessage: post });
player.play();
player.pause();
expect(timeline.play).toHaveBeenCalledTimes(1);
expect(timeline.pause).toHaveBeenCalledTimes(1);
expect(post).not.toHaveBeenCalled();
});
it("pauses same-origin __timelines after seek while playing", () => {
const pause = vi.fn();
const timeline: TimelineStub = {
duration: vi.fn(() => 5),
time: vi.fn(() => 0),
seek: vi.fn(),
play: vi.fn(),
pause,
};
const post = vi.fn();
stubContentWindow({ __timelines: { main: timeline }, postMessage: post });
player.play();
pause.mockClear();
player.seek(2);
expect(timeline.seek).toHaveBeenCalledWith(2);
expect(pause).toHaveBeenCalledTimes(1);
expect(post).not.toHaveBeenCalled();
});
it("does not bypass an installed runtime bridge for direct __timelines playback", () => {
const timeline: TimelineStub = {
duration: vi.fn(() => 5),
time: vi.fn(() => 0),
seek: vi.fn(),
play: vi.fn(),
pause: vi.fn(),
};
const post = vi.fn();
stubContentWindow({
__player: { play: vi.fn(), pause: vi.fn() },
__timelines: { main: timeline },
postMessage: post,
});
player.play();
player.pause();
expect(timeline.play).not.toHaveBeenCalled();
expect(timeline.pause).not.toHaveBeenCalled();
expect(post).toHaveBeenCalledWith(expect.objectContaining({ action: "play" }), "*");
expect(post).toHaveBeenCalledWith(expect.objectContaining({ action: "pause" }), "*");
});
it("falls back to postMessage when __player exists but lacks seek()", () => {
// Defensive: a partial `__player` (e.g. older runtime, mocked stub) must
// not be assumed callable. `typeof seek !== "function"` guards this.