@remotion/web-renderer 4.0.445 → 4.0.447
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create-scaffold.d.ts +4 -1
- package/dist/esm/index.mjs +257 -26
- package/dist/html-in-canvas.d.ts +43 -0
- package/dist/index.d.ts +2 -1
- package/dist/render-media-on-web.d.ts +1 -0
- package/dist/render-still-on-web.d.ts +4 -24
- package/dist/render-still-screenshot-task.d.ts +45 -0
- package/dist/take-screenshot.d.ts +11 -1
- package/package.json +7 -7
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { type ComponentType } from 'react';
|
|
2
2
|
import type { Codec, DelayRenderScope, LogLevel, TRenderAsset } from 'remotion';
|
|
3
3
|
import type { $ZodObject } from 'zod/v4/core';
|
|
4
|
+
import type { HtmlInCanvasContext } from './html-in-canvas';
|
|
4
5
|
import type { TimeUpdaterRef } from './update-time';
|
|
5
6
|
export type ErrorHolder = {
|
|
6
7
|
error: Error | null;
|
|
7
8
|
};
|
|
8
9
|
export declare function checkForError(errorHolder: ErrorHolder): void;
|
|
9
|
-
export declare function createScaffold<Props extends Record<string, unknown>>({ width, height, delayRenderTimeoutInMilliseconds, logLevel, resolvedProps, id, mediaCacheSizeInBytes, durationInFrames, fps, initialFrame, schema, Component, audioEnabled, videoEnabled, defaultCodec, defaultOutName }: {
|
|
10
|
+
export declare function createScaffold<Props extends Record<string, unknown>>({ width, height, delayRenderTimeoutInMilliseconds, logLevel, resolvedProps, id, mediaCacheSizeInBytes, durationInFrames, fps, initialFrame, schema, Component, audioEnabled, videoEnabled, defaultCodec, defaultOutName, allowHtmlInCanvas }: {
|
|
10
11
|
width: number;
|
|
11
12
|
height: number;
|
|
12
13
|
delayRenderTimeoutInMilliseconds: number;
|
|
@@ -23,6 +24,7 @@ export declare function createScaffold<Props extends Record<string, unknown>>({
|
|
|
23
24
|
videoEnabled: boolean;
|
|
24
25
|
defaultCodec: Codec | null;
|
|
25
26
|
defaultOutName: string | null;
|
|
27
|
+
allowHtmlInCanvas: boolean;
|
|
26
28
|
}): {
|
|
27
29
|
delayRenderScope: DelayRenderScope;
|
|
28
30
|
div: HTMLDivElement;
|
|
@@ -31,5 +33,6 @@ export declare function createScaffold<Props extends Record<string, unknown>>({
|
|
|
31
33
|
collectAssets: () => TRenderAsset[];
|
|
32
34
|
} | null>;
|
|
33
35
|
errorHolder: ErrorHolder;
|
|
36
|
+
htmlInCanvasContext: HtmlInCanvasContext | null;
|
|
34
37
|
[Symbol.dispose]: () => void;
|
|
35
38
|
};
|
package/dist/esm/index.mjs
CHANGED
|
@@ -584,7 +584,7 @@ var getEncodableAudioCodecs = async (container, options) => {
|
|
|
584
584
|
};
|
|
585
585
|
// src/render-media-on-web.tsx
|
|
586
586
|
import { BufferTarget, StreamTarget } from "mediabunny";
|
|
587
|
-
import { Internals as
|
|
587
|
+
import { Internals as Internals9 } from "remotion";
|
|
588
588
|
import { VERSION } from "remotion/version";
|
|
589
589
|
|
|
590
590
|
// src/add-sample.ts
|
|
@@ -823,6 +823,82 @@ import { flushSync as flushSync2 } from "react-dom";
|
|
|
823
823
|
import ReactDOM from "react-dom/client";
|
|
824
824
|
import { Internals as Internals3 } from "remotion";
|
|
825
825
|
|
|
826
|
+
// src/html-in-canvas.ts
|
|
827
|
+
var supportsNativeHtmlInCanvas = () => {
|
|
828
|
+
if (typeof document === "undefined") {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
const ctx = document.createElement("canvas").getContext("2d");
|
|
832
|
+
return typeof ctx?.drawElementImage === "function";
|
|
833
|
+
};
|
|
834
|
+
var setupHtmlInCanvas = ({
|
|
835
|
+
wrapper,
|
|
836
|
+
div,
|
|
837
|
+
width,
|
|
838
|
+
height
|
|
839
|
+
}) => {
|
|
840
|
+
if (!supportsNativeHtmlInCanvas()) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
const layoutCanvas = document.createElement("canvas");
|
|
844
|
+
layoutCanvas.layoutSubtree = true;
|
|
845
|
+
layoutCanvas.width = width;
|
|
846
|
+
layoutCanvas.height = height;
|
|
847
|
+
layoutCanvas.style.position = "absolute";
|
|
848
|
+
layoutCanvas.style.top = "0";
|
|
849
|
+
layoutCanvas.style.left = "0";
|
|
850
|
+
layoutCanvas.style.width = `${width}px`;
|
|
851
|
+
layoutCanvas.style.height = `${height}px`;
|
|
852
|
+
layoutCanvas.style.visibility = "visible";
|
|
853
|
+
const maybeCtx = layoutCanvas.getContext("2d");
|
|
854
|
+
if (!maybeCtx || typeof maybeCtx.drawElementImage !== "function") {
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
if (typeof layoutCanvas.requestPaint !== "function") {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
wrapper.removeChild(div);
|
|
861
|
+
layoutCanvas.appendChild(div);
|
|
862
|
+
wrapper.appendChild(layoutCanvas);
|
|
863
|
+
return { layoutCanvas, ctx: maybeCtx };
|
|
864
|
+
};
|
|
865
|
+
var waitForPaint = (layoutCanvas) => {
|
|
866
|
+
return new Promise((resolve) => {
|
|
867
|
+
layoutCanvas.addEventListener("paint", () => resolve(), { once: true });
|
|
868
|
+
layoutCanvas.requestPaint();
|
|
869
|
+
});
|
|
870
|
+
};
|
|
871
|
+
var drawWithHtmlInCanvas = async ({
|
|
872
|
+
htmlInCanvasContext,
|
|
873
|
+
element,
|
|
874
|
+
scaledWidth,
|
|
875
|
+
scaledHeight
|
|
876
|
+
}) => {
|
|
877
|
+
const { ctx, layoutCanvas } = htmlInCanvasContext;
|
|
878
|
+
await waitForPaint(layoutCanvas);
|
|
879
|
+
layoutCanvas.width = scaledWidth;
|
|
880
|
+
layoutCanvas.height = scaledHeight;
|
|
881
|
+
ctx.reset();
|
|
882
|
+
ctx.drawElementImage(element, 0, 0, scaledWidth, scaledHeight);
|
|
883
|
+
const offscreen = new OffscreenCanvas(scaledWidth, scaledHeight);
|
|
884
|
+
const offCtx = offscreen.getContext("2d");
|
|
885
|
+
if (!offCtx) {
|
|
886
|
+
throw new Error("Could not get offscreen context");
|
|
887
|
+
}
|
|
888
|
+
offCtx.drawImage(layoutCanvas, 0, 0);
|
|
889
|
+
return offCtx;
|
|
890
|
+
};
|
|
891
|
+
var teardownHtmlInCanvas = ({
|
|
892
|
+
htmlInCanvasContext,
|
|
893
|
+
wrapper,
|
|
894
|
+
div
|
|
895
|
+
}) => {
|
|
896
|
+
const { layoutCanvas } = htmlInCanvasContext;
|
|
897
|
+
layoutCanvas.removeChild(div);
|
|
898
|
+
wrapper.removeChild(layoutCanvas);
|
|
899
|
+
wrapper.appendChild(div);
|
|
900
|
+
};
|
|
901
|
+
|
|
826
902
|
// src/update-time.tsx
|
|
827
903
|
import { useImperativeHandle, useState } from "react";
|
|
828
904
|
import { flushSync } from "react-dom";
|
|
@@ -943,7 +1019,8 @@ function createScaffold({
|
|
|
943
1019
|
audioEnabled,
|
|
944
1020
|
videoEnabled,
|
|
945
1021
|
defaultCodec,
|
|
946
|
-
defaultOutName
|
|
1022
|
+
defaultOutName,
|
|
1023
|
+
allowHtmlInCanvas
|
|
947
1024
|
}) {
|
|
948
1025
|
if (!ReactDOM.createRoot) {
|
|
949
1026
|
throw new Error("@remotion/web-renderer requires React 18 or higher");
|
|
@@ -969,6 +1046,7 @@ function createScaffold({
|
|
|
969
1046
|
const cleanupCSS = Internals3.CSSUtils.injectCSS(Internals3.CSSUtils.makeDefaultPreviewCSS(`.${scaffoldClassName}`, "white"));
|
|
970
1047
|
wrapper.appendChild(div);
|
|
971
1048
|
document.body.appendChild(wrapper);
|
|
1049
|
+
const htmlInCanvasContext = allowHtmlInCanvas ? setupHtmlInCanvas({ wrapper, div, width, height }) : null;
|
|
972
1050
|
const errorHolder = { error: null };
|
|
973
1051
|
const root = ReactDOM.createRoot(div, {
|
|
974
1052
|
onUncaughtError: (err, errorInfo) => {
|
|
@@ -1059,8 +1137,12 @@ function createScaffold({
|
|
|
1059
1137
|
delayRenderScope,
|
|
1060
1138
|
div,
|
|
1061
1139
|
errorHolder,
|
|
1140
|
+
htmlInCanvasContext,
|
|
1062
1141
|
[Symbol.dispose]: () => {
|
|
1063
1142
|
root.unmount();
|
|
1143
|
+
if (htmlInCanvasContext) {
|
|
1144
|
+
teardownHtmlInCanvas({ htmlInCanvasContext, wrapper, div });
|
|
1145
|
+
}
|
|
1064
1146
|
div.remove();
|
|
1065
1147
|
wrapper.remove();
|
|
1066
1148
|
cleanupCSS();
|
|
@@ -1274,6 +1356,9 @@ var sendUsageEvent = async ({
|
|
|
1274
1356
|
});
|
|
1275
1357
|
};
|
|
1276
1358
|
|
|
1359
|
+
// src/take-screenshot.ts
|
|
1360
|
+
import { Internals as Internals8 } from "remotion";
|
|
1361
|
+
|
|
1277
1362
|
// src/tree-walker-cleanup-after-children.ts
|
|
1278
1363
|
var createTreeWalkerCleanupAfterChildren = (treeWalker) => {
|
|
1279
1364
|
const cleanupAfterChildren = [];
|
|
@@ -3918,10 +4003,32 @@ var createLayer = async ({
|
|
|
3918
4003
|
logLevel,
|
|
3919
4004
|
internalState,
|
|
3920
4005
|
onlyBackgroundClipText,
|
|
3921
|
-
cutout
|
|
4006
|
+
cutout,
|
|
4007
|
+
htmlInCanvasContext,
|
|
4008
|
+
onHtmlInCanvasLayerOutcome
|
|
3922
4009
|
}) => {
|
|
3923
4010
|
const scaledWidth = Math.ceil(cutout.width * scale);
|
|
3924
4011
|
const scaledHeight = Math.ceil(cutout.height * scale);
|
|
4012
|
+
if (!onlyBackgroundClipText && element instanceof HTMLElement && htmlInCanvasContext && onHtmlInCanvasLayerOutcome) {
|
|
4013
|
+
try {
|
|
4014
|
+
const offCtx = await drawWithHtmlInCanvas({
|
|
4015
|
+
htmlInCanvasContext,
|
|
4016
|
+
element,
|
|
4017
|
+
scaledWidth,
|
|
4018
|
+
scaledHeight
|
|
4019
|
+
});
|
|
4020
|
+
onHtmlInCanvasLayerOutcome({ native: true });
|
|
4021
|
+
return offCtx;
|
|
4022
|
+
} catch (err) {
|
|
4023
|
+
const detail = err instanceof Error ? err.message : JSON.stringify(err);
|
|
4024
|
+
onHtmlInCanvasLayerOutcome({
|
|
4025
|
+
native: false,
|
|
4026
|
+
reason: `drawElementImage failed (${detail}); falling back to the built-in DOM composer.`,
|
|
4027
|
+
shouldWarn: true
|
|
4028
|
+
});
|
|
4029
|
+
Internals8.Log.verbose({ logLevel, tag: "@remotion/web-renderer" }, "HTML-in-canvas capture failed, falling back to software compose", err);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
3925
4032
|
const canvas = new OffscreenCanvas(scaledWidth, scaledHeight);
|
|
3926
4033
|
const context = canvas.getContext("2d");
|
|
3927
4034
|
if (!context) {
|
|
@@ -4166,11 +4273,24 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4166
4273
|
licenseKey,
|
|
4167
4274
|
muted,
|
|
4168
4275
|
scale,
|
|
4169
|
-
isProduction
|
|
4276
|
+
isProduction,
|
|
4277
|
+
allowHtmlInCanvas
|
|
4170
4278
|
}) => {
|
|
4171
4279
|
let __stack2 = [];
|
|
4172
4280
|
try {
|
|
4173
4281
|
validateScale(scale);
|
|
4282
|
+
let htmlInCanvasLayerOutcomeReported = false;
|
|
4283
|
+
const onHtmlInCanvasLayerOutcome = (outcome) => {
|
|
4284
|
+
if (htmlInCanvasLayerOutcomeReported) {
|
|
4285
|
+
return;
|
|
4286
|
+
}
|
|
4287
|
+
htmlInCanvasLayerOutcomeReported = true;
|
|
4288
|
+
if (outcome.native) {
|
|
4289
|
+
Internals9.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, "Using Chromium experimental HTML-in-canvas (drawElementImage) for video frames. See https://github.com/WICG/html-in-canvas");
|
|
4290
|
+
} else if (outcome.shouldWarn) {
|
|
4291
|
+
Internals9.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, `Not using HTML-in-canvas: ${outcome.reason}`);
|
|
4292
|
+
}
|
|
4293
|
+
};
|
|
4174
4294
|
const outputTarget = userDesiredOutputTarget === null ? await canUseWebFsWriter() ? "web-fs" : "arraybuffer" : userDesiredOutputTarget;
|
|
4175
4295
|
if (outputTarget === "web-fs") {
|
|
4176
4296
|
await cleanupStaleOpfsFiles();
|
|
@@ -4201,11 +4321,11 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4201
4321
|
if (issue.severity === "error") {
|
|
4202
4322
|
return Promise.reject(new Error(issue.message));
|
|
4203
4323
|
}
|
|
4204
|
-
|
|
4324
|
+
Internals9.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, issue.message);
|
|
4205
4325
|
}
|
|
4206
4326
|
finalAudioCodec = audioResult.codec;
|
|
4207
4327
|
}
|
|
4208
|
-
const resolved = await
|
|
4328
|
+
const resolved = await Internals9.resolveVideoConfig({
|
|
4209
4329
|
calculateMetadata: composition.calculateMetadata ?? null,
|
|
4210
4330
|
signal: signal ?? new AbortController().signal,
|
|
4211
4331
|
defaultProps: composition.defaultProps ?? {},
|
|
@@ -4236,9 +4356,38 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4236
4356
|
videoEnabled,
|
|
4237
4357
|
initialFrame: 0,
|
|
4238
4358
|
defaultCodec: resolved.defaultCodec,
|
|
4239
|
-
defaultOutName: resolved.defaultOutName
|
|
4359
|
+
defaultOutName: resolved.defaultOutName,
|
|
4360
|
+
allowHtmlInCanvas
|
|
4240
4361
|
}), 0);
|
|
4241
|
-
const {
|
|
4362
|
+
const {
|
|
4363
|
+
delayRenderScope,
|
|
4364
|
+
div,
|
|
4365
|
+
timeUpdater,
|
|
4366
|
+
collectAssets,
|
|
4367
|
+
errorHolder,
|
|
4368
|
+
htmlInCanvasContext
|
|
4369
|
+
} = scaffold;
|
|
4370
|
+
if (allowHtmlInCanvas && !htmlInCanvasContext) {
|
|
4371
|
+
if (!supportsNativeHtmlInCanvas()) {
|
|
4372
|
+
onHtmlInCanvasLayerOutcome({
|
|
4373
|
+
native: false,
|
|
4374
|
+
reason: "This browser does not expose CanvasRenderingContext2D.prototype.drawElementImage. In Chromium, enable chrome://flags/#canvas-draw-element and use a version that ships the API.",
|
|
4375
|
+
shouldWarn: false
|
|
4376
|
+
});
|
|
4377
|
+
} else {
|
|
4378
|
+
onHtmlInCanvasLayerOutcome({
|
|
4379
|
+
native: false,
|
|
4380
|
+
reason: "drawElementImage is available but canvas.requestPaint() is missing. Use a Chromium version that ships requestPaint.",
|
|
4381
|
+
shouldWarn: true
|
|
4382
|
+
});
|
|
4383
|
+
}
|
|
4384
|
+
} else if (!allowHtmlInCanvas) {
|
|
4385
|
+
onHtmlInCanvasLayerOutcome({
|
|
4386
|
+
native: false,
|
|
4387
|
+
reason: "allowHtmlInCanvas is false; using the built-in DOM composer.",
|
|
4388
|
+
shouldWarn: false
|
|
4389
|
+
});
|
|
4390
|
+
}
|
|
4242
4391
|
const internalState = __using(__stack2, makeInternalState(), 0);
|
|
4243
4392
|
const keepalive = __using(__stack2, createBackgroundKeepalive({
|
|
4244
4393
|
fps: resolved.fps,
|
|
@@ -4351,7 +4500,9 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4351
4500
|
logLevel,
|
|
4352
4501
|
internalState,
|
|
4353
4502
|
onlyBackgroundClipText: false,
|
|
4354
|
-
cutout: new DOMRect(0, 0, resolved.width, resolved.height)
|
|
4503
|
+
cutout: new DOMRect(0, 0, resolved.width, resolved.height),
|
|
4504
|
+
htmlInCanvasContext,
|
|
4505
|
+
onHtmlInCanvasLayerOutcome: htmlInCanvasContext ? onHtmlInCanvasLayerOutcome : undefined
|
|
4355
4506
|
});
|
|
4356
4507
|
internalState.addCreateFrameTime(performance.now() - createFrameStart);
|
|
4357
4508
|
layerCanvas = layer.canvas;
|
|
@@ -4427,7 +4578,7 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4427
4578
|
videoSampleSource?.videoSampleSource.close();
|
|
4428
4579
|
audioSampleSource?.audioSampleSource.close();
|
|
4429
4580
|
await outputWithCleanup.output.finalize();
|
|
4430
|
-
|
|
4581
|
+
Internals9.Log.verbose({ logLevel, tag: "web-renderer" }, `Render timings: waitForReady=${internalState.getWaitForReadyTime().toFixed(2)}ms, createFrame=${internalState.getCreateFrameTime().toFixed(2)}ms, addSample=${internalState.getAddSampleTime().toFixed(2)}ms, audioMixing=${internalState.getAudioMixingTime().toFixed(2)}ms`);
|
|
4431
4582
|
if (webFsTarget) {
|
|
4432
4583
|
sendUsageEvent({
|
|
4433
4584
|
licenseKey: licenseKey ?? null,
|
|
@@ -4478,7 +4629,7 @@ var internalRenderMediaOnWeb = async ({
|
|
|
4478
4629
|
isStill: false,
|
|
4479
4630
|
isProduction: isProduction ?? true
|
|
4480
4631
|
}).catch((err2) => {
|
|
4481
|
-
|
|
4632
|
+
Internals9.Log.error({ logLevel: "error", tag: "web-renderer" }, "Failed to send usage event", err2);
|
|
4482
4633
|
});
|
|
4483
4634
|
}
|
|
4484
4635
|
throw err;
|
|
@@ -4515,33 +4666,79 @@ var renderMediaOnWeb = (options) => {
|
|
|
4515
4666
|
licenseKey: options.licenseKey ?? null,
|
|
4516
4667
|
muted: options.muted ?? false,
|
|
4517
4668
|
scale: options.scale ?? 1,
|
|
4518
|
-
isProduction: options.isProduction ?? true
|
|
4669
|
+
isProduction: options.isProduction ?? true,
|
|
4670
|
+
allowHtmlInCanvas: options.allowHtmlInCanvas ?? false
|
|
4519
4671
|
}));
|
|
4520
4672
|
return onlyOneRenderAtATimeQueue.ref;
|
|
4521
4673
|
};
|
|
4522
4674
|
// src/render-still-on-web.tsx
|
|
4523
4675
|
import {
|
|
4524
|
-
Internals as
|
|
4676
|
+
Internals as Internals10
|
|
4525
4677
|
} from "remotion";
|
|
4678
|
+
|
|
4679
|
+
// src/render-still-screenshot-task.ts
|
|
4680
|
+
var mimeTypeForFormat = (format) => {
|
|
4681
|
+
if (format === "jpeg") {
|
|
4682
|
+
return "image/jpeg";
|
|
4683
|
+
}
|
|
4684
|
+
if (format === "webp") {
|
|
4685
|
+
return "image/webp";
|
|
4686
|
+
}
|
|
4687
|
+
return "image/png";
|
|
4688
|
+
};
|
|
4689
|
+
var encodeCanvasToBlob = async (canvas, options) => {
|
|
4690
|
+
const format = options?.format ?? "png";
|
|
4691
|
+
const type = mimeTypeForFormat(format);
|
|
4692
|
+
if (format === "png") {
|
|
4693
|
+
return canvas.convertToBlob({ type });
|
|
4694
|
+
}
|
|
4695
|
+
return canvas.convertToBlob({
|
|
4696
|
+
type,
|
|
4697
|
+
quality: options?.quality
|
|
4698
|
+
});
|
|
4699
|
+
};
|
|
4700
|
+
var createRenderStillOnWebResult = ({
|
|
4701
|
+
canvas,
|
|
4702
|
+
internalState
|
|
4703
|
+
}) => {
|
|
4704
|
+
return {
|
|
4705
|
+
internalState,
|
|
4706
|
+
canvas: () => Promise.resolve(canvas),
|
|
4707
|
+
blob: (options) => encodeCanvasToBlob(canvas, options),
|
|
4708
|
+
url: async (options) => {
|
|
4709
|
+
const blob = await encodeCanvasToBlob(canvas, options);
|
|
4710
|
+
return URL.createObjectURL(blob);
|
|
4711
|
+
}
|
|
4712
|
+
};
|
|
4713
|
+
};
|
|
4714
|
+
|
|
4715
|
+
// src/render-still-on-web.tsx
|
|
4526
4716
|
async function internalRenderStillOnWeb({
|
|
4527
4717
|
frame,
|
|
4528
4718
|
delayRenderTimeoutInMilliseconds,
|
|
4529
4719
|
logLevel,
|
|
4530
4720
|
inputProps,
|
|
4531
4721
|
schema,
|
|
4532
|
-
imageFormat,
|
|
4533
4722
|
mediaCacheSizeInBytes,
|
|
4534
4723
|
composition,
|
|
4535
4724
|
signal,
|
|
4536
4725
|
onArtifact,
|
|
4537
4726
|
licenseKey,
|
|
4538
4727
|
scale,
|
|
4539
|
-
isProduction
|
|
4728
|
+
isProduction,
|
|
4729
|
+
allowHtmlInCanvas
|
|
4540
4730
|
}) {
|
|
4541
4731
|
let __stack = [];
|
|
4542
4732
|
try {
|
|
4543
4733
|
validateScale(scale);
|
|
4544
|
-
const
|
|
4734
|
+
const onHtmlInCanvasLayerOutcome = (outcome) => {
|
|
4735
|
+
if (outcome.native) {
|
|
4736
|
+
Internals10.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, "Using Chromium experimental HTML-in-canvas (drawElementImage) for this frame. Pixels may differ from the built-in DOM composer. Set allowHtmlInCanvas: false to force software rasterization. See https://github.com/WICG/html-in-canvas");
|
|
4737
|
+
} else if (outcome.shouldWarn) {
|
|
4738
|
+
Internals10.Log.warn({ logLevel, tag: "@remotion/web-renderer" }, `Not using HTML-in-canvas: ${outcome.reason}`);
|
|
4739
|
+
}
|
|
4740
|
+
};
|
|
4741
|
+
const resolved = await Internals10.resolveVideoConfig({
|
|
4545
4742
|
calculateMetadata: composition.calculateMetadata ?? null,
|
|
4546
4743
|
signal: signal ?? new AbortController().signal,
|
|
4547
4744
|
defaultProps: composition.defaultProps ?? {},
|
|
@@ -4572,9 +4769,37 @@ async function internalRenderStillOnWeb({
|
|
|
4572
4769
|
schema: schema ?? null,
|
|
4573
4770
|
initialFrame: frame,
|
|
4574
4771
|
defaultCodec: resolved.defaultCodec,
|
|
4575
|
-
defaultOutName: resolved.defaultOutName
|
|
4772
|
+
defaultOutName: resolved.defaultOutName,
|
|
4773
|
+
allowHtmlInCanvas
|
|
4576
4774
|
}), 0);
|
|
4577
|
-
const {
|
|
4775
|
+
const {
|
|
4776
|
+
delayRenderScope,
|
|
4777
|
+
div,
|
|
4778
|
+
collectAssets,
|
|
4779
|
+
errorHolder,
|
|
4780
|
+
htmlInCanvasContext
|
|
4781
|
+
} = scaffold;
|
|
4782
|
+
if (allowHtmlInCanvas && !htmlInCanvasContext) {
|
|
4783
|
+
if (!supportsNativeHtmlInCanvas()) {
|
|
4784
|
+
onHtmlInCanvasLayerOutcome({
|
|
4785
|
+
native: false,
|
|
4786
|
+
reason: "This browser does not expose CanvasRenderingContext2D.prototype.drawElementImage. In Chromium, enable chrome://flags/#canvas-draw-element and use a version that ships the API.",
|
|
4787
|
+
shouldWarn: false
|
|
4788
|
+
});
|
|
4789
|
+
} else {
|
|
4790
|
+
onHtmlInCanvasLayerOutcome({
|
|
4791
|
+
native: false,
|
|
4792
|
+
reason: "drawElementImage is available but canvas.requestPaint() is missing. Use a Chromium version that ships requestPaint.",
|
|
4793
|
+
shouldWarn: true
|
|
4794
|
+
});
|
|
4795
|
+
}
|
|
4796
|
+
} else if (!allowHtmlInCanvas) {
|
|
4797
|
+
onHtmlInCanvasLayerOutcome({
|
|
4798
|
+
native: false,
|
|
4799
|
+
reason: "allowHtmlInCanvas is false; using the built-in DOM composer.",
|
|
4800
|
+
shouldWarn: false
|
|
4801
|
+
});
|
|
4802
|
+
}
|
|
4578
4803
|
const artifactsHandler = handleArtifacts();
|
|
4579
4804
|
try {
|
|
4580
4805
|
if (signal?.aborted) {
|
|
@@ -4598,14 +4823,19 @@ async function internalRenderStillOnWeb({
|
|
|
4598
4823
|
logLevel,
|
|
4599
4824
|
internalState,
|
|
4600
4825
|
onlyBackgroundClipText: false,
|
|
4601
|
-
cutout: new DOMRect(0, 0, resolved.width, resolved.height)
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
type: `image/${imageFormat}`
|
|
4826
|
+
cutout: new DOMRect(0, 0, resolved.width, resolved.height),
|
|
4827
|
+
htmlInCanvasContext,
|
|
4828
|
+
onHtmlInCanvasLayerOutcome: htmlInCanvasContext ? onHtmlInCanvasLayerOutcome : undefined
|
|
4605
4829
|
});
|
|
4830
|
+
const { canvas } = capturedFrame;
|
|
4606
4831
|
const assets = collectAssets.current.collectAssets();
|
|
4607
4832
|
if (onArtifact) {
|
|
4608
|
-
await artifactsHandler.handle({
|
|
4833
|
+
await artifactsHandler.handle({
|
|
4834
|
+
imageData: canvas,
|
|
4835
|
+
frame,
|
|
4836
|
+
assets,
|
|
4837
|
+
onArtifact
|
|
4838
|
+
});
|
|
4609
4839
|
}
|
|
4610
4840
|
sendUsageEvent({
|
|
4611
4841
|
licenseKey: licenseKey ?? null,
|
|
@@ -4614,7 +4844,7 @@ async function internalRenderStillOnWeb({
|
|
|
4614
4844
|
isStill: true,
|
|
4615
4845
|
isProduction
|
|
4616
4846
|
});
|
|
4617
|
-
return {
|
|
4847
|
+
return createRenderStillOnWebResult({ canvas, internalState });
|
|
4618
4848
|
} catch (err) {
|
|
4619
4849
|
if (!signal?.aborted) {
|
|
4620
4850
|
sendUsageEvent({
|
|
@@ -4624,7 +4854,7 @@ async function internalRenderStillOnWeb({
|
|
|
4624
4854
|
isStill: true,
|
|
4625
4855
|
isProduction
|
|
4626
4856
|
}).catch((err2) => {
|
|
4627
|
-
|
|
4857
|
+
Internals10.Log.error({ logLevel: "error", tag: "web-renderer" }, "Failed to send usage event", err2);
|
|
4628
4858
|
});
|
|
4629
4859
|
}
|
|
4630
4860
|
throw err;
|
|
@@ -4646,7 +4876,8 @@ var renderStillOnWeb = (options) => {
|
|
|
4646
4876
|
onArtifact: options.onArtifact ?? null,
|
|
4647
4877
|
licenseKey: options.licenseKey ?? null,
|
|
4648
4878
|
scale: options.scale ?? 1,
|
|
4649
|
-
isProduction: options.isProduction ?? true
|
|
4879
|
+
isProduction: options.isProduction ?? true,
|
|
4880
|
+
allowHtmlInCanvas: options.allowHtmlInCanvas ?? false
|
|
4650
4881
|
}));
|
|
4651
4882
|
return onlyOneRenderAtATimeQueue.ref;
|
|
4652
4883
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
type Canvas2DWithDrawElement = CanvasRenderingContext2D & {
|
|
2
|
+
drawElementImage: (element: Element, dx: number, dy: number, dwidth: number, dheight: number) => DOMMatrix;
|
|
3
|
+
};
|
|
4
|
+
type HTMLCanvasWithLayoutSubtree = HTMLCanvasElement & {
|
|
5
|
+
layoutSubtree?: boolean;
|
|
6
|
+
requestPaint?: () => void;
|
|
7
|
+
};
|
|
8
|
+
export declare const supportsNativeHtmlInCanvas: () => boolean;
|
|
9
|
+
export type HtmlInCanvasContext = {
|
|
10
|
+
layoutCanvas: HTMLCanvasWithLayoutSubtree;
|
|
11
|
+
ctx: Canvas2DWithDrawElement;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Sets up a persistent layoutsubtree canvas that wraps the scaffold div.
|
|
15
|
+
* The div becomes a direct child of the canvas, which is required for drawElementImage.
|
|
16
|
+
* Must be called once before rendering begins; the canvas stays in the DOM for the
|
|
17
|
+
* lifetime of the render.
|
|
18
|
+
*/
|
|
19
|
+
export declare const setupHtmlInCanvas: ({ wrapper, div, width, height, }: {
|
|
20
|
+
wrapper: HTMLDivElement;
|
|
21
|
+
div: HTMLDivElement;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}) => HtmlInCanvasContext | null;
|
|
25
|
+
/**
|
|
26
|
+
* Triggers a fresh paint record via requestPaint(), waits for the paint event,
|
|
27
|
+
* then captures the element into an OffscreenCanvas using drawElementImage.
|
|
28
|
+
*
|
|
29
|
+
* The caller is responsible for ensuring the frame content is ready (via
|
|
30
|
+
* waitForReady) before calling this function.
|
|
31
|
+
*/
|
|
32
|
+
export declare const drawWithHtmlInCanvas: ({ htmlInCanvasContext, element, scaledWidth, scaledHeight, }: {
|
|
33
|
+
htmlInCanvasContext: HtmlInCanvasContext;
|
|
34
|
+
element: HTMLElement;
|
|
35
|
+
scaledWidth: number;
|
|
36
|
+
scaledHeight: number;
|
|
37
|
+
}) => Promise<OffscreenCanvasRenderingContext2D>;
|
|
38
|
+
export declare const teardownHtmlInCanvas: ({ htmlInCanvasContext, wrapper, div, }: {
|
|
39
|
+
htmlInCanvasContext: HtmlInCanvasContext;
|
|
40
|
+
wrapper: HTMLDivElement;
|
|
41
|
+
div: HTMLDivElement;
|
|
42
|
+
}) => void;
|
|
43
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -10,5 +10,6 @@ export type { WebRendererOutputTarget } from './output-target';
|
|
|
10
10
|
export { renderMediaOnWeb } from './render-media-on-web';
|
|
11
11
|
export type { RenderMediaOnWebOptions, RenderMediaOnWebProgress, RenderMediaOnWebProgressCallback, RenderMediaOnWebResult, WebRendererHardwareAcceleration, } from './render-media-on-web';
|
|
12
12
|
export { renderStillOnWeb } from './render-still-on-web';
|
|
13
|
-
export type { RenderStillOnWebImageFormat, RenderStillOnWebOptions, } from './render-still-on-web';
|
|
13
|
+
export type { RenderStillOnWebEncodeOptions, RenderStillOnWebImageFormat, RenderStillOnWebOptions, RenderStillOnWebResult, } from './render-still-on-web';
|
|
14
|
+
export type { HtmlInCanvasLayerOutcome } from './take-screenshot';
|
|
14
15
|
export type { OnFrameCallback } from './validate-video-frame';
|
|
@@ -65,6 +65,7 @@ type OptionalRenderMediaOnWebOptions<Schema extends $ZodObject> = {
|
|
|
65
65
|
isProduction: boolean;
|
|
66
66
|
muted: boolean;
|
|
67
67
|
scale: number;
|
|
68
|
+
allowHtmlInCanvas: boolean;
|
|
68
69
|
};
|
|
69
70
|
export type RenderMediaOnWebOptions<Schema extends $ZodObject, Props extends Record<string, unknown>> = MandatoryRenderMediaOnWebOptions<Schema, Props> & Partial<OptionalRenderMediaOnWebOptions<Schema>> & InputPropsIfHasProps<Schema, Props>;
|
|
70
71
|
export declare const renderMediaOnWeb: <Schema extends $ZodObject<Readonly<Readonly<{
|
|
@@ -3,10 +3,10 @@ import type { $ZodObject } from 'zod/v4/core';
|
|
|
3
3
|
import type { WebRendererOnArtifact } from './artifact';
|
|
4
4
|
import type { CompositionCalculateMetadataOrExplicit } from './props-if-has-props';
|
|
5
5
|
import type { InputPropsIfHasProps } from './render-media-on-web';
|
|
6
|
-
|
|
6
|
+
import { type RenderStillOnWebResult } from './render-still-screenshot-task';
|
|
7
|
+
export type { RenderStillOnWebEncodeOptions, RenderStillOnWebImageFormat, RenderStillOnWebResult, } from './render-still-screenshot-task';
|
|
7
8
|
type MandatoryRenderStillOnWebOptions<Schema extends $ZodObject, Props extends Record<string, unknown>> = {
|
|
8
9
|
frame: number;
|
|
9
|
-
imageFormat: RenderStillOnWebImageFormat;
|
|
10
10
|
} & {
|
|
11
11
|
composition: CompositionCalculateMetadataOrExplicit<Schema, Props>;
|
|
12
12
|
};
|
|
@@ -20,29 +20,9 @@ type OptionalRenderStillOnWebOptions<Schema extends $ZodObject> = {
|
|
|
20
20
|
licenseKey: string | null;
|
|
21
21
|
scale: number;
|
|
22
22
|
isProduction: boolean;
|
|
23
|
+
allowHtmlInCanvas: boolean;
|
|
23
24
|
};
|
|
24
25
|
export type RenderStillOnWebOptions<Schema extends $ZodObject, Props extends Record<string, unknown>> = MandatoryRenderStillOnWebOptions<Schema, Props> & Partial<OptionalRenderStillOnWebOptions<Schema>> & InputPropsIfHasProps<Schema, Props>;
|
|
25
26
|
export declare const renderStillOnWeb: <Schema extends $ZodObject<Readonly<Readonly<{
|
|
26
27
|
[k: string]: import("zod/v4/core").$ZodType<unknown, unknown, import("zod/v4/core").$ZodTypeInternals<unknown, unknown>>;
|
|
27
|
-
}>>, import("zod/v4/core").$ZodObjectConfig>, Props extends Record<string, unknown>>(options: RenderStillOnWebOptions<Schema, Props>) => Promise<
|
|
28
|
-
blob: Blob;
|
|
29
|
-
internalState: {
|
|
30
|
-
getDrawn3dPixels: () => number;
|
|
31
|
-
getPrecomposedTiles: () => number;
|
|
32
|
-
addPrecompose: ({ canvasWidth, canvasHeight, }: {
|
|
33
|
-
canvasWidth: number;
|
|
34
|
-
canvasHeight: number;
|
|
35
|
-
}) => void;
|
|
36
|
-
helperCanvasState: import("./internal-state").HelperCanvasState;
|
|
37
|
-
[Symbol.dispose]: () => void;
|
|
38
|
-
getWaitForReadyTime: () => number;
|
|
39
|
-
addWaitForReadyTime: (time: number) => void;
|
|
40
|
-
getAddSampleTime: () => number;
|
|
41
|
-
addAddSampleTime: (time: number) => void;
|
|
42
|
-
getCreateFrameTime: () => number;
|
|
43
|
-
addCreateFrameTime: (time: number) => void;
|
|
44
|
-
getAudioMixingTime: () => number;
|
|
45
|
-
addAudioMixingTime: (time: number) => void;
|
|
46
|
-
};
|
|
47
|
-
}>;
|
|
48
|
-
export {};
|
|
28
|
+
}>>, import("zod/v4/core").$ZodObjectConfig>, Props extends Record<string, unknown>>(options: RenderStillOnWebOptions<Schema, Props>) => Promise<RenderStillOnWebResult>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { InternalState } from './internal-state';
|
|
2
|
+
export type RenderStillOnWebImageFormat = 'png' | 'jpeg' | 'webp';
|
|
3
|
+
export type RenderStillOnWebEncodeOptions = {
|
|
4
|
+
format?: RenderStillOnWebImageFormat;
|
|
5
|
+
/**
|
|
6
|
+
* Encoder quality for `jpeg` and `webp`, between `0` and `1`.
|
|
7
|
+
* Ignored for `png`.
|
|
8
|
+
*/
|
|
9
|
+
quality?: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Outcome of `renderStillOnWeb()`. The frame is already rendered; use
|
|
13
|
+
* `canvas()`, `blob()`, or `url()` to read pixels or encode (same pattern as
|
|
14
|
+
* Renoun’s `screenshot()` task).
|
|
15
|
+
*/
|
|
16
|
+
export type RenderStillOnWebResult = {
|
|
17
|
+
internalState: InternalState;
|
|
18
|
+
canvas(): Promise<OffscreenCanvas>;
|
|
19
|
+
blob(options?: RenderStillOnWebEncodeOptions): Promise<Blob>;
|
|
20
|
+
/**
|
|
21
|
+
* Creates an object URL from an encoded blob. Call `URL.revokeObjectURL()` when done.
|
|
22
|
+
*/
|
|
23
|
+
url(options?: RenderStillOnWebEncodeOptions): Promise<string>;
|
|
24
|
+
};
|
|
25
|
+
export declare const createRenderStillOnWebResult: ({ canvas, internalState, }: {
|
|
26
|
+
canvas: OffscreenCanvas;
|
|
27
|
+
internalState: {
|
|
28
|
+
getDrawn3dPixels: () => number;
|
|
29
|
+
getPrecomposedTiles: () => number;
|
|
30
|
+
addPrecompose: ({ canvasWidth, canvasHeight, }: {
|
|
31
|
+
canvasWidth: number;
|
|
32
|
+
canvasHeight: number;
|
|
33
|
+
}) => void;
|
|
34
|
+
helperCanvasState: import("./internal-state").HelperCanvasState;
|
|
35
|
+
[Symbol.dispose]: () => void;
|
|
36
|
+
getWaitForReadyTime: () => number;
|
|
37
|
+
addWaitForReadyTime: (time: number) => void;
|
|
38
|
+
getAddSampleTime: () => number;
|
|
39
|
+
addAddSampleTime: (time: number) => void;
|
|
40
|
+
getCreateFrameTime: () => number;
|
|
41
|
+
addCreateFrameTime: (time: number) => void;
|
|
42
|
+
getAudioMixingTime: () => number;
|
|
43
|
+
addAudioMixingTime: (time: number) => void;
|
|
44
|
+
};
|
|
45
|
+
}) => RenderStillOnWebResult;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import type { HtmlInCanvasContext } from './html-in-canvas';
|
|
2
|
+
export type HtmlInCanvasLayerOutcome = {
|
|
3
|
+
native: true;
|
|
4
|
+
} | {
|
|
5
|
+
native: false;
|
|
6
|
+
reason: string;
|
|
7
|
+
shouldWarn: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const createLayer: ({ element, scale, logLevel, internalState, onlyBackgroundClipText, cutout, htmlInCanvasContext, onHtmlInCanvasLayerOutcome, }: {
|
|
2
10
|
element: HTMLElement | SVGElement;
|
|
3
11
|
scale: number;
|
|
4
12
|
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
|
@@ -22,4 +30,6 @@ export declare const createLayer: ({ element, scale, logLevel, internalState, on
|
|
|
22
30
|
};
|
|
23
31
|
onlyBackgroundClipText: boolean;
|
|
24
32
|
cutout: DOMRect;
|
|
33
|
+
htmlInCanvasContext?: HtmlInCanvasContext | null | undefined;
|
|
34
|
+
onHtmlInCanvasLayerOutcome?: ((outcome: HtmlInCanvasLayerOutcome) => void) | undefined;
|
|
25
35
|
}) => Promise<OffscreenCanvasRenderingContext2D>;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/web-renderer"
|
|
4
4
|
},
|
|
5
5
|
"name": "@remotion/web-renderer",
|
|
6
|
-
"version": "4.0.
|
|
6
|
+
"version": "4.0.447",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
"@mediabunny/mp3-encoder": "1.39.2",
|
|
23
23
|
"@mediabunny/aac-encoder": "1.39.2",
|
|
24
24
|
"@mediabunny/flac-encoder": "1.39.2",
|
|
25
|
-
"@remotion/licensing": "4.0.
|
|
26
|
-
"remotion": "4.0.
|
|
25
|
+
"@remotion/licensing": "4.0.447",
|
|
26
|
+
"remotion": "4.0.447",
|
|
27
27
|
"mediabunny": "1.39.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@react-three/fiber": "9.2.0",
|
|
31
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
32
|
-
"@remotion/player": "4.0.
|
|
33
|
-
"@remotion/media": "4.0.
|
|
34
|
-
"@remotion/three": "4.0.
|
|
31
|
+
"@remotion/eslint-config-internal": "4.0.447",
|
|
32
|
+
"@remotion/player": "4.0.447",
|
|
33
|
+
"@remotion/media": "4.0.447",
|
|
34
|
+
"@remotion/three": "4.0.447",
|
|
35
35
|
"@types/three": "0.170.0",
|
|
36
36
|
"@typescript/native-preview": "7.0.0-dev.20260217.1",
|
|
37
37
|
"@vitejs/plugin-react": "4.3.4",
|