remotion 4.0.536 → 4.0.537

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.
@@ -38,4 +38,7 @@ export type CompositionManagerContext = {
38
38
  canvasContent: CanvasContent | null;
39
39
  };
40
40
  export declare const CompositionManager: React.Context<CompositionManagerContext>;
41
+ export declare const CompositionManagerRefContext: React.Context<{
42
+ readonly current: CompositionManagerContext;
43
+ }>;
41
44
  export declare const CompositionSetters: React.Context<CompositionManagerSetters>;
@@ -1,16 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CompositionSetters = exports.CompositionManager = exports.getAssetPreviewCompositionId = void 0;
3
+ exports.CompositionSetters = exports.CompositionManagerRefContext = exports.CompositionManager = exports.getAssetPreviewCompositionId = void 0;
4
4
  const react_1 = require("react");
5
5
  const getAssetPreviewCompositionId = (asset) => `asset:${asset}`;
6
6
  exports.getAssetPreviewCompositionId = getAssetPreviewCompositionId;
7
- exports.CompositionManager = (0, react_1.createContext)({
7
+ const defaultCompositionManagerContext = {
8
8
  compositions: [],
9
9
  folders: [],
10
10
  currentCompositionMetadata: null,
11
11
  currentAssetMetadata: null,
12
12
  canvasContent: null,
13
- });
13
+ };
14
+ exports.CompositionManager = (0, react_1.createContext)(defaultCompositionManagerContext);
15
+ exports.CompositionManagerRefContext = (0, react_1.createContext)({ current: defaultCompositionManagerContext });
14
16
  exports.CompositionSetters = (0, react_1.createContext)({
15
17
  setCanvasContent: () => undefined,
16
18
  setCurrentAssetMetadata: () => undefined,
@@ -103,9 +103,11 @@ const CompositionManagerProvider = ({ children, onlyRenderComposition, currentCo
103
103
  currentAssetMetadata,
104
104
  canvasContent,
105
105
  ]);
106
+ const compositionManagerRef = (0, react_1.useRef)(compositionManagerContextValue);
107
+ compositionManagerRef.current = compositionManagerContextValue;
106
108
  if (registrationError !== null) {
107
109
  throw registrationError;
108
110
  }
109
- return (jsx_runtime_1.jsx(CompositionRegistryProvider_js_1.CompositionRegistryProvider, { ref: flushSnapshotRef, onSnapshot: onCommitRegistrations, children: jsx_runtime_1.jsx(CompositionManagerContext_1.CompositionManager.Provider, { value: compositionManagerContextValue, children: jsx_runtime_1.jsx(CompositionManagerContext_1.CompositionSetters.Provider, { value: compositionManagerSetters, children: children }) }) }));
111
+ return (jsx_runtime_1.jsx(CompositionRegistryProvider_js_1.CompositionRegistryProvider, { ref: flushSnapshotRef, onSnapshot: onCommitRegistrations, children: jsx_runtime_1.jsx(CompositionManagerContext_1.CompositionManager.Provider, { value: compositionManagerContextValue, children: jsx_runtime_1.jsx(CompositionManagerContext_1.CompositionManagerRefContext.Provider, { value: compositionManagerRef, children: jsx_runtime_1.jsx(CompositionManagerContext_1.CompositionSetters.Provider, { value: compositionManagerSetters, children: children }) }) }) }));
110
112
  };
111
113
  exports.CompositionManagerProvider = CompositionManagerProvider;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HtmlInCanvas = exports.htmlInCanvasSchema = exports.getHtmlInCanvasUnsupportedMessage = exports.HTML_IN_CANVAS_UNSUPPORTED_MESSAGE = exports.isHtmlInCanvasNestingSupported = exports.isHtmlInCanvasSupported = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
+ const DefaultPremountContext_js_1 = require("./DefaultPremountContext.js");
6
7
  const run_effect_chain_js_1 = require("./effects/run-effect-chain.js");
7
8
  const use_effect_chain_state_js_1 = require("./effects/use-effect-chain-state.js");
8
9
  const use_memoized_effects_js_1 = require("./effects/use-memoized-effects.js");
@@ -160,11 +161,15 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, e
160
161
  const { isClientSideRendering, isRendering } = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
161
162
  const canRetryMissingPaintRecord = !isRendering || isClientSideRendering;
162
163
  const usesDirectLayoutCanvas = onPaint === undefined && onInit === undefined;
163
- const paintTargetMode = usesDirectLayoutCanvas
164
- ? 'direct'
165
- : isRendering
166
- ? 'copied'
167
- : 'transferred';
164
+ // Keep a nested drawable canvas separate from the bitmap captured by its
165
+ // parent. Reusing it for both can crash Chromium during raster invalidation.
166
+ const paintTargetMode = ancestorPaintState
167
+ ? 'copied'
168
+ : usesDirectLayoutCanvas
169
+ ? 'direct'
170
+ : isRendering
171
+ ? 'copied'
172
+ : 'transferred';
168
173
  if (!(0, exports.isHtmlInCanvasSupported)()) {
169
174
  cancelRender(new Error((0, exports.getHtmlInCanvasUnsupportedMessage)()));
170
175
  }
@@ -237,9 +242,11 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, e
237
242
  const initializedRef = (0, react_1.useRef)(false);
238
243
  const onInitCleanupRef = (0, react_1.useRef)(null);
239
244
  const unmountedRef = (0, react_1.useRef)(false);
245
+ const effectScratchCanvasesRef = (0, react_1.useRef)(null);
240
246
  const paintingTargets = (0, react_1.useRef)(new WeakMap());
241
247
  const onPaintCb = (0, react_1.useCallback)(async () => {
242
248
  var _a, _b;
249
+ var _c;
243
250
  if (isRendering && paintState.pendingChildren > 0) {
244
251
  return;
245
252
  }
@@ -351,6 +358,7 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, e
351
358
  }
352
359
  throw error;
353
360
  }
361
+ let effectSourceCanvas = null;
354
362
  try {
355
363
  const currentOnPaint = onPaintRef.current;
356
364
  if (currentOnPaint) {
@@ -367,6 +375,48 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, e
367
375
  await paintResult;
368
376
  }
369
377
  }
378
+ else if (paintTarget instanceof HTMLCanvasElement &&
379
+ effectsRef.current.some((effect) => !effect.params.disabled)) {
380
+ // The element image can only be drawn into the visible layout
381
+ // canvas, but effects finish asynchronously (e.g. while the
382
+ // WebGPU device initializes). Move the unprocessed pixels to a
383
+ // scratch canvas and restore the previous output within this task,
384
+ // so the browser never presents the HTML without its effects.
385
+ (_c = effectScratchCanvasesRef.current) !== null && _c !== void 0 ? _c : (effectScratchCanvasesRef.current = {
386
+ source: document.createElement('canvas'),
387
+ previousOutput: document.createElement('canvas'),
388
+ });
389
+ const { source: effectSource, previousOutput } = effectScratchCanvasesRef.current;
390
+ resizePaintTarget({
391
+ target: effectSource,
392
+ width: canvasWidth,
393
+ height: canvasHeight,
394
+ });
395
+ resizePaintTarget({
396
+ target: previousOutput,
397
+ width: canvasWidth,
398
+ height: canvasHeight,
399
+ });
400
+ const effectSourceCtx = effectSource.getContext('2d');
401
+ const previousOutputCtx = previousOutput.getContext('2d');
402
+ const paintTargetCtx = paintTarget.getContext('2d');
403
+ if (!effectSourceCtx || !previousOutputCtx || !paintTargetCtx) {
404
+ throw new Error('Failed to acquire 2D context for <HtmlInCanvas> canvas');
405
+ }
406
+ previousOutputCtx.reset();
407
+ previousOutputCtx.drawImage(paintTarget, 0, 0);
408
+ defaultOnPaint({
409
+ canvas: paintTarget,
410
+ element,
411
+ elementImage: elImage,
412
+ pixelDensity: resolvedPixelDensity,
413
+ });
414
+ effectSourceCtx.reset();
415
+ effectSourceCtx.drawImage(paintTarget, 0, 0);
416
+ paintTargetCtx.reset();
417
+ paintTargetCtx.drawImage(previousOutput, 0, 0);
418
+ effectSourceCanvas = effectSource;
419
+ }
370
420
  else {
371
421
  defaultOnPaint({
372
422
  canvas: paintTarget,
@@ -380,7 +430,7 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, e
380
430
  if (state) {
381
431
  await (0, run_effect_chain_js_1.runEffectChain)({
382
432
  state,
383
- source: paintTarget,
433
+ source: effectSourceCanvas !== null && effectSourceCanvas !== void 0 ? effectSourceCanvas : paintTarget,
384
434
  effects: effectsRef.current,
385
435
  output: paintTarget,
386
436
  width: canvasWidth,
@@ -445,10 +495,11 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, e
445
495
  paintState.release = null;
446
496
  };
447
497
  }, [paintState]);
448
- // Default paint handlers draw synchronously on the layout canvas itself.
449
- // ElementImage can only be drawn into its associated canvas, so custom
450
- // handlers retain a transferred target. Rendering copies it to a separate
451
- // visible canvas without waiting for the transferred canvas to present.
498
+ // Non-nested default handlers paint directly on the layout canvas. Nested
499
+ // canvases and custom handlers retain a transferred target because an
500
+ // ElementImage can only be drawn into its associated canvas. Nested canvases
501
+ // and rendering copy it to a separate visible canvas without waiting for
502
+ // the transferred canvas to present.
452
503
  (0, react_1.useLayoutEffect)(() => {
453
504
  var _a;
454
505
  const placeholder = canvas2dRef.current;
@@ -539,7 +590,7 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, e
539
590
  };
540
591
  }, [height, style, width]);
541
592
  return (jsx_runtime_1.jsxs(HtmlInCanvasAncestorContext.Provider, { value: paintState, children: [
542
- jsx_runtime_1.jsxs("canvas", { ref: paintTargetMode === 'copied' ? canvas2dRef : setVisibleCanvasRef, width: canvasWidth, height: canvasHeight, style: paintTargetMode === 'copied'
593
+ jsx_runtime_1.jsx("canvas", { ref: paintTargetMode === 'copied' ? canvas2dRef : setVisibleCanvasRef, width: canvasWidth, height: canvasHeight, style: paintTargetMode === 'copied'
543
594
  ? {
544
595
  ...canvasStyle,
545
596
  position: 'absolute',
@@ -548,10 +599,10 @@ const HtmlInCanvasContent = (0, react_1.forwardRef)(({ width, height, effects, e
548
599
  visibility: 'hidden',
549
600
  pointerEvents: 'none',
550
601
  }
551
- : canvasStyle, children: [
552
- jsx_runtime_1.jsx("div", { ref: divRef, style: paintTargetMode === 'copied'
553
- ? { ...innerStyle, visibility: 'visible' }
554
- : innerStyle, children: children }), canvasSiblings] }, canvasSizeKey), paintTargetMode === 'copied' ? (jsx_runtime_1.jsx("canvas", { ref: setVisibleCanvasRef, width: canvasWidth, height: canvasHeight, style: canvasStyle }, `output-${canvasSizeKey}`)) : null] }));
602
+ : canvasStyle, children: jsx_runtime_1.jsxs(DefaultPremountContext_js_1.DefaultPremountContext.Provider, { value: 0, children: [
603
+ jsx_runtime_1.jsx("div", { ref: divRef, style: paintTargetMode === 'copied'
604
+ ? { ...innerStyle, visibility: 'visible' }
605
+ : innerStyle, children: children }), canvasSiblings] }) }, canvasSizeKey), paintTargetMode === 'copied' ? (jsx_runtime_1.jsx("canvas", { ref: setVisibleCanvasRef, width: canvasWidth, height: canvasHeight, style: canvasStyle }, `output-${canvasSizeKey}`)) : null] }));
555
606
  });
