mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(producer): keep overlay layers in their scene during shader transitions
This commit is contained in:
@@ -174,6 +174,7 @@ export {
|
||||
captureAlphaPng,
|
||||
applyDomLayerMask,
|
||||
removeDomLayerMask,
|
||||
layerUnitsInside,
|
||||
DOM_LAYER_MASK_STYLE_ID,
|
||||
type BeginFrameResult,
|
||||
} from "./services/screenshotService.js";
|
||||
|
||||
@@ -565,6 +565,25 @@ export async function applyDomLayerMask(
|
||||
* we strip opacity here and then seek to the same time for the next layer,
|
||||
* GSAP won't put it back and the wrapper will render fully opaque.
|
||||
*/
|
||||
/** Layer units inside the elements named by `ids`; their ids are minted per frame, so earlier id lists miss them. */
|
||||
export async function layerUnitsInside(page: Page, ids: string[]): Promise<string[]> {
|
||||
return page.evaluate(
|
||||
(args: { ids: string[]; attr: string }) => {
|
||||
const owners = new Set(args.ids);
|
||||
const inside = (unit: Element) => {
|
||||
for (let node: Element | null = unit; node; node = node.parentElement) {
|
||||
if (owners.has(node.id)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return Array.from(document.querySelectorAll(`[${args.attr}]`))
|
||||
.filter(inside)
|
||||
.map((unit) => unit.getAttribute(args.attr) ?? "");
|
||||
},
|
||||
{ ids, attr: HDR_LAYER_UNIT_ATTR },
|
||||
);
|
||||
}
|
||||
|
||||
export async function removeDomLayerMask(page: Page, _extraHideIds: string[]): Promise<void> {
|
||||
await page.evaluate(
|
||||
(args: {
|
||||
|
||||
@@ -3,7 +3,12 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import puppeteer, { type Browser, type Page } from "puppeteer-core";
|
||||
import { groupIntoLayers } from "../utils/layerCompositor.js";
|
||||
import { resolveHeadlessShellPath } from "./browserManager.js";
|
||||
import { HDR_LAYER_UNIT_ATTR, applyDomLayerMask, removeDomLayerMask } from "./screenshotService.js";
|
||||
import {
|
||||
HDR_LAYER_UNIT_ATTR,
|
||||
applyDomLayerMask,
|
||||
layerUnitsInside,
|
||||
removeDomLayerMask,
|
||||
} from "./screenshotService.js";
|
||||
import { queryElementStacking } from "./videoFrameInjector.js";
|
||||
|
||||
const chromePath = resolveHeadlessShellPath();
|
||||
@@ -49,6 +54,18 @@ describe.skipIf(!hasBrowser)("HDR stacking of content inside an HDR element's ho
|
||||
);
|
||||
});
|
||||
|
||||
it("finds a layer unit inside the elements that hold it, and only there", async () => {
|
||||
await queryElementStacking(page, new Set(["hdr"]));
|
||||
const unitId = await page.$eval(
|
||||
"#title",
|
||||
(el, attr) => el.getAttribute(attr),
|
||||
HDR_LAYER_UNIT_ATTR,
|
||||
);
|
||||
|
||||
expect(await layerUnitsInside(page, ["host"])).toEqual([unitId]);
|
||||
expect(await layerUnitsInside(page, ["hdr"])).toEqual([]);
|
||||
});
|
||||
|
||||
it("captures the overlay only in its own layer", async () => {
|
||||
const stacking = await queryElementStacking(page, new Set(["hdr"]));
|
||||
const ids = stacking.map((entry) => entry.id);
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
blitRgba8OverRgb48le,
|
||||
captureAlphaPng,
|
||||
decodePng,
|
||||
layerUnitsInside,
|
||||
removeDomLayerMask,
|
||||
} from "@hyperframes/engine";
|
||||
import type { ProducerLogger } from "../../../logger.js";
|
||||
@@ -19,6 +20,7 @@ vi.mock("@hyperframes/engine", async (importOriginal) => {
|
||||
captureAlphaPng: vi.fn(async () => Buffer.from("png")),
|
||||
decodePng: vi.fn(() => ({ data: Buffer.alloc(4), width: 1, height: 1 })),
|
||||
removeDomLayerMask: vi.fn(async () => undefined),
|
||||
layerUnitsInside: vi.fn(async () => []),
|
||||
blitRgba8OverRgb48le: vi.fn(),
|
||||
};
|
||||
});
|
||||
@@ -85,6 +87,37 @@ describe("captureSceneIntoBuffer", () => {
|
||||
vi.mocked(blitRgba8OverRgb48le).mockClear();
|
||||
});
|
||||
|
||||
it("keeps a layer unit in the scene that holds it", async () => {
|
||||
const page = { evaluate: vi.fn(async () => undefined) };
|
||||
vi.mocked(layerUnitsInside).mockResolvedValueOnce(["hf-layer-unit-1"]);
|
||||
|
||||
await captureSceneIntoBuffer({
|
||||
session: { page, onBeforeCapture: null } as never,
|
||||
sceneBuf: Buffer.alloc(6),
|
||||
sceneIds: new Set(["scene-a", "title"]),
|
||||
stackingInfo: [makeEl("scene-a"), makeEl("hf-layer-unit-1"), makeEl("scene-b")],
|
||||
time: 0.5,
|
||||
width: 1,
|
||||
height: 1,
|
||||
nativeHdrIds: new Set(),
|
||||
nativeHdrImageIds: new Set(),
|
||||
beforeCaptureHook: null,
|
||||
hdrCompositeCtx: makeContext(),
|
||||
compositeTransfer: "srgb",
|
||||
hdrTargetTransfer: undefined,
|
||||
hdrPerf: undefined,
|
||||
log: makeLogger(),
|
||||
frameIdx: 15,
|
||||
});
|
||||
|
||||
expect(layerUnitsInside).toHaveBeenCalledWith(page, ["scene-a", "title"]);
|
||||
expect(applyDomLayerMask).toHaveBeenCalledWith(
|
||||
page,
|
||||
["scene-a", "hf-layer-unit-1"],
|
||||
["scene-b"],
|
||||
);
|
||||
});
|
||||
|
||||
it("does not re-show hidden transition-scene members in the DOM mask", async () => {
|
||||
const page = { evaluate: vi.fn(async () => undefined) };
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
blitRgba8OverRgb48le,
|
||||
captureAlphaPng,
|
||||
decodePng,
|
||||
layerUnitsInside,
|
||||
queryElementStacking,
|
||||
removeDomLayerMask,
|
||||
} from "@hyperframes/engine";
|
||||
@@ -180,7 +181,7 @@ export async function captureSceneIntoBuffer(a: CaptureSceneArgs): Promise<void>
|
||||
const {
|
||||
session,
|
||||
sceneBuf,
|
||||
sceneIds,
|
||||
sceneIds: sceneElementIds,
|
||||
stackingInfo,
|
||||
time,
|
||||
width,
|
||||
@@ -195,6 +196,10 @@ export async function captureSceneIntoBuffer(a: CaptureSceneArgs): Promise<void>
|
||||
log,
|
||||
frameIdx,
|
||||
} = a;
|
||||
const sceneIds = new Set([
|
||||
...sceneElementIds,
|
||||
...(await layerUnitsInside(session.page, [...sceneElementIds])),
|
||||
]);
|
||||
await seekAndInject(
|
||||
session.page,
|
||||
time,
|
||||
|
||||
Reference in New Issue
Block a user