Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/components/canvas/players/html5-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export class Html5Player extends Player {
const { asset, captureFps } = this;
const { frameCount } = computeHtml5FrameCount({
clipLengthSeconds: this.getLength(),
htmlContent: asset.html,
jsContent: asset.js,
cssContent: asset.css,
fps: captureFps
Expand Down Expand Up @@ -440,6 +441,7 @@ export class Html5Player extends Player {

const { frameCount } = computeHtml5FrameCount({
clipLengthSeconds: this.getLength(),
htmlContent: this.asset.html,
jsContent: this.asset.js,
cssContent: this.asset.css,
fps: this.captureFps
Expand Down Expand Up @@ -692,7 +694,13 @@ export class Html5Player extends Player {
const fps = cached?.fps ?? this.captureFps;
const frameCount =
cached?.pngs.length ??
computeHtml5FrameCount({ clipLengthSeconds: this.getLength(), jsContent: this.asset.js, cssContent: this.asset.css, fps }).frameCount;
computeHtml5FrameCount({
clipLengthSeconds: this.getLength(),
htmlContent: this.asset.html,
jsContent: this.asset.js,
cssContent: this.asset.css,
fps
}).frameCount;
// Seeking to the bake's frame time makes a still match the frame playback shows there.
const idx = Math.min(Math.max(0, Math.floor(seconds * fps)), frameCount - 1);
const blob = cached ? cached.pngs[idx] : await this.captureStillFrame(idx / fps, cacheKey, stale);
Expand Down
34 changes: 32 additions & 2 deletions tests/html5-player.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,16 @@ jest.mock("@canvas/players/player", () => ({
jest.mock("@shotstack/shotstack-canvas", () => ({
Html5AssetSchema: { safeParse: (data: unknown) => ({ success: true, data }) },
composeHtml5IframeSrcdoc: jest.fn(() => "<html><body>Updated</body></html>"),
computeHtml5FrameCount: ({ clipLengthSeconds, fps }: { clipLengthSeconds: number; fps: number }) => ({
computeHtml5FrameCount: jest.fn(({ clipLengthSeconds, fps }: { clipLengthSeconds: number; fps: number }) => ({
frameCount: Math.ceil(clipLengthSeconds * fps)
}),
})),
findHtml5IngestUrls: jest.fn(() => []),
html5ClipLongestEdge: jest.fn(() => 1280),
inlineHtml5IngestAssets: jest.fn()
}));
const canvasLib = jest.requireMock("@shotstack/shotstack-canvas") as {
composeHtml5IframeSrcdoc: jest.Mock;
computeHtml5FrameCount: jest.Mock;
findHtml5IngestUrls: jest.Mock;
html5ClipLongestEdge: jest.Mock;
inlineHtml5IngestAssets: jest.Mock;
Expand Down Expand Up @@ -280,6 +281,24 @@ it("ignores an older iframe timeout while newer content is loading", async () =>
expect(harness.contentContainer.children).toEqual([expect.objectContaining({ kind: "loading" })]);
});

// A page animated only by a style block in its markup has frames to capture, as it does in the render.
it("counts frames from the page's markup when keying and capturing it", async () => {
const { player, harness, iframe } = createPlayer();
Object.assign(player, { getLength: () => 1 });
jest.spyOn(harness, "seekHarness").mockImplementation(() => {
throw new Error("stop after first seek");
});

await harness.hashAsset();
const capture = harness.captureFrames();
await until(() => iframe.srcdoc === "<html><body>Updated</body></html>");
iframe.dispatchEvent(new Event("load"));
await expect(capture).rejects.toThrow("stop after first seek");

const counted = canvasLib.computeHtml5FrameCount.mock.calls.map(([args]) => args.htmlContent);
expect(counted).toEqual(["<div>Background</div>", "<div>Background</div>"]);
});

it("captures from a freshly loaded document rather than the live-edited one", async () => {
const { player, harness, iframe } = createPlayer();
Object.assign(player, { getLength: () => 1 });
Expand Down Expand Up @@ -647,6 +666,17 @@ it("renders a still from its own page without waiting for the clip's capture", a
expect(loading.visible).toBe(true);
});

it("counts frames from the page's markup when rendering a still", async () => {
const { player, harness } = createStillPlayer();
jest.spyOn(harness, "captureFrames").mockImplementation(() => new Promise(() => {}));
harness.beginCapture();

await player.prepareStaticRender();
player.endStaticRender();

expect(canvasLib.computeHtml5FrameCount).toHaveBeenCalledWith(expect.objectContaining({ htmlContent: "<div>Background</div>" }));
});

it("takes a still from captured frames, in memory or cached, without rendering one", async () => {
const { player, harness } = createStillPlayer();
Object.assign(player, { getPlaybackTime: () => 1 });
Expand Down
Loading