diff --git a/src/components/canvas/players/html5-player.ts b/src/components/canvas/players/html5-player.ts
index b1158fe6..9158d52a 100644
--- a/src/components/canvas/players/html5-player.ts
+++ b/src/components/canvas/players/html5-player.ts
@@ -291,9 +291,10 @@ export class Html5Player extends Player {
// Captured frames are SVG images, which can't load remote files, so ingest files are inlined as data URIs.
// Callers await this only when the asset has ingest URLs, so other assets compose without yielding.
- private async composeWithIngestFiles(asset: Html5Asset): Promise {
+ // Every frame decodes its inlined images again, so a scaled bake inlines images sized for its scale.
+ private async composeWithIngestFiles(asset: Html5Asset, scale: number = 1): Promise {
const longestEdge = html5ClipLongestEdge(this.clipConfiguration, this.edit.getResolvedEdit()?.output);
- return composeHtml5IframeSrcdoc(await inlineHtml5IngestAssets(asset, longestEdge));
+ return composeHtml5IframeSrcdoc(await inlineHtml5IngestAssets(asset, longestEdge * scale));
}
private async mountIframe(asset: Html5Asset): Promise {
@@ -442,7 +443,7 @@ export class Html5Player extends Player {
// An earlier capture may have seeked this iframe, and a seeked animation can leave state
// a fresh page lacks (GSAP writes start values inline once a tween has
// rendered). The backend captures from a fresh page, so capture does too.
- if (!(await this.loadFreshPage(this.iframe, stale))) return null;
+ if (!(await this.loadFreshPage(this.iframe, stale, scale))) return null;
const { frameCount } = computeHtml5FrameCount({
clipLengthSeconds: this.getLength(),
@@ -535,8 +536,9 @@ export class Html5Player extends Player {
return true;
}
- private async loadFreshPage(iframe: HTMLIFrameElement, stale: () => boolean): Promise {
- const srcdoc = findHtml5IngestUrls(this.asset).length > 0 ? await this.composeWithIngestFiles(this.asset) : composeHtml5IframeSrcdoc(this.asset);
+ private async loadFreshPage(iframe: HTMLIFrameElement, stale: () => boolean, scale: number = 1): Promise {
+ const srcdoc =
+ findHtml5IngestUrls(this.asset).length > 0 ? await this.composeWithIngestFiles(this.asset, scale) : composeHtml5IframeSrcdoc(this.asset);
if (stale()) return false;
iframe.srcdoc = srcdoc; // eslint-disable-line no-param-reassign -- reloading the iframe it is given is this helper's job
await waitForIframeLoad(iframe, undefined, true);
diff --git a/tests/html5-player.test.ts b/tests/html5-player.test.ts
index 94696d51..16857fa6 100644
--- a/tests/html5-player.test.ts
+++ b/tests/html5-player.test.ts
@@ -938,6 +938,20 @@ it("keys each bake by its scale", async () => {
expect(jest.requireMock("@canvas/players/html5-cache").html5CachePut).toHaveBeenCalledWith("original@0.5", expect.anything());
});
+it("inlines ingest files sized for the bake's scale, and for full size in a still", async () => {
+ const { player, harness } = createStillPlayer();
+ shownAt(harness, 0.46);
+ canvasLib.findHtml5IngestUrls.mockReturnValue([INGEST_URL]);
+ canvasLib.inlineHtml5IngestAssets.mockImplementation(async asset => asset);
+
+ await harness.runCapture();
+ expect(canvasLib.inlineHtml5IngestAssets).toHaveBeenLastCalledWith(expect.objectContaining({ type: "html5" }), 640);
+
+ await player.prepareStaticRender();
+ player.endStaticRender();
+ expect(canvasLib.inlineHtml5IngestAssets).toHaveBeenLastCalledWith(expect.objectContaining({ type: "html5" }), 1280);
+});
+
it("keeps stills and captures at full resolution while a scaled bake exists", async () => {
const { player, harness } = createStillPlayer();
Object.assign(harness, { capturedFrames: [new Blob(["baked 0"])], capturedHash: "original@0.5", capturedScale: 0.5 });