Skip to content

Commit 8bb1771

Browse files
committed
fix: capture html5 clips animated only by their markup
1 parent cfa1adb commit 8bb1771

2 files changed

Lines changed: 41 additions & 3 deletions

File tree

‎src/components/canvas/players/html5-player.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export class Html5Player extends Player {
164164
const { asset, captureFps } = this;
165165
const { frameCount } = computeHtml5FrameCount({
166166
clipLengthSeconds: this.getLength(),
167+
htmlContent: asset.html,
167168
jsContent: asset.js,
168169
cssContent: asset.css,
169170
fps: captureFps
@@ -440,6 +441,7 @@ export class Html5Player extends Player {
440441

441442
const { frameCount } = computeHtml5FrameCount({
442443
clipLengthSeconds: this.getLength(),
444+
htmlContent: this.asset.html,
443445
jsContent: this.asset.js,
444446
cssContent: this.asset.css,
445447
fps: this.captureFps
@@ -692,7 +694,13 @@ export class Html5Player extends Player {
692694
const fps = cached?.fps ?? this.captureFps;
693695
const frameCount =
694696
cached?.pngs.length ??
695-
computeHtml5FrameCount({ clipLengthSeconds: this.getLength(), jsContent: this.asset.js, cssContent: this.asset.css, fps }).frameCount;
697+
computeHtml5FrameCount({
698+
clipLengthSeconds: this.getLength(),
699+
htmlContent: this.asset.html,
700+
jsContent: this.asset.js,
701+
cssContent: this.asset.css,
702+
fps
703+
}).frameCount;
696704
// Seeking to the bake's frame time makes a still match the frame playback shows there.
697705
const idx = Math.min(Math.max(0, Math.floor(seconds * fps)), frameCount - 1);
698706
const blob = cached ? cached.pngs[idx] : await this.captureStillFrame(idx / fps, cacheKey, stale);

‎tests/html5-player.test.ts‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,16 @@ jest.mock("@canvas/players/player", () => ({
5858
jest.mock("@shotstack/shotstack-canvas", () => ({
5959
Html5AssetSchema: { safeParse: (data: unknown) => ({ success: true, data }) },
6060
composeHtml5IframeSrcdoc: jest.fn(() => "<html><body>Updated</body></html>"),
61-
computeHtml5FrameCount: ({ clipLengthSeconds, fps }: { clipLengthSeconds: number; fps: number }) => ({
61+
computeHtml5FrameCount: jest.fn(({ clipLengthSeconds, fps }: { clipLengthSeconds: number; fps: number }) => ({
6262
frameCount: Math.ceil(clipLengthSeconds * fps)
63-
}),
63+
})),
6464
findHtml5IngestUrls: jest.fn(() => []),
6565
html5ClipLongestEdge: jest.fn(() => 1280),
6666
inlineHtml5IngestAssets: jest.fn()
6767
}));
6868
const canvasLib = jest.requireMock("@shotstack/shotstack-canvas") as {
6969
composeHtml5IframeSrcdoc: jest.Mock;
70+
computeHtml5FrameCount: jest.Mock;
7071
findHtml5IngestUrls: jest.Mock;
7172
html5ClipLongestEdge: jest.Mock;
7273
inlineHtml5IngestAssets: jest.Mock;
@@ -280,6 +281,24 @@ it("ignores an older iframe timeout while newer content is loading", async () =>
280281
expect(harness.contentContainer.children).toEqual([expect.objectContaining({ kind: "loading" })]);
281282
});
282283

284+
// A page animated only by a style block in its markup has frames to capture, as it does in the render.
285+
it("counts frames from the page's markup when keying and capturing it", async () => {
286+
const { player, harness, iframe } = createPlayer();
287+
Object.assign(player, { getLength: () => 1 });
288+
jest.spyOn(harness, "seekHarness").mockImplementation(() => {
289+
throw new Error("stop after first seek");
290+
});
291+
292+
await harness.hashAsset();
293+
const capture = harness.captureFrames();
294+
await until(() => iframe.srcdoc === "<html><body>Updated</body></html>");
295+
iframe.dispatchEvent(new Event("load"));
296+
await expect(capture).rejects.toThrow("stop after first seek");
297+
298+
const counted = canvasLib.computeHtml5FrameCount.mock.calls.map(([args]) => args.htmlContent);
299+
expect(counted).toEqual(["<div>Background</div>", "<div>Background</div>"]);
300+
});
301+
283302
it("captures from a freshly loaded document rather than the live-edited one", async () => {
284303
const { player, harness, iframe } = createPlayer();
285304
Object.assign(player, { getLength: () => 1 });
@@ -647,6 +666,17 @@ it("renders a still from its own page without waiting for the clip's capture", a
647666
expect(loading.visible).toBe(true);
648667
});
649668

669+
it("counts frames from the page's markup when rendering a still", async () => {
670+
const { player, harness } = createStillPlayer();
671+
jest.spyOn(harness, "captureFrames").mockImplementation(() => new Promise(() => {}));
672+
harness.beginCapture();
673+
674+
await player.prepareStaticRender();
675+
player.endStaticRender();
676+
677+
expect(canvasLib.computeHtml5FrameCount).toHaveBeenCalledWith(expect.objectContaining({ htmlContent: "<div>Background</div>" }));
678+
});
679+
650680
it("takes a still from captured frames, in memory or cached, without rendering one", async () => {
651681
const { player, harness } = createStillPlayer();
652682
Object.assign(player, { getPlaybackTime: () => 1 });

0 commit comments

Comments
 (0)