556
607
  HtmlInCanvasContent.displayName = 'HtmlInCanvasContent';
557
608
  // Subscribe to frame changes only while rendering. Preview keeps relying on
@@ -56,6 +56,7 @@ const AnimatedImageContent = (0, react_2.forwardRef)(({ src, width, height, onEr
56
56
  const resolvedSrc = (0, resolve_image_source_1.resolveAnimatedImageSource)(src);
57
57
  const [decodedImage, setDecodedImage] = (0, react_2.useState)(null);
58
58
  const imageDecoder = (_a = decodedImage === null || decodedImage === void 0 ? void 0 : decodedImage.decoder) !== null && _a !== void 0 ? _a : null;
59
+ const activeDecoderRef = (0, react_2.useRef)(null);
59
60
  const { delayRender, continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)();
60
61
  const frame = (0, use_current_frame_js_1.useCurrentFrame)();
61
62
  const { fps } = (0, use_video_config_js_1.useVideoConfig)();
@@ -85,6 +86,7 @@ const AnimatedImageContent = (0, react_2.forwardRef)(({ src, width, height, onEr
85
86
  const controller = new AbortController();
86
87
  let cancelled = false;
87
88
  let continued = false;
89
+ let decoder = null;
88
90
  const continueRenderOnce = () => {
89
91
  if (continued) {
90
92
  return;
@@ -104,6 +106,8 @@ const AnimatedImageContent = (0, react_2.forwardRef)(({ src, width, height, onEr
104
106
  d.close();
105
107
  return;
106
108
  }
109
+ decoder = d;
110
+ activeDecoderRef.current = d;
107
111
  setDecodedImage({ decoder: d, handle: decodeHandle });
108
112
  })
109
113
  .catch((err) => {
@@ -124,6 +128,8 @@ const AnimatedImageContent = (0, react_2.forwardRef)(({ src, width, height, onEr
124
128
  });
125
129
  return () => {
126
130
  cancelled = true;
131
+ activeDecoderRef.current = null;
132
+ decoder === null || decoder === void 0 ? void 0 : decoder.close();
127
133
  controller.abort();
128
134
  continueRenderOnce();
129
135
  };
@@ -136,13 +142,12 @@ const AnimatedImageContent = (0, react_2.forwardRef)(({ src, width, height, onEr
136
142
  delayRender,
137
143
  cancelRender,
138
144
  ]);
139
- (0, react_2.useEffect)(() => {
140
- return () => {
141
- imageDecoder === null || imageDecoder === void 0 ? void 0 : imageDecoder.close();
142
- };
143
- }, [imageDecoder]);
144
145
  (0, react_2.useLayoutEffect)(() => {
145
- if (!imageDecoder || !decodedImage) {
146
+ // Activity and Suspense can retain state after cleaning up effects.
147
+ // Wait for the current loading effect to publish a live decoder.
148
+ if (!imageDecoder ||
149
+ !decodedImage ||
150
+ activeDecoderRef.current !== imageDecoder) {
146
151
  return;
147
152
  }
148
153
  const delay = delayRender(`Rendering frame at ${currentTime} of <AnimatedImage src="${src}"/>`);
@@ -39,6 +39,7 @@ const react_1 = __importStar(require("react"));
39
39
  const evaluate_source_expressions_js_1 = require("../evaluate-source-expressions.js");
40
40
  const get_effective_visual_mode_value_js_1 = require("../get-effective-visual-mode-value.js");
41
41
  const interpolate_keyframed_status_js_1 = require("../interpolate-keyframed-status.js");
42
+ const registry_store_js_1 = require("../registry-store.js");
42
43
  const runtime_value_store_js_1 = require("../runtime-value-store.js");
43
44
  const sequence_activity_context_js_1 = require("../sequence-activity-context.js");
44
45
  const sequence_node_path_js_1 = require("../sequence-node-path.js");
@@ -46,6 +47,20 @@ const SequenceManager_js_1 = require("../SequenceManager.js");
46
47
  const use_current_frame_js_1 = require("../use-current-frame.js");
47
48
  const emptyDragOverrides = {};
48
49
  const useCacheCommitEffect = (_a = react_1.default.useInsertionEffect) !== null && _a !== void 0 ? _a : react_1.useLayoutEffect;
50
+ const areEffectParamsEqual = (previous, next) => {
51
+ if (Object.is(previous, next)) {
52
+ return true;
53
+ }
54
+ if (previous === null ||
55
+ next === null ||
56
+ typeof previous !== 'object' ||
57
+ typeof next !== 'object' ||
58
+ Object.getPrototypeOf(previous) !== Object.prototype ||
59
+ Object.getPrototypeOf(next) !== Object.prototype) {
60
+ return false;
61
+ }
62
+ return (0, registry_store_js_1.areRegistryEntriesEqual)(previous, next);
63
+ };
49
64
  const mergeOverrides = ({ descriptor, propStatusOverrides, dragOverrides, frame, }) => {
50
65
  if (!propStatusOverrides && !dragOverrides) {
51
66
  return { params: descriptor.params, effectKey: descriptor.effectKey };
@@ -105,17 +120,16 @@ const resolvePropStatusOverrides = (propStatus, frame) => {
105
120
  const useMemoizedEffectDefinitions = (effects) => {
106
121
  const activityDormant = (0, react_1.useContext)(sequence_activity_context_js_1.SequenceActivityDormantContext);
107
122
  const previousRef = (0, react_1.useRef)(null);
108
- const definitions = effects.map((descriptor) => descriptor.definition);
109
123
  const previous = previousRef.current;
110
124
  const isSame = previous !== null &&
111
- previous.definitions.length === definitions.length &&
112
- previous.definitions.every((definition, i) => definition === definitions[i]);
125
+ previous.definitions.length === effects.length &&
126
+ previous.definitions.every((definition, i) => definition === effects[i].definition);
113
127
  const controllers = isSame
114
128
  ? previous.controllers
115
129
  : effects.map((effect) => (0, runtime_value_store_js_1.createRuntimeValueStore)(effect.params));
116
130
  const stableDefinitions = isSame
117
131
  ? previous.definitions
118
- : Object.assign(definitions, {
132
+ : Object.assign(effects.map((descriptor) => descriptor.definition), {
119
133
  runtimeValues: controllers.map((controller) => controller.store),
120
134
  });
121
135
  useCacheCommitEffect(() => {
@@ -125,6 +139,12 @@ const useMemoizedEffectDefinitions = (effects) => {
125
139
  if (!activityDormant || controllers.length === 0) {
126
140
  return;
127
141
  }
142
+ if (controllers.every((controller, index) => {
143
+ var _a;
144
+ return areEffectParamsEqual(controller.store.getSnapshot(), (_a = effects[index]) === null || _a === void 0 ? void 0 : _a.params);
145
+ })) {
146
+ return;
147
+ }
128
148
  let cancelled = false;
129
149
  queueMicrotask(() => {
130
150
  if (cancelled) {
@@ -134,21 +154,10 @@ const useMemoizedEffectDefinitions = (effects) => {
134
154
  var _a;
135
155
  const snapshot = (_a = effects[index]) === null || _a === void 0 ? void 0 : _a.params;
136
156
  const currentSnapshot = controller.store.getSnapshot();
137
- if (Object.is(snapshot, currentSnapshot)) {
138
- return;
139
- }
140
157
  // Equivalent inline params should not notify inspector subscribers on
141
158
  // every dormant render. Preserve changes to keys and leaf references.
142
- if (snapshot !== null &&
143
- currentSnapshot !== null &&
144
- typeof snapshot === 'object' &&
145
- typeof currentSnapshot === 'object') {
146
- const keys = Object.keys(snapshot);
147
- if (keys.length === Object.keys(currentSnapshot).length &&
148
- keys.every((key) => Object.prototype.hasOwnProperty.call(currentSnapshot, key) &&
149
- Object.is(snapshot[key], currentSnapshot[key]))) {
150
- return;
151
- }
159
+ if (areEffectParamsEqual(snapshot, currentSnapshot)) {
160
+ return;
152
161
  }
153
162
  controller.setSnapshot(snapshot);
154
163
  });
@@ -164,7 +173,9 @@ const useMemoizedEffectDefinitions = (effects) => {
164
173
  stableDefinitions.forEach((_definition, index) => {
165
174
  var _a;
166
175
  const snapshot = (_a = effects[index]) === null || _a === void 0 ? void 0 : _a.params;
167
- controllers[index].setSnapshot(snapshot);
176
+ if (!areEffectParamsEqual(controllers[index].store.getSnapshot(), snapshot)) {
177
+ controllers[index].setSnapshot(snapshot);
178
+ }
168
179
  });
169
180
  }, [controllers, effects, stableDefinitions]);
170
181
  return stableDefinitions;
@@ -206,55 +217,86 @@ const useMemoizedEffects = ({ effects, overrideId, videoConfigValues, }) => {
206
217
  var _a;
207
218
  const previousRef = (0, react_1.useRef)(null);
208
219
  const { propStatuses } = (0, react_1.useContext)(SequenceManager_js_1.VisualModePropStatusesContext);
209
- const frame = (0, use_current_frame_js_1.useCurrentFrame)();
210
220
  const { overrideIdToNodePathMappings } = (0, react_1.useContext)(sequence_node_path_js_1.OverrideIdsToNodePathsGettersContext);
211
221
  const previous = previousRef.current;
212
222
  const nodePath = overrideId
213
223
  ? ((_a = overrideIdToNodePathMappings[overrideId]) !== null && _a !== void 0 ? _a : null)
214
224
  : null;
215
225
  const effectDragOverrides = (0, SequenceManager_js_1.useEffectDragOverridesForNodePath)(nodePath);
216
- const resolved = effects.map((descriptor, index) => {
217
- var _a;
226
+ const effectPropStatuses = (0, react_1.useMemo)(() => Array.from({ length: effects.length }, (_, effectIndex) => {
218
227
  if (nodePath === null) {
219
- return {
220
- descriptor,
221
- params: descriptor.params,
222
- effectKey: descriptor.effectKey,
223
- };
228
+ return null;
224
229
  }
225
- const effectStatus = (0, exports.getEffectPropStatusesCtx)({
230
+ const status = (0, exports.getEffectPropStatusesCtx)({
226
231
  propStatuses,
227
232
  nodePath: { ...nodePath, videoConfigValues },
228
- effectIndex: index,
233
+ effectIndex,
229
234
  });
230
- const propStatusOverrides = effectStatus.type === 'can-update-effect'
231
- ? resolvePropStatusOverrides(effectStatus.props, frame)
232
- : null;
235
+ return status.type === 'can-update-effect' ? status.props : null;
236
+ }), [effects.length, nodePath, propStatuses, videoConfigValues]);
237
+ const animatedEffects = (0, react_1.useMemo)(() => effectPropStatuses.map((status, index) => {
238
+ var _a;
239
+ return Object.values(status !== null && status !== void 0 ? status : {}).some((prop) => prop.status === 'keyframed') ||
240
+ Object.values((_a = effectDragOverrides[index]) !== null && _a !== void 0 ? _a : emptyDragOverrides).some((override) => override.type === 'keyframed');
241
+ }), [effectDragOverrides, effectPropStatuses]);
242
+ // Runtime animations arrive through props. Only source/drag keyframes need
243
+ // their own clock, and an animated effect must not invalidate static siblings.
244
+ const frame = (0, use_current_frame_js_1.useCurrentFrameWithOptionalSubscription)(animatedEffects.some(Boolean));
245
+ const prepared = effects.map((descriptor, index) => {
246
+ var _a;
247
+ const propStatus = effectPropStatuses[index];
233
248
  const dragOverridesMap = (_a = effectDragOverrides[index]) !== null && _a !== void 0 ? _a : emptyDragOverrides;
234
249
  const dragOverrides = Object.keys(dragOverridesMap).length === 0 ? null : dragOverridesMap;
250
+ const effectFrame = animatedEffects[index] ? frame : null;
251
+ const cached = previous === null || previous === void 0 ? void 0 : previous.prepared[index];
252
+ if (cached &&
253
+ cached.definition === descriptor.definition &&
254
+ cached.sourceKey === descriptor.effectKey &&
255
+ areEffectParamsEqual(cached.params, descriptor.params) &&
256
+ cached.propStatus === propStatus &&
257
+ cached.dragOverrides === dragOverrides &&
258
+ cached.frame === effectFrame) {
259
+ return cached;
260
+ }
261
+ const propStatusOverrides = resolvePropStatusOverrides(propStatus !== null && propStatus !== void 0 ? propStatus : undefined, frame);
235
262
  const { params, effectKey } = mergeOverrides({
236
263
  descriptor,
237
264
  propStatusOverrides,
238
265
  dragOverrides,
239
266
  frame,
240
267
  });
241
- return { descriptor, params, effectKey };
268
+ return {
269
+ definition: descriptor.definition,
270
+ sourceKey: descriptor.effectKey,
271
+ // Snapshot plain params so shallow comparisons see in-place field edits.
272
+ params: descriptor.params !== null &&
273
+ typeof descriptor.params === 'object' &&
274
+ Object.getPrototypeOf(descriptor.params) === Object.prototype
275
+ ? { ...descriptor.params }
276
+ : descriptor.params,
277
+ propStatus,
278
+ dragOverrides,
279
+ frame: effectFrame,
280
+ value: (cached === null || cached === void 0 ? void 0 : cached.value.definition) === descriptor.definition &&
281
+ cached.value.effectKey === effectKey
282
+ ? cached.value
283
+ : {
284
+ definition: descriptor.definition,
285
+ effectKey,
286
+ params,
287
+ memoized: true,
288
+ },
289
+ };
242
290
  });
243
291
  const isSame = previous !== null &&
244
- previous.length === resolved.length &&
245
- previous.every((p, i) => p.definition === resolved[i].descriptor.definition &&
246
- p.effectKey === resolved[i].effectKey);
292
+ previous.effects.length === prepared.length &&
293
+ previous.effects.every((effect, i) => effect === prepared[i].value);
247
294
  const next = isSame
248
- ? previous
249
- : resolved.map(({ descriptor, params, effectKey }) => ({
250
- definition: descriptor.definition,
251
- effectKey,
252
- params,
253
- memoized: true,
254
- }));
295
+ ? previous.effects
296
+ : prepared.map((effect) => effect.value);
255
297
  useCacheCommitEffect(() => {
256
- previousRef.current = next;
257
- }, [next]);
298
+ previousRef.current = { prepared, effects: next };
299
+ }, [next, prepared]);
258
300
  return next;
259
301
  };
260
302
  exports.useMemoizedEffects = useMemoizedEffects;
@@ -133,6 +133,9 @@ export declare const Internals: {
133
133
  readonly crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
134
134
  }, "ref"> & import("react").RefAttributes<HTMLVideoElement>>;
135
135
  readonly CompositionManager: import("react").Context<CompositionManagerContext>;
136
+ readonly CompositionManagerRefContext: import("react").Context<{
137
+ readonly current: CompositionManagerContext;
138
+ }>;
136
139
  readonly CompositionSetters: import("react").Context<import("./CompositionManagerContext.js").CompositionManagerSetters>;
137
140
  readonly VisualModePropStatusesContext: import("react").Context<import("./SequenceManager.js").VisualModePropStatuses>;
138
141
  readonly VisualModePropStatusesRefContext: import("react").Context<import("./SequenceManager.js").VisualModePropStatusesRef>;
@@ -180,6 +180,7 @@ exports.Internals = {
180
180
  resolveTrimProps: validate_start_from_props_js_1.resolveTrimProps,
181
181
  VideoForPreview: VideoForPreview_js_1.VideoForPreview,
182
182
  CompositionManager: CompositionManagerContext_js_1.CompositionManager,
183
+ CompositionManagerRefContext: CompositionManagerContext_js_1.CompositionManagerRefContext,
183
184
  CompositionSetters: CompositionManagerContext_js_1.CompositionSetters,
184
185
  VisualModePropStatusesContext: SequenceManager_js_1.VisualModePropStatusesContext,
185
186
  VisualModePropStatusesRefContext: SequenceManager_js_1.VisualModePropStatusesRefContext,
@@ -11,6 +11,7 @@ export declare const clampFrameToCompositionRange: (frame: number, durationInFra
11
11
  export declare const useTimelineContext: () => TimelineContextValue;
12
12
  export declare const usePlaybackRate: () => PlaybackRateContextValue;
13
13
  export declare const useTimelinePosition: () => number;
14
+ export declare const useTimelinePositionWithOptionalSubscription: (subscribe: boolean) => number;
14
15
  export declare const useIsInsideFreeze: () => boolean;
15
16
  export declare const useAbsoluteTimelinePosition: () => number;
16
17
  export declare const useTimelineSetFrameWithoutSeek: () => (u: import("react").SetStateAction<Record<string, number>>) => void;
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useBuffering = exports.usePlaying = exports.useTimelineSeekFrame = exports.useTimelineSetFrameWithoutSeek = exports.useAbsoluteTimelinePosition = exports.useIsInsideFreeze = exports.useTimelinePosition = exports.usePlaybackRate = exports.useTimelineContext = exports.clampFrameToCompositionRange = exports.getFrameForComposition = exports.getInitialFrameState = exports.persistCurrentFrame = void 0;
3
+ exports.useBuffering = exports.usePlaying = exports.useTimelineSeekFrame = exports.useTimelineSetFrameWithoutSeek = exports.useAbsoluteTimelinePosition = exports.useIsInsideFreeze = exports.useTimelinePositionWithOptionalSubscription = exports.useTimelinePosition = exports.usePlaybackRate = exports.useTimelineContext = exports.clampFrameToCompositionRange = exports.getFrameForComposition = exports.getInitialFrameState = exports.persistCurrentFrame = void 0;
4
4
  const react_1 = require("react");
5
5
  const sequence_activity_context_js_1 = require("./sequence-activity-context.js");
6
6
  const TimelineContext_js_1 = require("./TimelineContext.js");
7
7
  const use_remotion_environment_js_1 = require("./use-remotion-environment.js");
8
8
  const use_video_js_1 = require("./use-video.js");
9
+ // Reading TimelineContext and ignoring its value would still schedule frame
10
+ // updates. Inactive subscribers must read a separate, never-changing context.
11
+ const UnsubscribedTimelineContext = (0, react_1.createContext)(null);
9
12
  const makeKey = () => {
10
13
  return `remotion.time-all`;
11
14
  };
@@ -42,6 +45,9 @@ const useTimelinePositionFromContext = (state) => {
42
45
  const videoConfig = (0, use_video_js_1.useVideo)();
43
46
  const env = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
44
47
  const dormant = (0, react_1.useContext)(sequence_activity_context_js_1.SequenceActivityDormantContext);
48
+ if (state === null) {
49
+ return 0;
50
+ }
45
51
  if (!videoConfig) {
46
52
  return typeof window === 'undefined'
47
53
  ? 0
@@ -76,6 +82,20 @@ const useTimelinePosition = () => {
76
82
  return useTimelinePositionFromContext(state);
77
83
  };
78
84
  exports.useTimelinePosition = useTimelinePosition;
85
+ const useTimelinePositionWithOptionalSubscription = (subscribe) => {
86
+ const dormant = (0, react_1.useContext)(sequence_activity_context_js_1.SequenceActivityDormantContext);
87
+ const state = (0, react_1.useContext)(subscribe
88
+ ? dormant
89
+ ? sequence_activity_context_js_1.SequenceActivityTimelineContext
90
+ : TimelineContext_js_1.TimelineContext
91
+ : UnsubscribedTimelineContext);
92
+ const frame = useTimelinePositionFromContext(state);
93
+ if (subscribe && state === null) {
94
+ throw new Error('TimelineContext is not available. This hook must be used inside a <Player> or the Remotion Studio.');
95
+ }
96
+ return frame;
97
+ };
98
+ exports.useTimelinePositionWithOptionalSubscription = useTimelinePositionWithOptionalSubscription;
79
99
  const useIsInsideFreeze = () => {
80
100
  return (0, exports.useTimelineContext)().isInsideFreeze;
81
101
  };
@@ -1 +1,2 @@
1
+ export declare const useCurrentFrameWithOptionalSubscription: (subscribe: boolean) => number;
1
2
  export declare const useCurrentFrame: () => number;
@@ -1,16 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useCurrentFrame = void 0;
3
+ exports.useCurrentFrame = exports.useCurrentFrameWithOptionalSubscription = void 0;
4
4
  const react_1 = require("react");
5
5
  const CanUseRemotionHooks_js_1 = require("./CanUseRemotionHooks.js");
6
6
  const SequenceContext_js_1 = require("./SequenceContext.js");
7
7
  const timeline_position_state_js_1 = require("./timeline-position-state.js");
8
8
  const use_remotion_environment_js_1 = require("./use-remotion-environment.js");
9
- /*
10
- * @description Retrieves the current frame of the video within a component. Frames are 0-indexed, and if the component is wrapped in a `<Sequence>`, it returns the frame relative to when the Sequence starts.
11
- * @see [Documentation](https://www.remotion.dev/docs/use-current-frame)
12
- */
13
- const useCurrentFrame = () => {
9
+ const useCurrentFrameWithOptionalSubscription = (subscribe) => {
14
10
  var _a;
15
11
  const canUseRemotionHooks = (0, react_1.useContext)(CanUseRemotionHooks_js_1.CanUseRemotionHooks);
16
12
  const env = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
@@ -20,11 +16,22 @@ const useCurrentFrame = () => {
20
16
  }
21
17
  throw new Error(`useCurrentFrame() can only be called inside a component that was registered as a composition. See https://www.remotion.dev/docs/the-fundamentals#defining-compositions`);
22
18
  }
23
- const frame = (0, timeline_position_state_js_1.useTimelinePosition)();
19
+ const frame = (0, timeline_position_state_js_1.useTimelinePositionWithOptionalSubscription)(subscribe);
24
20
  const context = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
21
+ if (!subscribe) {
22
+ return 0;
23
+ }
25
24
  const contextOffset = context
26
25
  ? context.cumulatedFrom + context.relativeFrom
27
26
  : 0;
28
27
  return (frame - contextOffset) * ((_a = context === null || context === void 0 ? void 0 : context.playbackRate) !== null && _a !== void 0 ? _a : 1);
29
28
  };
29
+ exports.useCurrentFrameWithOptionalSubscription = useCurrentFrameWithOptionalSubscription;
30
+ /*
31
+ * @description Retrieves the current frame of the video within a component. Frames are 0-indexed, and if the component is wrapped in a `<Sequence>`, it returns the frame relative to when the Sequence starts.
32
+ * @see [Documentation](https://www.remotion.dev/docs/use-current-frame)
33
+ */
34
+ const useCurrentFrame = () => {
35
+ return (0, exports.useCurrentFrameWithOptionalSubscription)(true);
36
+ };
30
37
  exports.useCurrentFrame = useCurrentFrame;
@@ -12,10 +12,10 @@ function validateDurationInFrames(durationInFrames, options) {
12
12
  if (durationInFrames <= 0) {
13
13
  throw new TypeError(`The "durationInFrames" prop ${component} must be positive, but got ${durationInFrames}.`);
14
14
  }
15
- if (!allowFloats && durationInFrames % 1 !== 0) {
16
- throw new TypeError(`The "durationInFrames" prop ${component} must be an integer, but got ${durationInFrames}.`);
17
- }
18
15
  if (!Number.isFinite(durationInFrames)) {
19
16
  throw new TypeError(`The "durationInFrames" prop ${component} must be finite, but got ${durationInFrames}.`);
20
17
  }
18
+ if (!allowFloats && durationInFrames % 1 !== 0) {
19
+ throw new TypeError(`The "durationInFrames" prop ${component} must be an integer, but got ${durationInFrames}.`);
20
+ }
21
21
  }
@@ -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.536";
6
+ export declare const VERSION = "4.0.537";
@@ -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.536';
10
+ exports.VERSION = '4.0.537';