remotion 4.0.452 → 4.0.453

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.
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import type { EffectsProp } from './canvas-effects/effect-types.js';
3
+ import type { AbsoluteFillLayout, LayoutAndStyle, SequenceProps } from './Sequence.js';
4
+ export type HtmlInCanvasComposeParams = {
5
+ readonly source: CanvasImageSource;
6
+ readonly target: HTMLCanvasElement;
7
+ readonly frame: number;
8
+ readonly width: number;
9
+ readonly height: number;
10
+ readonly pixelRatio: number;
11
+ };
12
+ export declare const isHtmlInCanvasSupported: () => boolean;
13
+ export type HtmlInCanvasProps = Omit<SequenceProps, 'children' | 'durationInFrames' | keyof LayoutAndStyle> & Omit<AbsoluteFillLayout, 'layout'> & {
14
+ readonly durationInFrames?: number;
15
+ readonly width: number;
16
+ readonly height: number;
17
+ readonly effects?: EffectsProp;
18
+ readonly children: React.ReactNode;
19
+ readonly pixelRatio?: number;
20
+ readonly onCompose?: (params: HtmlInCanvasComposeParams) => void | Promise<void>;
21
+ };
22
+ export declare const HtmlInCanvas: React.ComponentType<Omit<SequenceProps, "children" | "durationInFrames" | "layout"> & Omit<AbsoluteFillLayout, "layout"> & {
23
+ readonly durationInFrames?: number | undefined;
24
+ readonly width: number;
25
+ readonly height: number;
26
+ readonly effects?: EffectsProp | undefined;
27
+ readonly children: React.ReactNode;
28
+ readonly pixelRatio?: number | undefined;
29
+ readonly onCompose?: ((params: HtmlInCanvasComposeParams) => void | Promise<void>) | undefined;
30
+ }> & {
31
+ readonly isHtmlInCanvasSupported: () => boolean;
32
+ };
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HtmlInCanvas = exports.isHtmlInCanvasSupported = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const run_effect_chain_js_1 = require("./canvas-effects/run-effect-chain.js");
7
+ const use_effect_chain_state_js_1 = require("./canvas-effects/use-effect-chain-state.js");
8
+ const enable_sequence_stack_traces_js_1 = require("./enable-sequence-stack-traces.js");
9
+ const Sequence_js_1 = require("./Sequence.js");
10
+ const use_current_frame_js_1 = require("./use-current-frame.js");
11
+ const use_delay_render_js_1 = require("./use-delay-render.js");
12
+ const use_video_config_js_1 = require("./use-video-config.js");
13
+ const wrap_in_schema_js_1 = require("./wrap-in-schema.js");
14
+ const isHtmlInCanvasSupported = () => {
15
+ if (typeof document === 'undefined') {
16
+ return false;
17
+ }
18
+ const canvas = document.createElement('canvas');
19
+ const ctx = canvas.getContext('2d');
20
+ return (typeof (ctx === null || ctx === void 0 ? void 0 : ctx.drawElementImage) === 'function' &&
21
+ typeof canvas.requestPaint === 'function');
22
+ };
23
+ exports.isHtmlInCanvasSupported = isHtmlInCanvasSupported;
24
+ const htmlInCanvasSchema = {
25
+ 'style.translate': {
26
+ type: 'translate',
27
+ step: 1,
28
+ default: '0px 0px',
29
+ description: 'Position',
30
+ },
31
+ 'style.scale': {
32
+ type: 'number',
33
+ min: 0.05,
34
+ max: 100,
35
+ step: 0.01,
36
+ default: 1,
37
+ description: 'Scale',
38
+ },
39
+ 'style.rotate': {
40
+ type: 'rotation',
41
+ step: 1,
42
+ default: '0deg',
43
+ description: 'Rotation',
44
+ },
45
+ 'style.opacity': {
46
+ type: 'number',
47
+ min: 0,
48
+ max: 1,
49
+ step: 0.01,
50
+ default: 1,
51
+ description: 'Opacity',
52
+ },
53
+ };
54
+ const HtmlInCanvasInner = ({ width, height, effects = [], children, style, pixelRatio = 1, onCompose, controls, durationInFrames, ...sequenceProps }) => {
55
+ const { durationInFrames: videoDuration } = (0, use_video_config_js_1.useVideoConfig)();
56
+ const resolvedDuration = durationInFrames !== null && durationInFrames !== void 0 ? durationInFrames : videoDuration;
57
+ const frame = (0, use_current_frame_js_1.useCurrentFrame)();
58
+ const { delayRender, continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)();
59
+ const canvasRef = (0, react_1.useRef)(null);
60
+ const sceneRef = (0, react_1.useRef)(null);
61
+ const chainState = (0, use_effect_chain_state_js_1.useEffectChainState)(width, height);
62
+ // Refs so the paint handler always reads fresh values.
63
+ const effectsRef = (0, react_1.useRef)(effects);
64
+ effectsRef.current = effects;
65
+ const frameRef = (0, react_1.useRef)(frame);
66
+ frameRef.current = frame;
67
+ const pixelRatioRef = (0, react_1.useRef)(pixelRatio);
68
+ pixelRatioRef.current = pixelRatio;
69
+ const widthRef = (0, react_1.useRef)(width);
70
+ widthRef.current = width;
71
+ const heightRef = (0, react_1.useRef)(height);
72
+ heightRef.current = height;
73
+ // Track the current delayRender handle so the paint handler can settle it.
74
+ const pendingHandleRef = (0, react_1.useRef)(null);
75
+ const onPaint = (0, react_1.useCallback)(() => {
76
+ const canvas = canvasRef.current;
77
+ const sceneEl = sceneRef.current;
78
+ const handle = pendingHandleRef.current;
79
+ if (!canvas || !sceneEl || !chainState || handle === null) {
80
+ return;
81
+ }
82
+ try {
83
+ const ctx = canvas.getContext('2d');
84
+ if (!ctx) {
85
+ throw new Error('Failed to acquire 2D context for <HtmlInCanvas> canvas');
86
+ }
87
+ const w = widthRef.current;
88
+ const h = heightRef.current;
89
+ // Layout-subtree children are not shown on-screen until rasterized here;
90
+ // effects then read from and write back to this same surface (via pool).
91
+ ctx.reset();
92
+ ctx.drawElementImage(sceneEl, 0, 0, w, h);
93
+ const capturedHandle = handle;
94
+ pendingHandleRef.current = null;
95
+ (0, run_effect_chain_js_1.runEffectChain)({
96
+ state: chainState,
97
+ source: canvas,
98
+ effects: effectsRef.current,
99
+ output: canvas,
100
+ frame: frameRef.current,
101
+ width: w,
102
+ height: h,
103
+ pixelRatio: pixelRatioRef.current,
104
+ })
105
+ .then((completed) => {
106
+ if (completed) {
107
+ continueRender(capturedHandle);
108
+ }
109
+ })
110
+ .catch((err) => {
111
+ cancelRender(err);
112
+ });
113
+ }
114
+ catch (error) {
115
+ cancelRender(error);
116
+ }
117
+ }, [chainState, continueRender, cancelRender]);
118
+ // Set up layoutSubtree and persistent paint listener.
119
+ (0, react_1.useEffect)(() => {
120
+ if (!(0, exports.isHtmlInCanvasSupported)()) {
121
+ cancelRender(new Error('HTML in Canvas is not supported. Open this page in Chrome Canary with chrome://flags/#canvas-draw-element enabled.'));
122
+ return;
123
+ }
124
+ const canvas = canvasRef.current;
125
+ if (!canvas) {
126
+ return;
127
+ }
128
+ canvas.layoutSubtree = true;
129
+ canvas.addEventListener('paint', onPaint);
130
+ return () => {
131
+ canvas.removeEventListener('paint', onPaint);
132
+ };
133
+ }, [onPaint, cancelRender]);
134
+ // On each frame change: block the renderer and request a paint.
135
+ (0, react_1.useEffect)(() => {
136
+ const handle = delayRender(`HtmlInCanvas (frame ${frame})`);
137
+ // Continue a stale handle from a previous frame that never got a paint.
138
+ if (pendingHandleRef.current !== null) {
139
+ continueRender(pendingHandleRef.current);
140
+ }
141
+ pendingHandleRef.current = handle;
142
+ const canvas = canvasRef.current;
143
+ canvas === null || canvas === void 0 ? void 0 : canvas.requestPaint();
144
+ return () => {
145
+ if (pendingHandleRef.current === handle) {
146
+ continueRender(handle);
147
+ pendingHandleRef.current = null;
148
+ }
149
+ };
150
+ }, [frame, delayRender, continueRender]);
151
+ const outerStyle = (0, react_1.useMemo)(() => {
152
+ return {
153
+ position: 'absolute',
154
+ inset: 0,
155
+ width: width + 'px',
156
+ height: height + 'px',
157
+ };
158
+ }, [width, height]);
159
+ const innerStyle = (0, react_1.useMemo)(() => {
160
+ return {
161
+ width,
162
+ height,
163
+ };
164
+ }, [width, height]);
165
+ return (jsx_runtime_1.jsx(Sequence_js_1.Sequence, { durationInFrames: resolvedDuration, name: "<HtmlInCanvas>", controls: controls, ...sequenceProps, style: style, children: jsx_runtime_1.jsx("canvas", { ref: canvasRef, width: width, height: height, style: outerStyle, children: jsx_runtime_1.jsx("div", { ref: sceneRef, style: innerStyle, children: children }) }) }));
166
+ };
167
+ const HtmlInCanvasWrapped = (0, wrap_in_schema_js_1.wrapInSchema)(HtmlInCanvasInner, htmlInCanvasSchema);
168
+ exports.HtmlInCanvas = Object.assign(HtmlInCanvasWrapped, {
169
+ isHtmlInCanvasSupported: exports.isHtmlInCanvasSupported,
170
+ });
171
+ exports.HtmlInCanvas.displayName = 'HtmlInCanvas';
172
+ (0, enable_sequence_stack_traces_js_1.addSequenceStackTraces)(exports.HtmlInCanvas);
@@ -3,16 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Solid = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
- const use_effect_chain_js_1 = require("./use-effect-chain.js");
7
- // `<Solid>` is the simplest source component: it produces a flat-color image
8
- // to feed into the canvas-effect chain. Combined with an effect chain it can
9
- // generate procedural content (gradients, blurs, noise patches, ...) without
10
- // needing to mount a DOM source.
11
- //
12
- // Internally the source is a 1x1 canvas filled with `color`; the chain runtime
13
- // scales it to the chain's `width`/`height` when the next stage samples it,
14
- // which is correct because every pixel is the same color.
15
- const Solid = ({ color, width, height, effects = [], className, style, pixelRatio, }) => {
6
+ const use_current_frame_js_1 = require("../use-current-frame.js");
7
+ const use_delay_render_js_1 = require("../use-delay-render.js");
8
+ const run_effect_chain_js_1 = require("./run-effect-chain.js");
9
+ const use_effect_chain_state_js_1 = require("./use-effect-chain-state.js");
10
+ const Solid = ({ color, width, height, effects = [], className, style, pixelRatio = 1, }) => {
11
+ const frame = (0, use_current_frame_js_1.useCurrentFrame)();
12
+ const { delayRender, continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)();
16
13
  const [outputCanvas, setOutputCanvas] = (0, react_1.useState)(null);
17
14
  const sourceCanvas = (0, react_1.useMemo)(() => {
18
15
  if (typeof document === 'undefined') {
@@ -23,27 +20,55 @@ const Solid = ({ color, width, height, effects = [], className, style, pixelRati
23
20
  canvas.height = 1;
24
21
  return canvas;
25
22
  }, []);
26
- const source = (0, react_1.useCallback)(() => {
27
- if (!sourceCanvas) {
28
- return null;
23
+ const chainState = (0, use_effect_chain_state_js_1.useEffectChainState)(width, height);
24
+ // Fill source and run effect chain on every frame / color change.
25
+ (0, react_1.useEffect)(() => {
26
+ if (!outputCanvas || !sourceCanvas || !chainState) {
27
+ return;
29
28
  }
29
+ const handle = delayRender(`Solid effect chain (frame ${frame})`);
30
30
  const ctx = sourceCanvas.getContext('2d', { colorSpace: 'srgb' });
31
31
  if (!ctx) {
32
- throw new Error('Failed to acquire 2D context for <Solid> source');
32
+ cancelRender(new Error('Failed to acquire 2D context for <Solid> source'));
33
+ return;
33
34
  }
34
35
  ctx.fillStyle = color;
35
36
  ctx.fillRect(0, 0, 1, 1);
36
- return sourceCanvas;
37
- }, [color, sourceCanvas]);
38
- (0, use_effect_chain_js_1.useEffectChain)({
39
- source,
37
+ (0, run_effect_chain_js_1.runEffectChain)({
38
+ state: chainState,
39
+ source: sourceCanvas,
40
+ effects,
41
+ output: outputCanvas,
42
+ frame,
43
+ width,
44
+ height,
45
+ pixelRatio,
46
+ })
47
+ .then((completed) => {
48
+ if (completed) {
49
+ continueRender(handle);
50
+ }
51
+ })
52
+ .catch((err) => {
53
+ cancelRender(err);
54
+ });
55
+ return () => {
56
+ continueRender(handle);
57
+ };
58
+ }, [
59
+ frame,
60
+ color,
40
61
  effects,
62
+ outputCanvas,
63
+ sourceCanvas,
64
+ chainState,
41
65
  width,
42
66
  height,
43
67
  pixelRatio,
44
- output: outputCanvas,
45
- sourceDeps: [color],
46
- });
68
+ delayRender,
69
+ continueRender,
70
+ cancelRender,
71
+ ]);
47
72
  return (jsx_runtime_1.jsx("canvas", { ref: setOutputCanvas, width: width, height: height, className: className, style: style }));
48
73
  };
49
74
  exports.Solid = Solid;
@@ -4,8 +4,10 @@ export declare class CanvasPool {
4
4
  private readonly width;
5
5
  private readonly height;
6
6
  private readonly pairs;
7
+ private readonly lostContexts;
7
8
  constructor(width: number, height: number);
8
9
  getPair(backend: Backend): CanvasPair;
10
+ assertContextNotLost(canvas: HTMLCanvasElement): void;
9
11
  private allocateCanvas;
10
12
  }
11
13
  export {};
@@ -12,6 +12,7 @@ class CanvasPool {
12
12
  width;
13
13
  height;
14
14
  pairs = new Map();
15
+ lostContexts = new Set();
15
16
  constructor(width, height) {
16
17
  this.width = width;
17
18
  this.height = height;
@@ -28,6 +29,13 @@ class CanvasPool {
28
29
  this.pairs.set(backend, pair);
29
30
  return pair;
30
31
  }
32
+ assertContextNotLost(canvas) {
33
+ if (this.lostContexts.has(canvas)) {
34
+ throw new Error('WebGL context was lost during canvas effect rendering. ' +
35
+ 'This typically happens in headless or memory-constrained environments (e.g. Remotion Lambda). ' +
36
+ 'Try reducing concurrency or increasing the Lambda function memory.');
37
+ }
38
+ }
31
39
  allocateCanvas(backend) {
32
40
  const canvas = document.createElement('canvas');
33
41
  canvas.width = this.width;
@@ -51,6 +59,13 @@ class CanvasPool {
51
59
  if (!ctx) {
52
60
  throw new Error('Failed to acquire WebGL2 context for canvas effect');
53
61
  }
62
+ canvas.addEventListener('webglcontextlost', (e) => {
63
+ e.preventDefault();
64
+ this.lostContexts.add(canvas);
65
+ });
66
+ canvas.addEventListener('webglcontextrestored', () => {
67
+ this.lostContexts.delete(canvas);
68
+ });
54
69
  ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
55
70
  return canvas;
56
71
  }
@@ -14,8 +14,11 @@ exports.defineEffect = defineEffect;
14
14
  // erased to `unknown` in the descriptor because consumers (the chain runtime,
15
15
  // other effects in the chain) treat state as opaque; only the definition's
16
16
  // own `setup`, `apply`, and `cleanup` functions ever see the real shape.
17
- const createDescriptor = (definition, params) => ({
18
- definition: definition,
19
- params,
20
- });
17
+ const createDescriptor = (definition, params) => {
18
+ return {
19
+ definition: definition,
20
+ params,
21
+ stack: new Error().stack,
22
+ };
23
+ };
21
24
  exports.createDescriptor = createDescriptor;
@@ -1,3 +1,4 @@
1
+ import type { SequenceSchema } from '../internals';
1
2
  export type Backend = '2d' | 'webgl2' | 'webgpu';
2
3
  type AnyGpuDevice = unknown;
3
4
  export type EffectApplyParams<P, S> = {
@@ -13,13 +14,18 @@ export type EffectApplyParams<P, S> = {
13
14
  };
14
15
  export type EffectDefinition<P, S = unknown> = {
15
16
  readonly type: string;
17
+ readonly label: string;
16
18
  readonly backend: Backend;
17
19
  readonly setup: (target: HTMLCanvasElement) => S | Promise<S>;
18
20
  readonly apply: (params: EffectApplyParams<P, S>) => void | Promise<void>;
19
- readonly cleanup?: (state: S) => void;
21
+ readonly cleanup: (state: S) => void;
22
+ readonly schema: SequenceSchema | null;
20
23
  };
21
- export type EffectDescriptor<P = unknown> = {
24
+ export type EffectDefinitionAndStack<P = unknown> = {
22
25
  readonly definition: EffectDefinition<P, unknown>;
26
+ readonly stack: string;
27
+ };
28
+ export type EffectDescriptor<P = unknown> = EffectDefinitionAndStack<P> & {
23
29
  readonly params: P;
24
30
  };
25
31
  export type EffectsProp = ReadonlyArray<EffectDescriptor<any> | ReadonlyArray<EffectDescriptor<any>>>;
@@ -1,6 +1,4 @@
1
1
  export { createDescriptor, defineEffect } from './define-effect.js';
2
- export type { Backend, EffectApplyParams, EffectDefinition, EffectDescriptor, EffectsProp, } from './effect-types.js';
3
- export { HtmlInCanvas } from './HtmlInCanvas.js';
4
- export type { HtmlInCanvasProps } from './HtmlInCanvas.js';
2
+ export type { Backend, EffectApplyParams, EffectDefinition, EffectDefinitionAndStack, EffectDescriptor, EffectsProp, } from './effect-types.js';
5
3
  export { Solid } from './Solid.js';
6
4
  export type { SolidProps } from './Solid.js';
@@ -1,10 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Solid = exports.HtmlInCanvas = exports.defineEffect = exports.createDescriptor = void 0;
3
+ exports.Solid = exports.defineEffect = exports.createDescriptor = void 0;
4
4
  const define_effect_js_1 = require("./define-effect.js");
5
5
  Object.defineProperty(exports, "createDescriptor", { enumerable: true, get: function () { return define_effect_js_1.createDescriptor; } });
6
6
  Object.defineProperty(exports, "defineEffect", { enumerable: true, get: function () { return define_effect_js_1.defineEffect; } });
7
- const HtmlInCanvas_js_1 = require("./HtmlInCanvas.js");
8
- Object.defineProperty(exports, "HtmlInCanvas", { enumerable: true, get: function () { return HtmlInCanvas_js_1.HtmlInCanvas; } });
9
7
  const Solid_js_1 = require("./Solid.js");
10
8
  Object.defineProperty(exports, "Solid", { enumerable: true, get: function () { return Solid_js_1.Solid; } });
@@ -0,0 +1,24 @@
1
+ import { CanvasPool } from './canvas-pool.js';
2
+ import type { EffectDefinition, EffectsProp } from './effect-types.js';
3
+ export type EffectChainState = {
4
+ pool: CanvasPool;
5
+ setupCache: WeakMap<EffectDefinition<unknown, unknown>, Promise<unknown>>;
6
+ cleanupRegistry: Array<{
7
+ definition: EffectDefinition<unknown, unknown>;
8
+ statePromise: Promise<unknown>;
9
+ }>;
10
+ currentRunId: number;
11
+ };
12
+ export declare const createEffectChainState: (width: number, height: number) => EffectChainState;
13
+ export declare const cleanupEffectChainState: (state: EffectChainState) => void;
14
+ export type RunEffectChainOptions = {
15
+ readonly state: EffectChainState;
16
+ readonly source: CanvasImageSource;
17
+ readonly effects: EffectsProp;
18
+ readonly output: HTMLCanvasElement;
19
+ readonly frame: number;
20
+ readonly width: number;
21
+ readonly height: number;
22
+ readonly pixelRatio: number;
23
+ };
24
+ export declare const runEffectChain: ({ state, source, effects, output, frame, width, height, pixelRatio, }: RunEffectChainOptions) => Promise<boolean>;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runEffectChain = exports.cleanupEffectChainState = exports.createEffectChainState = void 0;
4
+ const canvas_pool_js_1 = require("./canvas-pool.js");
5
+ const effect_internals_js_1 = require("./effect-internals.js");
6
+ const gpu_device_js_1 = require("./gpu-device.js");
7
+ const createEffectChainState = (width, height) => ({
8
+ pool: new canvas_pool_js_1.CanvasPool(width, height),
9
+ setupCache: new WeakMap(),
10
+ cleanupRegistry: [],
11
+ currentRunId: 0,
12
+ });
13
+ exports.createEffectChainState = createEffectChainState;
14
+ const cleanupEffectChainState = (state) => {
15
+ state.currentRunId++;
16
+ for (const entry of state.cleanupRegistry) {
17
+ entry.statePromise.then((s) => {
18
+ var _a, _b;
19
+ return (_b = (_a = entry.definition).cleanup) === null || _b === void 0 ? void 0 : _b.call(_a, s);
20
+ }, () => undefined);
21
+ }
22
+ };
23
+ exports.cleanupEffectChainState = cleanupEffectChainState;
24
+ const ensureSetup = (state, def, target) => {
25
+ const cached = state.setupCache.get(def);
26
+ if (cached) {
27
+ return cached;
28
+ }
29
+ const promise = Promise.resolve(def.setup(target));
30
+ state.setupCache.set(def, promise);
31
+ state.cleanupRegistry.push({ definition: def, statePromise: promise });
32
+ return promise;
33
+ };
34
+ // Runs the effect pipeline imperatively. Returns `true` if the pipeline
35
+ // completed and wrote to `output`, `false` if it was superseded by a newer
36
+ // run (caller should not act on a stale result).
37
+ const runEffectChain = async ({ state, source, effects, output, frame, width, height, pixelRatio, }) => {
38
+ var _a;
39
+ var _b;
40
+ const runId = ++state.currentRunId;
41
+ const isCancelled = () => state.currentRunId !== runId;
42
+ const flattened = (0, effect_internals_js_1.flattenEffects)(effects);
43
+ const runs = (0, effect_internals_js_1.groupByBackend)(flattened);
44
+ let currentImage = source;
45
+ let lastTarget = null;
46
+ if (runs.length === 0) {
47
+ // In-place pipeline (e.g. <HtmlInCanvas>: drawElementImage into the same
48
+ // surface, no further effects) — the bitmap is already on `output`.
49
+ if (source === output) {
50
+ return true;
51
+ }
52
+ const ctx = output.getContext('2d');
53
+ if (!ctx) {
54
+ throw new Error('Failed to acquire 2D context for output canvas');
55
+ }
56
+ ctx.clearRect(0, 0, width, height);
57
+ ctx.drawImage(currentImage, 0, 0, width, height);
58
+ return true;
59
+ }
60
+ let needsGpuDevice = false;
61
+ for (const run of runs) {
62
+ if (run.backend === 'webgpu') {
63
+ needsGpuDevice = true;
64
+ break;
65
+ }
66
+ }
67
+ const gpuDevice = needsGpuDevice ? await (0, gpu_device_js_1.getGpuDevice)() : null;
68
+ if (isCancelled()) {
69
+ return false;
70
+ }
71
+ for (let runIndex = 0; runIndex < runs.length; runIndex++) {
72
+ const run = runs[runIndex];
73
+ const [a, b] = state.pool.getPair(run.backend);
74
+ let dst = a;
75
+ for (const eff of run.effects) {
76
+ const def = eff.definition;
77
+ const setupState = await ensureSetup(state, def, dst);
78
+ if (isCancelled()) {
79
+ return false;
80
+ }
81
+ await def.apply({
82
+ source: currentImage,
83
+ target: dst,
84
+ state: setupState,
85
+ params: eff.params,
86
+ frame,
87
+ width,
88
+ height,
89
+ pixelRatio,
90
+ gpuDevice,
91
+ });
92
+ if (isCancelled()) {
93
+ return false;
94
+ }
95
+ if (run.backend === 'webgl2') {
96
+ state.pool.assertContextNotLost(dst);
97
+ }
98
+ currentImage = dst;
99
+ dst = dst === a ? b : a;
100
+ }
101
+ lastTarget = (_b = currentImage) !== null && _b !== void 0 ? _b : lastTarget;
102
+ const nextRun = runs[runIndex + 1];
103
+ if (nextRun && nextRun.backend !== run.backend && lastTarget) {
104
+ const bitmap = await createImageBitmap(lastTarget);
105
+ if (isCancelled()) {
106
+ (_a = bitmap.close) === null || _a === void 0 ? void 0 : _a.call(bitmap);
107
+ return false;
108
+ }
109
+ currentImage = bitmap;
110
+ }
111
+ }
112
+ if (!lastTarget) {
113
+ return true;
114
+ }
115
+ const outCtx = output.getContext('2d');
116
+ if (!outCtx) {
117
+ throw new Error('Failed to acquire 2D context for output canvas');
118
+ }
119
+ outCtx.clearRect(0, 0, width, height);
120
+ outCtx.drawImage(lastTarget, 0, 0, width, height);
121
+ return true;
122
+ };
123
+ exports.runEffectChain = runEffectChain;
@@ -0,0 +1,2 @@
1
+ import type { EffectChainState } from './run-effect-chain.js';
2
+ export declare const useEffectChainState: (width: number, height: number) => EffectChainState | null;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useEffectChainState = void 0;
4
+ const react_1 = require("react");
5
+ const run_effect_chain_js_1 = require("./run-effect-chain.js");
6
+ const useEffectChainState = (width, height) => {
7
+ const chainStateRef = (0, react_1.useRef)(null);
8
+ const sizeRef = (0, react_1.useRef)(null);
9
+ if (!sizeRef.current ||
10
+ sizeRef.current.width !== width ||
11
+ sizeRef.current.height !== height) {
12
+ if (chainStateRef.current) {
13
+ (0, run_effect_chain_js_1.cleanupEffectChainState)(chainStateRef.current);
14
+ }
15
+ chainStateRef.current = (0, run_effect_chain_js_1.createEffectChainState)(width, height);
16
+ sizeRef.current = { width, height };
17
+ }
18
+ (0, react_1.useEffect)(() => {
19
+ return () => {
20
+ if (chainStateRef.current) {
21
+ (0, run_effect_chain_js_1.cleanupEffectChainState)(chainStateRef.current);
22
+ }
23
+ };
24
+ }, []);
25
+ return chainStateRef.current;
26
+ };
27
+ exports.useEffectChainState = useEffectChainState;
@@ -0,0 +1,2 @@
1
+ import type { EffectDefinitionAndStack, EffectDescriptor } from './effect-types.js';
2
+ export declare const useMemoizedEffects: (effects: EffectDescriptor<unknown>[]) => EffectDefinitionAndStack<unknown>[];
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useMemoizedEffects = void 0;
4
+ const react_1 = require("react");
5
+ const useMemoizedEffects = (effects) => {
6
+ const previousRef = (0, react_1.useRef)(null);
7
+ const previous = previousRef.current;
8
+ const isSame = previous !== null &&
9
+ previous.length === effects.length &&
10
+ previous.every((p, i) => p.definition === effects[i].definition && p.stack === effects[i].stack);
11
+ if (isSame) {
12
+ return previous;
13
+ }
14
+ const next = effects.map((e) => ({
15
+ definition: e.definition,
16
+ stack: e.stack,
17
+ }));
18
+ previousRef.current = next;
19
+ return next;
20
+ };
21
+ exports.useMemoizedEffects = useMemoizedEffects;
@@ -0,0 +1 @@
1
+ export declare const ENABLE_EFFECTS = true;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ENABLE_EFFECTS = void 0;
4
+ exports.ENABLE_EFFECTS = true;
@@ -3,4 +3,4 @@
3
3
  * @see [Documentation](https://remotion.dev/docs/version)
4
4
  * @returns {string} The current version of the remotion package
5
5
  */
6
- export declare const VERSION = "4.0.452";
6
+ export declare const VERSION = "4.0.453";
@@ -7,4 +7,4 @@ exports.VERSION = void 0;
7
7
  * @see [Documentation](https://remotion.dev/docs/version)
8
8
  * @returns {string} The current version of the remotion package
9
9
  */
10
- exports.VERSION = '4.0.452';
10
+ exports.VERSION = '4.0.453';
@@ -1238,7 +1238,7 @@ var addSequenceStackTraces = (component) => {
1238
1238
  };
1239
1239
 
1240
1240
  // src/version.ts
1241
- var VERSION = "4.0.452";
1241
+ var VERSION = "4.0.453";
1242
1242
 
1243
1243
  // src/multiple-versions-warning.ts
1244
1244
  var checkMultipleRemotionVersions = () => {
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "4.0.452";
2
+ var VERSION = "4.0.453";
3
3
  export {
4
4
  VERSION
5
5
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/core"
4
4
  },
5
5
  "name": "remotion",
6
- "version": "4.0.452",
6
+ "version": "4.0.453",
7
7
  "description": "Make videos programmatically",
8
8
  "main": "dist/cjs/index.js",
9
9
  "types": "dist/cjs/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  "react-dom": "19.2.3",
36
36
  "webpack": "5.105.0",
37
37
  "zod": "4.3.6",
38
- "@remotion/eslint-config-internal": "4.0.452",
38
+ "@remotion/eslint-config-internal": "4.0.453",
39
39
  "eslint": "9.19.0",
40
40
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
41
41
  },
@@ -1,12 +0,0 @@
1
- import React from 'react';
2
- import type { EffectsProp } from './effect-types.js';
3
- export type HtmlInCanvasProps = {
4
- readonly width: number;
5
- readonly height: number;
6
- readonly effects?: EffectsProp;
7
- readonly children: React.ReactNode;
8
- readonly className?: string;
9
- readonly style?: React.CSSProperties;
10
- readonly pixelRatio?: number;
11
- };
12
- export declare const HtmlInCanvas: React.FC<HtmlInCanvasProps>;
@@ -1,91 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HtmlInCanvas = void 0;
4
- const jsx_runtime_1 = require("react/jsx-runtime");
5
- const react_1 = require("react");
6
- const cancel_render_js_1 = require("../cancel-render.js");
7
- const use_effect_chain_js_1 = require("./use-effect-chain.js");
8
- const isHtmlInCanvasSupported = () => {
9
- if (typeof document === 'undefined') {
10
- return false;
11
- }
12
- const canvas = document.createElement('canvas');
13
- const ctx = canvas.getContext('2d');
14
- return (typeof (ctx === null || ctx === void 0 ? void 0 : ctx.drawElementImage) === 'function' &&
15
- typeof canvas.requestPaint === 'function');
16
- };
17
- // `<HtmlInCanvas>` rasterizes its DOM children into a canvas using the WICG
18
- // html-in-canvas proposal, then runs the resulting image through an
19
- // effect chain. The captured DOM image is the source for the chain; effects
20
- // then transform it just like with any other source component.
21
- //
22
- // Requires Chrome Canary with `chrome://flags/#canvas-draw-element` enabled.
23
- const HtmlInCanvas = ({ width, height, effects = [], children, className, style, pixelRatio, }) => {
24
- const sourceCanvasRef = (0, react_1.useRef)(null);
25
- const sceneRef = (0, react_1.useRef)(null);
26
- const [outputCanvas, setOutputCanvas] = (0, react_1.useState)(null);
27
- (0, react_1.useEffect)(() => {
28
- if (!isHtmlInCanvasSupported()) {
29
- (0, cancel_render_js_1.cancelRender)(new Error('HTML in Canvas is not supported. Open this page in Chrome Canary with chrome://flags/#canvas-draw-element enabled.'));
30
- return;
31
- }
32
- const sourceCanvas = sourceCanvasRef.current;
33
- if (!sourceCanvas) {
34
- return;
35
- }
36
- sourceCanvas.layoutSubtree = true;
37
- }, []);
38
- const source = (0, react_1.useCallback)(() => {
39
- const sourceCanvas = sourceCanvasRef.current;
40
- const sceneEl = sceneRef.current;
41
- if (!sourceCanvas || !sceneEl) {
42
- return null;
43
- }
44
- const ctx = sourceCanvas.getContext('2d');
45
- if (!ctx) {
46
- throw new Error('Failed to acquire 2D context for <HtmlInCanvas> source');
47
- }
48
- return new Promise((resolve, reject) => {
49
- const onPaint = () => {
50
- sourceCanvas.removeEventListener('paint', onPaint);
51
- try {
52
- ctx.reset();
53
- ctx.drawElementImage(sceneEl, 0, 0, width, height);
54
- resolve(sourceCanvas);
55
- }
56
- catch (err) {
57
- reject(err instanceof Error ? err : new Error(String(err)));
58
- }
59
- };
60
- sourceCanvas.addEventListener('paint', onPaint);
61
- sourceCanvas.requestPaint();
62
- });
63
- }, [width, height]);
64
- (0, use_effect_chain_js_1.useEffectChain)({
65
- source,
66
- effects,
67
- width,
68
- height,
69
- pixelRatio,
70
- output: outputCanvas,
71
- sourceDeps: [],
72
- });
73
- return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
74
- jsx_runtime_1.jsx("canvas", { ref: sourceCanvasRef, width: width, height: height, style: {
75
- position: 'absolute',
76
- inset: 0,
77
- width,
78
- height,
79
- }, children: jsx_runtime_1.jsx("div", { ref: sceneRef, style: {
80
- width,
81
- height,
82
- }, children: children }) }), jsx_runtime_1.jsx("canvas", { ref: setOutputCanvas, width: width, height: height, className: className, style: {
83
- position: 'absolute',
84
- inset: 0,
85
- width,
86
- height,
87
- ...style,
88
- } })
89
- ] }));
90
- };
91
- exports.HtmlInCanvas = HtmlInCanvas;
@@ -1,13 +0,0 @@
1
- import type { EffectsProp } from './effect-types.js';
2
- type SourceFn = () => CanvasImageSource | Promise<CanvasImageSource> | null | undefined;
3
- export type UseEffectChainOptions = {
4
- readonly source: SourceFn;
5
- readonly effects: EffectsProp;
6
- readonly width: number;
7
- readonly height: number;
8
- readonly pixelRatio?: number;
9
- readonly output: HTMLCanvasElement | null;
10
- readonly sourceDeps: ReadonlyArray<unknown>;
11
- };
12
- export declare const useEffectChain: ({ source, effects, width, height, pixelRatio, output, sourceDeps, }: UseEffectChainOptions) => void;
13
- export {};
@@ -1,197 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useEffectChain = void 0;
4
- const react_1 = require("react");
5
- const use_current_frame_js_1 = require("../use-current-frame.js");
6
- const use_delay_render_js_1 = require("../use-delay-render.js");
7
- const canvas_pool_js_1 = require("./canvas-pool.js");
8
- const effect_internals_js_1 = require("./effect-internals.js");
9
- const gpu_device_js_1 = require("./gpu-device.js");
10
- const ensureSetup = (state, def, target) => {
11
- const cached = state.setupCache.get(def);
12
- if (cached) {
13
- return cached;
14
- }
15
- const promise = Promise.resolve(def.setup(target));
16
- state.setupCache.set(def, promise);
17
- state.cleanupRegistry.push({ definition: def, statePromise: promise });
18
- return promise;
19
- };
20
- // `useEffectChain` is the shared per-frame runtime that powers source
21
- // components like `<Solid>` and `<HtmlInCanvas>`. Source components are
22
- // responsible for producing a `CanvasImageSource` via the `source` callback;
23
- // the runtime owns the rest of the pipeline (scratch canvases, ping-pong,
24
- // cross-backend transfer via `createImageBitmap`, single `delayRender` per
25
- // frame).
26
- const useEffectChain = ({ source, effects, width, height, pixelRatio = 1, output, sourceDeps, }) => {
27
- const frame = (0, use_current_frame_js_1.useCurrentFrame)();
28
- const { delayRender, continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)();
29
- const stateRef = (0, react_1.useRef)(null);
30
- const sizeRef = (0, react_1.useRef)(null);
31
- // Reset pool if dimensions changed.
32
- if (!sizeRef.current ||
33
- sizeRef.current.width !== width ||
34
- sizeRef.current.height !== height) {
35
- stateRef.current = {
36
- pool: new canvas_pool_js_1.CanvasPool(width, height),
37
- setupCache: new WeakMap(),
38
- cleanupRegistry: [],
39
- };
40
- sizeRef.current = { width, height };
41
- }
42
- const flattened = (0, react_1.useMemo)(() => (0, effect_internals_js_1.flattenEffects)(effects), [effects]);
43
- const runs = (0, react_1.useMemo)(() => (0, effect_internals_js_1.groupByBackend)(flattened), [flattened]);
44
- (0, react_1.useEffect)(() => {
45
- const state = stateRef.current;
46
- if (!state || !output) {
47
- return;
48
- }
49
- const handle = delayRender(`Canvas effect chain (frame ${frame})`);
50
- let cancelled = false;
51
- let resolved = false;
52
- const finish = () => {
53
- if (resolved) {
54
- return;
55
- }
56
- resolved = true;
57
- continueRender(handle);
58
- };
59
- const fail = (err) => {
60
- if (resolved) {
61
- return;
62
- }
63
- resolved = true;
64
- cancelRender(err);
65
- };
66
- (async () => {
67
- var _a;
68
- var _b;
69
- try {
70
- const sourceImage = await source();
71
- if (cancelled) {
72
- return;
73
- }
74
- if (!sourceImage) {
75
- finish();
76
- return;
77
- }
78
- let currentImage = sourceImage;
79
- let lastTarget = null;
80
- if (runs.length === 0) {
81
- // No effects: blit source directly into output.
82
- const ctx = output.getContext('2d');
83
- if (!ctx) {
84
- throw new Error('Failed to acquire 2D context for output canvas');
85
- }
86
- ctx.clearRect(0, 0, width, height);
87
- ctx.drawImage(currentImage, 0, 0, width, height);
88
- finish();
89
- return;
90
- }
91
- let needsGpuDevice = false;
92
- for (const run of runs) {
93
- if (run.backend === 'webgpu') {
94
- needsGpuDevice = true;
95
- break;
96
- }
97
- }
98
- const gpuDevice = needsGpuDevice ? await (0, gpu_device_js_1.getGpuDevice)() : null;
99
- if (cancelled) {
100
- return;
101
- }
102
- for (let runIndex = 0; runIndex < runs.length; runIndex++) {
103
- const run = runs[runIndex];
104
- const [a, b] = state.pool.getPair(run.backend);
105
- let dst = a;
106
- for (const eff of run.effects) {
107
- const def = eff.definition;
108
- const setupState = await ensureSetup(state, def, dst);
109
- if (cancelled) {
110
- return;
111
- }
112
- await def.apply({
113
- source: currentImage,
114
- target: dst,
115
- state: setupState,
116
- params: eff.params,
117
- frame,
118
- width,
119
- height,
120
- pixelRatio,
121
- gpuDevice,
122
- });
123
- if (cancelled) {
124
- return;
125
- }
126
- currentImage = dst;
127
- dst = dst === a ? b : a;
128
- }
129
- lastTarget = (_b = currentImage) !== null && _b !== void 0 ? _b : lastTarget;
130
- // If another run follows with a different backend, transfer via
131
- // `createImageBitmap` so the next backend can sample from a
132
- // neutral GPU-resident handle.
133
- const nextRun = runs[runIndex + 1];
134
- if (nextRun && nextRun.backend !== run.backend && lastTarget) {
135
- const bitmap = await createImageBitmap(lastTarget);
136
- if (cancelled) {
137
- (_a = bitmap.close) === null || _a === void 0 ? void 0 : _a.call(bitmap);
138
- return;
139
- }
140
- currentImage = bitmap;
141
- }
142
- }
143
- if (!lastTarget) {
144
- finish();
145
- return;
146
- }
147
- const outCtx = output.getContext('2d');
148
- if (!outCtx) {
149
- throw new Error('Failed to acquire 2D context for output canvas');
150
- }
151
- outCtx.clearRect(0, 0, width, height);
152
- outCtx.drawImage(lastTarget, 0, 0, width, height);
153
- finish();
154
- }
155
- catch (err) {
156
- if (!cancelled) {
157
- fail(err);
158
- }
159
- }
160
- })();
161
- return () => {
162
- cancelled = true;
163
- if (!resolved) {
164
- continueRender(handle);
165
- }
166
- };
167
- /* eslint-disable react-hooks/exhaustive-deps */
168
- }, [
169
- frame,
170
- width,
171
- height,
172
- pixelRatio,
173
- output,
174
- runs,
175
- delayRender,
176
- continueRender,
177
- cancelRender,
178
- ...sourceDeps,
179
- ]);
180
- /* eslint-enable react-hooks/exhaustive-deps */
181
- // Cleanup on unmount: invoke `cleanup` for every cached state.
182
- (0, react_1.useEffect)(() => {
183
- return () => {
184
- const state = stateRef.current;
185
- if (!state) {
186
- return;
187
- }
188
- for (const entry of state.cleanupRegistry) {
189
- entry.statePromise.then((s) => {
190
- var _a, _b;
191
- return (_b = (_a = entry.definition).cleanup) === null || _b === void 0 ? void 0 : _b.call(_a, s);
192
- }, () => undefined);
193
- }
194
- };
195
- }, []);
196
- };
197
- exports.useEffectChain = useEffectChain;
@@ -1,19 +0,0 @@
1
- /**
2
- * Visual-mode fields for clip timing. Used by `<Sequence>` and merged into media schemas (e.g. `@remotion/media` `<Video>`).
3
- */
4
- export declare const SEQUENCE_TIMING_SCHEMA: {
5
- readonly from: {
6
- readonly type: "number";
7
- readonly min: 0;
8
- readonly step: 1;
9
- readonly default: 0;
10
- readonly description: "From";
11
- };
12
- readonly durationInFrames: {
13
- readonly type: "number";
14
- readonly min: 0.01;
15
- readonly step: 0.01;
16
- readonly default: undefined;
17
- readonly description: "Duration in frames";
18
- };
19
- };
@@ -1,22 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SEQUENCE_TIMING_SCHEMA = void 0;
4
- /**
5
- * Visual-mode fields for clip timing. Used by `<Sequence>` and merged into media schemas (e.g. `@remotion/media` `<Video>`).
6
- */
7
- exports.SEQUENCE_TIMING_SCHEMA = {
8
- from: {
9
- type: 'number',
10
- min: 0,
11
- step: 1,
12
- default: 0,
13
- description: 'From',
14
- },
15
- durationInFrames: {
16
- type: 'number',
17
- min: 0.01,
18
- step: 0.01,
19
- default: undefined,
20
- description: 'Duration in frames',
21
- },
22
- };