remotion
Version:
Make videos programmatically
446 lines (445 loc) • 25.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SequenceWithoutFrom = exports.Sequence = exports.SequenceWithoutSchema = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
/* eslint-disable @typescript-eslint/no-use-before-define */
const react_1 = require("react");
const AbsoluteFillElement_js_1 = require("./AbsoluteFillElement.js");
const enable_sequence_stack_traces_js_1 = require("./enable-sequence-stack-traces.js");
const freeze_js_1 = require("./freeze.js");
const interactivity_schema_js_1 = require("./interactivity-schema.js");
const sequence_crop_js_1 = require("./sequence-crop.js");
const sequence_order_marker_js_1 = require("./sequence-order-marker.js");
const SequenceContext_js_1 = require("./SequenceContext.js");
const SequenceManager_js_1 = require("./SequenceManager.js");
const is_inside_series_js_1 = require("./series/is-inside-series.js");
const timeline_position_state_js_1 = require("./timeline-position-state.js");
const use_premounting_js_1 = require("./use-premounting.js");
const use_remotion_environment_js_1 = require("./use-remotion-environment.js");
const use_sequence_registration_js_1 = require("./use-sequence-registration.js");
const use_video_config_js_1 = require("./use-video-config.js");
const v5_flag_js_1 = require("./v5-flag.js");
const with_interactivity_schema_js_1 = require("./with-interactivity-schema.js");
const EMPTY_EFFECTS = [];
const RegularSequenceRefForwardingFunction = ({ from = 0, trimBefore = 0, freeze, durationInFrames = Infinity, children, name, height, width, showInTimeline = true, hidden = false, controls, _remotionInternalEffects, _remotionInternalLoopDisplay: loopDisplay, _remotionInternalStack: stack, _remotionInternalDocumentationLink: documentationLink, _remotionInternalSingleChildComponent: singleChildComponent, _remotionInternalPremountDisplay: premountDisplay, _remotionInternalPostmountDisplay: postmountDisplay, _remotionInternalIsMedia: isMedia, outlineRef: passedRefForOutline, cropLeft, cropRight, cropTop, cropBottom, ...other }, ref) => {
var _a, _b, _c, _d;
const { layout = 'absolute-fill' } = other;
const [id] = (0, react_1.useState)(() => String(Math.random()));
const parentSequence = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
const cumulatedFrom = parentSequence
? parentSequence.cumulatedFrom + parentSequence.relativeFrom
: 0;
if (layout !== 'absolute-fill' && layout !== 'none') {
throw new TypeError(`The layout prop of <Sequence /> expects either "absolute-fill" or "none", but you passed: ${layout}`);
}
const cropProps = { cropLeft, cropRight, cropTop, cropBottom };
const hasCropProp = Object.values(cropProps).some((value) => value !== undefined);
if (layout === 'none' && hasCropProp) {
throw new TypeError('The cropLeft, cropRight, cropTop and cropBottom props of <Sequence /> are only supported with layout="absolute-fill".');
}
(0, sequence_crop_js_1.validateSequenceCrop)(cropProps);
const { left: resolvedCropLeft, right: resolvedCropRight, top: resolvedCropTop, bottom: resolvedCropBottom, } = (0, sequence_crop_js_1.resolveSequenceCrop)(cropProps);
// @ts-expect-error
if (layout === 'none' && typeof other.style !== 'undefined') {
throw new TypeError('If layout="none", you may not pass a style. Passed: ' +
// @ts-expect-error
JSON.stringify(other.style));
}
if (typeof durationInFrames !== 'number') {
throw new TypeError(`You passed to durationInFrames an argument of type ${typeof durationInFrames}, but it must be a number.`);
}
if (durationInFrames <= 0) {
throw new TypeError(`durationInFrames must be positive, but got ${durationInFrames}`);
}
if (typeof from !== 'number') {
throw new TypeError(`You passed to the "from" props of your <Sequence> an argument of type ${typeof from}, but it must be a number.`);
}
if (!Number.isFinite(from)) {
throw new TypeError(`The "from" prop of a sequence must be finite, but got ${from}.`);
}
if (typeof trimBefore !== 'number') {
throw new TypeError(`You passed to the "trimBefore" prop of your <Sequence> an argument of type ${typeof trimBefore}, but it must be a number.`);
}
if (trimBefore < 0) {
throw new TypeError(`The "trimBefore" prop of <Sequence /> must be greater than or equal to 0, but got ${trimBefore}.`);
}
if (Number.isNaN(trimBefore)) {
throw new TypeError('The "trimBefore" prop of <Sequence /> must be a real number, but it is NaN.');
}
if (!Number.isFinite(trimBefore)) {
throw new TypeError(`The "trimBefore" prop of <Sequence /> must be finite, but it is ${trimBefore}.`);
}
if (typeof freeze !== 'undefined' && freeze !== null) {
if (typeof freeze !== 'number') {
throw new TypeError(`The "freeze" prop of <Sequence /> must be a number, but is of type ${typeof freeze}.`);
}
if (Number.isNaN(freeze)) {
throw new TypeError(`The "freeze" prop of <Sequence /> must be a real number, but it is NaN.`);
}
if (!Number.isFinite(freeze)) {
throw new TypeError(`The "freeze" prop of <Sequence /> must be finite, but it is ${freeze}.`);
}
}
const absoluteFrame = (0, timeline_position_state_js_1.useTimelinePosition)();
const videoConfig = (0, use_video_config_js_1.useVideoConfig)();
const effectiveRelativeFrom = from - trimBefore;
const absoluteFrom = ((_a = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.absoluteFrom) !== null && _a !== void 0 ? _a : 0) + effectiveRelativeFrom;
const parentSequenceDuration = parentSequence
? Math.min(parentSequence.durationInFrames - effectiveRelativeFrom, durationInFrames)
: durationInFrames;
const actualDurationInFrames = Math.max(0, Math.min(videoConfig.durationInFrames - from, parentSequenceDuration));
const sequenceRegistrationEnabled = (0, react_1.useContext)(SequenceManager_js_1.SequenceRegistrationContext);
const wrapperRefForOutline = (0, react_1.useRef)(null);
const refForOutline = other.layout === 'none'
? (passedRefForOutline !== null && passedRefForOutline !== void 0 ? passedRefForOutline : null)
: (passedRefForOutline !== null && passedRefForOutline !== void 0 ? passedRefForOutline : wrapperRefForOutline);
const premounting = (0, react_1.useMemo)(() => {
// || is intentional, ?? would not trigger on `false`
return ((parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.premounting) ||
Boolean(other._remotionInternalIsPremounting));
}, [other._remotionInternalIsPremounting, parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.premounting]);
const postmounting = (0, react_1.useMemo)(() => {
// || is intentional, ?? would not trigger on `false`
return ((parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.postmounting) ||
Boolean(other._remotionInternalIsPostmounting));
}, [other._remotionInternalIsPostmounting, parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.postmounting]);
// `cumulatedNegativeFrom` answers: "How many frames of this media have
// already elapsed before the first visible frame of this sequence?"
//
// This is intentionally based on the effective sequence start, not on adding
// all negative `from` values. See the asset-calculation tests for:
// - "Should calculate startFrom correctly with negative offset (Html5Audio)"
// - "same as above, but with <Sequence from={0}> inbetween"
// - "same as above, but a positive child offset cancels part of the negative parent offset"
//
// In particular, <Sequence from={-20}><Sequence from={10}> should have a
// 10-frame pre-roll, because the positive child offset cancels part of the
// negative parent offset. But <Sequence from={10}><Sequence from={-5}>
// should still trim 5 frames from the media once the parent starts.
const currentSequenceStart = cumulatedFrom + effectiveRelativeFrom;
const parentSequenceStart = parentSequence
? parentSequence.cumulatedFrom + parentSequence.relativeFrom
: 0;
const parentFirstFrame = parentSequence
? parentSequenceStart - parentSequence.cumulatedNegativeFrom
: 0;
const firstFrame = Math.max(0, parentFirstFrame, currentSequenceStart);
const cumulatedNegativeFrom = currentSequenceStart - firstFrame;
const contextValue = (0, react_1.useMemo)(() => {
var _a, _b, _c;
return {
absoluteFrom,
cumulatedFrom,
relativeFrom: effectiveRelativeFrom,
cumulatedNegativeFrom,
durationInFrames: actualDurationInFrames,
parentFrom: (_a = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.relativeFrom) !== null && _a !== void 0 ? _a : 0,
id,
height: (_b = height !== null && height !== void 0 ? height : parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.height) !== null && _b !== void 0 ? _b : null,
width: (_c = width !== null && width !== void 0 ? width : parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.width) !== null && _c !== void 0 ? _c : null,
premounting,
postmounting,
premountDisplay: premountDisplay !== null && premountDisplay !== void 0 ? premountDisplay : null,
postmountDisplay: postmountDisplay !== null && postmountDisplay !== void 0 ? postmountDisplay : null,
};
}, [
cumulatedFrom,
absoluteFrom,
effectiveRelativeFrom,
actualDurationInFrames,
parentSequence,
id,
height,
width,
premounting,
postmounting,
premountDisplay,
postmountDisplay,
cumulatedNegativeFrom,
]);
const timelineClipName = (0, react_1.useMemo)(() => {
return name !== null && name !== void 0 ? name : '';
}, [name]);
const resolvedDocumentationLink = documentationLink !== null && documentationLink !== void 0 ? documentationLink : 'https://www.remotion.dev/docs/sequence';
const env = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
const isInsideSeries = (0, react_1.useContext)(is_inside_series_js_1.IsInsideSeriesContext);
// Our assumption: Stack doesnt' change. After we symbolicate we assign it a nodePath
// and if it changes, it would lead to-remounting of the sequence.
const stackRef = (0, react_1.useRef)(null);
stackRef.current = controls
? ((_c = (_b = (0, enable_sequence_stack_traces_js_1.getStackForControls)(controls)) !== null && _b !== void 0 ? _b : stack) !== null && _c !== void 0 ? _c : null)
: (stack !== null && stack !== void 0 ? stack : null);
const registeredFrozenFrame = typeof freeze === 'number' ? freeze : null;
const registeredTrimBefore = trimBefore === 0 ? null : trimBefore;
const parentCumulatedNegativeFrom = (_d = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.cumulatedNegativeFrom) !== null && _d !== void 0 ? _d : 0;
const startMediaFrom = isMedia && isMedia.type !== 'image'
? isMedia.data.startMediaFrom +
parentCumulatedNegativeFrom -
cumulatedNegativeFrom
: null;
const mediaFrameAtSequenceZero = isMedia && isMedia.type !== 'image'
? isMedia.data.startMediaFrom + parentCumulatedNegativeFrom
: null;
const frozenMediaFrame = isMedia && isMedia.type !== 'image' && mediaFrameAtSequenceZero !== null
? registeredFrozenFrame === null
? null
: mediaFrameAtSequenceZero +
(loopDisplay
? registeredFrozenFrame % loopDisplay.durationInFrames
: registeredFrozenFrame) *
isMedia.data.playbackRate
: null;
const controlsSchema = controls === null || controls === void 0 ? void 0 : controls.schema;
const controlsRuntimeValues = controls === null || controls === void 0 ? void 0 : controls.runtimeValues;
const controlsOverrideId = controls === null || controls === void 0 ? void 0 : controls.overrideId;
const controlsSupportsEffects = controls === null || controls === void 0 ? void 0 : controls.supportsEffects;
const controlsComponentIdentity = controls === null || controls === void 0 ? void 0 : controls.componentIdentity;
const controlsComponentName = controls === null || controls === void 0 ? void 0 : controls.componentName;
const controlsVideoConfigValues = controls === null || controls === void 0 ? void 0 : controls.videoConfigValues;
const effectRuntimeValues = (0, react_1.useMemo)(() => {
var _a;
return (_a = _remotionInternalEffects === null || _remotionInternalEffects === void 0 ? void 0 : _remotionInternalEffects.runtimeValues) !== null && _a !== void 0 ? _a : null;
}, [_remotionInternalEffects]);
const registrationControls = (0, react_1.useMemo)(() => {
if (controlsSchema === undefined ||
controlsRuntimeValues === undefined ||
controlsOverrideId === undefined ||
controlsSupportsEffects === undefined ||
controlsComponentIdentity === undefined ||
controlsComponentName === undefined ||
controlsVideoConfigValues === undefined) {
return null;
}
return {
schema: controlsSchema,
runtimeValues: controlsRuntimeValues,
overrideId: controlsOverrideId,
supportsEffects: controlsSupportsEffects,
componentIdentity: controlsComponentIdentity,
componentName: controlsComponentName,
videoConfigValues: controlsVideoConfigValues,
};
}, [
controlsComponentIdentity,
controlsComponentName,
controlsVideoConfigValues,
controlsOverrideId,
controlsRuntimeValues,
controlsSchema,
controlsSupportsEffects,
]);
const getSequenceForRegistration = (0, react_1.useCallback)(() => {
var _a, _b, _c;
if (isMedia) {
if (isMedia.type === 'image') {
return {
type: 'image',
controls: registrationControls,
effects: _remotionInternalEffects !== null && _remotionInternalEffects !== void 0 ? _remotionInternalEffects : EMPTY_EFFECTS,
effectRuntimeValues,
displayName: timelineClipName,
documentationLink: resolvedDocumentationLink,
duration: actualDurationInFrames,
from,
trimBefore: registeredTrimBefore,
id,
loopDisplay,
parent: (_a = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.id) !== null && _a !== void 0 ? _a : null,
postmountDisplay: postmountDisplay !== null && postmountDisplay !== void 0 ? postmountDisplay : null,
premountDisplay: premountDisplay !== null && premountDisplay !== void 0 ? premountDisplay : null,
showInTimeline,
timelineOrder: null,
src: isMedia.src,
getStack: () => stackRef.current,
refForOutline: refForOutline !== null && refForOutline !== void 0 ? refForOutline : null,
isInsideSeries,
frozenFrame: registeredFrozenFrame,
singleChildComponent: singleChildComponent !== null && singleChildComponent !== void 0 ? singleChildComponent : null,
};
}
return {
type: isMedia.type,
controls: registrationControls,
effects: _remotionInternalEffects !== null && _remotionInternalEffects !== void 0 ? _remotionInternalEffects : EMPTY_EFFECTS,
effectRuntimeValues,
displayName: timelineClipName,
documentationLink: resolvedDocumentationLink,
doesVolumeChange: isMedia.data.doesVolumeChange,
duration: actualDurationInFrames,
from,
trimBefore: registeredTrimBefore,
id,
loopDisplay,
parent: (_b = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.id) !== null && _b !== void 0 ? _b : null,
playbackRate: isMedia.data.playbackRate,
postmountDisplay: postmountDisplay !== null && postmountDisplay !== void 0 ? postmountDisplay : null,
premountDisplay: premountDisplay !== null && premountDisplay !== void 0 ? premountDisplay : null,
showInTimeline,
timelineOrder: null,
src: isMedia.data.src,
getStack: () => stackRef.current,
startMediaFrom: startMediaFrom !== null && startMediaFrom !== void 0 ? startMediaFrom : isMedia.data.startMediaFrom,
mediaFrameAtSequenceZero,
volume: isMedia.data.volumes,
muted: isMedia.data.muted,
refForOutline: refForOutline !== null && refForOutline !== void 0 ? refForOutline : null,
isInsideSeries,
frozenFrame: registeredFrozenFrame,
frozenMediaFrame,
singleChildComponent: singleChildComponent !== null && singleChildComponent !== void 0 ? singleChildComponent : null,
};
}
return {
from,
trimBefore: registeredTrimBefore,
duration: actualDurationInFrames,
id,
displayName: timelineClipName,
documentationLink: resolvedDocumentationLink,
parent: (_c = parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.id) !== null && _c !== void 0 ? _c : null,
type: 'sequence',
showInTimeline,
timelineOrder: null,
loopDisplay,
getStack: () => stackRef.current,
premountDisplay: premountDisplay !== null && premountDisplay !== void 0 ? premountDisplay : null,
postmountDisplay: postmountDisplay !== null && postmountDisplay !== void 0 ? postmountDisplay : null,
controls: registrationControls,
effects: _remotionInternalEffects !== null && _remotionInternalEffects !== void 0 ? _remotionInternalEffects : EMPTY_EFFECTS,
effectRuntimeValues,
refForOutline: refForOutline !== null && refForOutline !== void 0 ? refForOutline : null,
isInsideSeries,
frozenFrame: registeredFrozenFrame,
singleChildComponent: singleChildComponent !== null && singleChildComponent !== void 0 ? singleChildComponent : null,
};
}, [
id,
timelineClipName,
parentSequence === null || parentSequence === void 0 ? void 0 : parentSequence.id,
actualDurationInFrames,
from,
registeredTrimBefore,
showInTimeline,
loopDisplay,
premountDisplay,
postmountDisplay,
registrationControls,
_remotionInternalEffects,
effectRuntimeValues,
isMedia,
resolvedDocumentationLink,
refForOutline,
isInsideSeries,
registeredFrozenFrame,
startMediaFrom,
mediaFrameAtSequenceZero,
frozenMediaFrame,
singleChildComponent,
]);
(0, use_sequence_registration_js_1.useSequenceRegistration)({
getSequence: env.isStudio || sequenceRegistrationEnabled
? getSequenceForRegistration
: null,
id,
});
// Ceil to support floats
// https://github.com/remotion-dev/remotion/issues/2958
const endThreshold = Math.ceil(cumulatedFrom + from + durationInFrames - 1);
const content = absoluteFrame < cumulatedFrom + from
? null
: absoluteFrame > endThreshold
? null
: children;
const frozenContent = content === null || typeof freeze === 'undefined' || freeze === null ? (content) : (jsx_runtime_1.jsx(freeze_js_1.Freeze, { frame: freeze, children: content }));
const styleIfThere = other.layout === 'none' ? undefined : other.style;
const cropClipPath = (0, sequence_crop_js_1.getSequenceCropClipPath)({
left: resolvedCropLeft,
right: resolvedCropRight,
top: resolvedCropTop,
bottom: resolvedCropBottom,
style: styleIfThere,
});
const sequenceRef = (0, react_1.useCallback)((node) => {
wrapperRefForOutline.current = node;
if (typeof ref === 'function') {
ref(node);
}
else if (ref) {
ref.current = node;
}
}, [ref]);
const defaultStyle = (0, react_1.useMemo)(() => {
return {
flexDirection: undefined,
...(width ? { width } : {}),
...(height ? { height } : {}),
...(styleIfThere !== null && styleIfThere !== void 0 ? styleIfThere : {}),
...(cropClipPath
? {
clipPath: cropClipPath,
}
: {}),
};
}, [cropClipPath, height, styleIfThere, width]);
if (ref !== null && layout === 'none') {
throw new TypeError('It is not supported to pass both a `ref` and `layout="none"` to <Sequence />.');
}
if (hidden) {
return env.isStudio ? (jsx_runtime_1.jsx(sequence_order_marker_js_1.SequenceOrderMarker, { sequenceId: id, children: null })) : null;
}
const sequence = (jsx_runtime_1.jsx(SequenceContext_js_1.SequenceContext.Provider, { value: contextValue, children: frozenContent === null ? null : other.layout === 'none' ? (frozenContent) : (jsx_runtime_1.jsx(AbsoluteFillElement_js_1.AbsoluteFillElement, { ref: sequenceRef, style: defaultStyle, className: other.className, children: frozenContent })) }));
return env.isStudio ? (jsx_runtime_1.jsx(sequence_order_marker_js_1.SequenceOrderMarker, { sequenceId: id, children: sequence })) : (sequence);
};
const RegularSequence = (0, react_1.forwardRef)(RegularSequenceRefForwardingFunction);
const PremountedPostmountedSequenceRefForwardingFunction = (props, ref) => {
if (props.layout === 'none') {
throw new Error('`<Sequence>` with `premountFor` and `postmountFor` props does not support layout="none"');
}
const { style: passedStyle, from = 0, durationInFrames = Infinity, premountFor = 0, postmountFor = 0, styleWhilePremounted, styleWhilePostmounted, ...otherProps } = props;
const { freezeFrame, isPremountingOrPostmounting, postmountingActive, premountingActive, premountingStyle, } = (0, use_premounting_js_1.usePremounting)({
from,
durationInFrames,
premountFor,
postmountFor,
style: passedStyle !== null && passedStyle !== void 0 ? passedStyle : null,
styleWhilePremounted: styleWhilePremounted !== null && styleWhilePremounted !== void 0 ? styleWhilePremounted : null,
styleWhilePostmounted: styleWhilePostmounted !== null && styleWhilePostmounted !== void 0 ? styleWhilePostmounted : null,
hideWhilePremounted: 'opacity',
});
return (jsx_runtime_1.jsx(freeze_js_1.Freeze, { frame: freezeFrame, active: isPremountingOrPostmounting, children: jsx_runtime_1.jsx(SequenceInner, { ref: ref, from: from, durationInFrames: durationInFrames, style: premountingStyle !== null && premountingStyle !== void 0 ? premountingStyle : undefined, _remotionInternalPremountDisplay: premountFor, _remotionInternalPostmountDisplay: postmountFor, _remotionInternalIsPremounting: premountingActive, _remotionInternalIsPostmounting: postmountingActive, ...otherProps }) }));
};
const PremountedPostmountedSequence = (0, react_1.forwardRef)(PremountedPostmountedSequenceRefForwardingFunction);
const SequenceRefForwardingFunction = (props, ref) => {
var _a;
const env = (0, use_remotion_environment_js_1.useRemotionEnvironment)();
const { fps } = (0, use_video_config_js_1.useVideoConfig)();
if (props.layout !== 'none' && !env.isRendering) {
const effectivePremountFor = v5_flag_js_1.ENABLE_V5_BREAKING_CHANGES
? ((_a = props.premountFor) !== null && _a !== void 0 ? _a : fps)
: props.premountFor;
if (effectivePremountFor || props.postmountFor) {
return (jsx_runtime_1.jsx(PremountedPostmountedSequence, { ref: ref, ...props, premountFor: effectivePremountFor }));
}
}
return jsx_runtime_1.jsx(RegularSequence, { ...props, ref: ref });
};
const SequenceInner = (0, react_1.forwardRef)(SequenceRefForwardingFunction);
exports.SequenceWithoutSchema = SequenceInner;
/*
* @description A component that time-shifts its children and wraps them in an absolutely positioned <div>.
* @see [Documentation](https://www.remotion.dev/docs/sequence)
*/
exports.Sequence = (0, with_interactivity_schema_js_1.withInteractivitySchema)({
Component: SequenceInner,
componentName: '<Sequence>',
componentIdentity: 'dev.remotion.remotion.Sequence',
schema: interactivity_schema_js_1.sequenceSchema,
supportsEffects: false,
});
exports.SequenceWithoutFrom = (0, with_interactivity_schema_js_1.withInteractivitySchema)({
Component: SequenceInner,
componentName: '<Sequence>',
componentIdentity: null,
schema: interactivity_schema_js_1.sequenceSchemaWithoutFrom,
supportsEffects: false,
});