fix(slideshow): harden media controls in present decks (#1619)

This commit is contained in:
Vance Ingalls
2026-06-20 23:35:58 -07:00
committed by GitHub
parent 0d2198e654
commit 341e65aea2
16 changed files with 1713 additions and 81 deletions
@@ -247,6 +247,52 @@ describe("HyperframesPlayer parent-frame media", () => {
expect(mockAudio.pause).toHaveBeenCalled();
});
function dispatchAutoplayBlockedFromPlayerFrame(player: HTMLElement): HTMLMediaElement {
const iframe = player.shadowRoot?.querySelector("iframe");
if (!(iframe instanceof HTMLIFrameElement)) throw new Error("expected player iframe");
const iframeDoc = iframe.contentDocument;
if (!iframeDoc) throw new Error("expected player iframe document");
const video = iframeDoc.createElement("video");
video.setAttribute("data-start", "0");
video.setAttribute("data-duration", "10");
video.muted = false;
iframeDoc.body.appendChild(video);
window.dispatchEvent(
new MessageEvent("message", {
source: iframe.contentWindow,
data: { source: "hf-preview", type: "media-autoplay-blocked" },
}),
);
return video;
}
it("does not mute iframe media on autoplay fallback inside presenter slideshow", () => {
const slideshow = document.createElement("hyperframes-slideshow");
slideshow.appendChild(player);
document.body.appendChild(slideshow);
const video = dispatchAutoplayBlockedFromPlayerFrame(player);
expect(video.muted).toBe(false);
expect(player._audioOwner).toBe("runtime");
slideshow.remove();
});
it("does not promote autoplay fallback inside audience slideshow", () => {
const slideshow = document.createElement("hyperframes-slideshow");
slideshow.setAttribute("mode", "audience");
slideshow.appendChild(player);
document.body.appendChild(slideshow);
const video = dispatchAutoplayBlockedFromPlayerFrame(player);
expect(video.muted).toBe(false);
expect(player._audioOwner).toBe("runtime");
slideshow.remove();
});
it("seek() while playing pauses parent proxy (prevents mirrorTime stutter loop)", () => {
// Regression: previously `seek()` only called `seekAll()`, leaving the
// proxy playing. With the timeline frozen at the new seek target, the
@@ -1834,6 +1880,40 @@ describe("HyperframesPlayer runtime ready handshake", () => {
});
});
it("keeps runtime WebAudio media enabled outside slideshow embeds", () => {
postSpy.mockClear();
player._onMessage(readyMessage());
expect(findControlCalls("set-native-media-sync-disabled")[0]?.[0]).toMatchObject({
action: "set-native-media-sync-disabled",
disabled: false,
});
expect(findControlCalls("set-web-audio-media-disabled")[0]?.[0]).toMatchObject({
action: "set-web-audio-media-disabled",
disabled: false,
});
});
it("disables runtime WebAudio media inside slideshow embeds", () => {
const slideshow = document.createElement("hyperframes-slideshow");
slideshow.appendChild(player);
document.body.appendChild(slideshow);
postSpy.mockClear();
player._onMessage(readyMessage());
expect(findControlCalls("set-native-media-sync-disabled")[0]?.[0]).toMatchObject({
action: "set-native-media-sync-disabled",
disabled: true,
});
expect(findControlCalls("set-web-audio-media-disabled")[0]?.[0]).toMatchObject({
action: "set-web-audio-media-disabled",
disabled: true,
});
slideshow.remove();
});
it("replays the muted state forced by audio-locked", () => {
// The audio-locked attribute is the original motivating case for this
// handshake — its `muted = true` side effect must survive an iframe race.