UNPKG

remotion

Version:

Make videos programmatically

269 lines (268 loc) • 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Img = exports.imgSchema = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("react"); const index_js_1 = require("./canvas-image/index.js"); const enable_sequence_stack_traces_js_1 = require("./enable-sequence-stack-traces.js"); const get_cross_origin_value_js_1 = require("./get-cross-origin-value.js"); const prefetch_js_1 = require("./prefetch.js"); const sequence_field_schema_js_1 = require("./sequence-field-schema.js"); const Sequence_js_1 = require("./Sequence.js"); const SequenceContext_js_1 = require("./SequenceContext.js"); const truncate_src_for_label_js_1 = require("./truncate-src-for-label.js"); const use_buffer_state_js_1 = require("./use-buffer-state.js"); const use_delay_render_js_1 = require("./use-delay-render.js"); const use_remotion_environment_js_1 = require("./use-remotion-environment.js"); const wrap_in_schema_js_1 = require("./wrap-in-schema.js"); function exponentialBackoff(errorCount) { return 1000 * 2 ** (errorCount - 1); } const ImgContent = ({ onError, maxRetries = 2, src, pauseWhenLoading, delayRenderRetries, delayRenderTimeoutInMilliseconds, onImageFrame, crossOrigin, decoding, ref, refForOutline, ...props }) => { const imageRef = (0, react_1.useRef)(null); const errors = (0, react_1.useRef)({}); const { delayPlayback } = (0, use_buffer_state_js_1.useBufferState)(); const sequenceContext = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext); const _propsValid = true; if (!_propsValid) { throw new Error('typecheck error'); } const imageCallbackRef = (0, react_1.useCallback)((img) => { imageRef.current = img; refForOutline.current = img; if (typeof ref === 'function') { ref(img); } else if (ref) { ref.current = img; } }, [ref, refForOutline]); const actualSrc = (0, prefetch_js_1.usePreload)(src); const retryIn = (0, react_1.useCallback)((timeout) => { if (!imageRef.current) { return; } const currentSrc = imageRef.current.src; setTimeout(() => { var _a; if (!imageRef.current) { // Component has been unmounted, do not retry return; } const newSrc = (_a = imageRef.current) === null || _a === void 0 ? void 0 : _a.src; if (newSrc !== currentSrc) { // src has changed, do not retry return; } imageRef.current.removeAttribute('src'); imageRef.current.setAttribute('src', newSrc); }, timeout); }, []); const { delayRender, continueRender, cancelRender } = (0, use_delay_render_js_1.useDelayRender)(); const didGetError = (0, react_1.useCallback)((e) => { var _a, _b, _c, _d, _e, _f, _g; var _h, _j, _k, _l; if (!errors.current) { return; } errors.current[(_a = imageRef.current) === null || _a === void 0 ? void 0 : _a.src] = ((_h = errors.current[(_b = imageRef.current) === null || _b === void 0 ? void 0 : _b.src]) !== null && _h !== void 0 ? _h : 0) + 1; if (onError && ((_j = errors.current[(_c = imageRef.current) === null || _c === void 0 ? void 0 : _c.src]) !== null && _j !== void 0 ? _j : 0) > maxRetries) { onError(e); return; } if (((_k = errors.current[(_d = imageRef.current) === null || _d === void 0 ? void 0 : _d.src]) !== null && _k !== void 0 ? _k : 0) <= maxRetries) { const backoff = exponentialBackoff((_l = errors.current[(_e = imageRef.current) === null || _e === void 0 ? void 0 : _e.src]) !== null && _l !== void 0 ? _l : 0); // eslint-disable-next-line no-console console.warn(`Could not load image with source ${(0, truncate_src_for_label_js_1.truncateSrcForLabel)((_f = imageRef.current) === null || _f === void 0 ? void 0 : _f.src)}, retrying again in ${backoff}ms`); retryIn(backoff); return; } try { cancelRender('Error loading image with src: ' + (0, truncate_src_for_label_js_1.truncateSrcForLabel)((_g = imageRef.current) === null || _g === void 0 ? void 0 : _g.src)); } catch (_m) { // cancelRender() intentionally throws after storing the error in scope. // In async image callbacks, we rely on the stored error for renderer propagation. } }, [cancelRender, maxRetries, onError, retryIn]); if (typeof window !== 'undefined') { const isPremounting = Boolean(sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.premounting); const isPostmounting = Boolean(sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.postmounting); // eslint-disable-next-line react-hooks/rules-of-hooks (0, react_1.useLayoutEffect)(() => { var _a, _b; if (((_b = (_a = window.process) === null || _a === void 0 ? void 0 : _a.env) === null || _b === void 0 ? void 0 : _b.NODE_ENV) === 'test') { if (imageRef.current) { imageRef.current.src = actualSrc; } return; } const { current } = imageRef; if (!current) { return; } const newHandle = delayRender('Loading <Img> with src=' + (0, truncate_src_for_label_js_1.truncateSrcForLabel)(actualSrc), { retries: delayRenderRetries !== null && delayRenderRetries !== void 0 ? delayRenderRetries : undefined, timeoutInMilliseconds: delayRenderTimeoutInMilliseconds !== null && delayRenderTimeoutInMilliseconds !== void 0 ? delayRenderTimeoutInMilliseconds : undefined, }); const unblock = pauseWhenLoading && !isPremounting && !isPostmounting ? delayPlayback().unblock : () => undefined; let unmounted = false; const onComplete = () => { var _a, _b, _c; var _d; // the decode() promise isn't cancelable -- it may still resolve after unmounting if (unmounted) { continueRender(newHandle); return; } if (((_d = errors.current[(_a = imageRef.current) === null || _a === void 0 ? void 0 : _a.src]) !== null && _d !== void 0 ? _d : 0) > 0) { delete errors.current[(_b = imageRef.current) === null || _b === void 0 ? void 0 : _b.src]; // eslint-disable-next-line no-console console.info(`Retry successful - ${(0, truncate_src_for_label_js_1.truncateSrcForLabel)((_c = imageRef.current) === null || _c === void 0 ? void 0 : _c.src)} is now loaded`); } if (current) { onImageFrame === null || onImageFrame === void 0 ? void 0 : onImageFrame(current); } unblock(); continueRender(newHandle); }; if (!imageRef.current) { onComplete(); return; } current.src = actualSrc; current .decode() .then(onComplete) .catch((err) => { // fall back to onload event if decode() fails // eslint-disable-next-line no-console console.warn(err); // HTMLImageElement.complete is also true for broken images (e.g. 404), // so only treat it as loaded if intrinsic dimensions are available. if (current.complete && current.naturalWidth > 0 && current.naturalHeight > 0) { onComplete(); } else { current.addEventListener('load', onComplete); } }); // If tag gets unmounted, clear pending handles because image is not going to load return () => { unmounted = true; current.removeEventListener('load', onComplete); unblock(); continueRender(newHandle); }; }, [ actualSrc, delayPlayback, delayRenderRetries, delayRenderTimeoutInMilliseconds, pauseWhenLoading, isPremounting, isPostmounting, onImageFrame, continueRender, delayRender, ]); } const { isClientSideRendering, isRendering } = (0, use_remotion_environment_js_1.useRemotionEnvironment)(); const crossOriginValue = (0, get_cross_origin_value_js_1.getCrossOriginValue)({ crossOrigin, requestsVideoFrame: false, isClientSideRendering, }); // src gets set once we've loaded and decoded the image. return (jsx_runtime_1.jsx("img", { ...props, ref: imageCallbackRef, crossOrigin: crossOriginValue, onError: didGetError, decoding: isRendering ? 'sync' : decoding })); }; const NativeImgInner = ({ hidden, name, stack, showInTimeline, src, from, durationInFrames, _experimentalControls: controls, _remotionInternalRefForOutline: refForOutline, ...props }) => { if (!src) { throw new Error('No "src" prop was passed to <Img>.'); } return (jsx_runtime_1.jsx(Sequence_js_1.Sequence, { layout: "none", from: from !== null && from !== void 0 ? from : 0, durationInFrames: durationInFrames !== null && durationInFrames !== void 0 ? durationInFrames : Infinity, _remotionInternalStack: stack, _remotionInternalDocumentationLink: name === undefined ? 'https://www.remotion.dev/docs/img' : undefined, _remotionInternalIsMedia: { type: 'image', src }, name: name !== null && name !== void 0 ? name : '<Img>', _experimentalControls: controls, showInTimeline: showInTimeline !== null && showInTimeline !== void 0 ? showInTimeline : true, hidden: hidden, _remotionInternalRefForOutline: refForOutline, children: jsx_runtime_1.jsx(ImgContent, { src: src, refForOutline: refForOutline, ...props }) })); }; const CanvasImageWithPrivateProps = index_js_1.CanvasImage; exports.imgSchema = { durationInFrames: sequence_field_schema_js_1.durationInFramesField, from: sequence_field_schema_js_1.fromField, ...sequence_field_schema_js_1.sequenceVisualStyleSchema, hidden: sequence_field_schema_js_1.hiddenField, }; const imgCanvasFallbackIncompatibleProps = new Set([ 'alt', 'crossOrigin', 'decoding', 'fetchPriority', 'loading', 'onError', 'onImageFrame', 'onLoad', 'sizes', 'srcSet', 'useMap', ]); const getIncompatiblePropNames = (props) => Object.keys(props).filter((key) => props[key] !== undefined && imgCanvasFallbackIncompatibleProps.has(key)); const formatPropList = (props) => { return props.map((prop) => `"${prop}"`).join(', '); }; const validateCanvasImageFallbackProps = ({ props, ref, width, height, }) => { if (typeof width === 'string' || typeof height === 'string') { throw new Error('The "width" and "height" props must be numbers on <Img> when effects are passed, because <Img> renders a <CanvasImage>. Use numeric props or CSS dimensions in "style".'); } const conflictingProps = getIncompatiblePropNames(props); if (ref !== null && ref !== undefined) { conflictingProps.unshift('ref'); } if (conflictingProps.length === 0) { return; } throw new Error(`The ${formatPropList(conflictingProps)} prop${conflictingProps.length === 1 ? '' : 's'} cannot be used on <Img> when effects are passed, because <Img> renders a <canvas> instead of a native <img>. Remove ${conflictingProps.length === 1 ? 'this prop' : 'these props'}.`); }; const getFitFromObjectFit = (style) => { const objectFit = style === null || style === void 0 ? void 0 : style.objectFit; if (objectFit === 'fill' || objectFit === 'contain' || objectFit === 'cover') { return objectFit; } return undefined; }; const ImgInner = ({ effects = [], ref, hidden, name, stack, showInTimeline, src, from, durationInFrames, _experimentalControls: controls, width, height, className, style, id, pauseWhenLoading, maxRetries, delayRenderRetries, delayRenderTimeoutInMilliseconds, ...props }) => { var _a; const refForOutline = (0, react_1.useRef)(null); if (effects.length === 0) { return (jsx_runtime_1.jsx(NativeImgInner, { ...props, ref: ref, hidden: hidden, name: name, stack: stack, showInTimeline: showInTimeline, src: src, from: from, durationInFrames: durationInFrames, _experimentalControls: controls, width: width, height: height, className: className, style: style, id: id, pauseWhenLoading: pauseWhenLoading, maxRetries: maxRetries, delayRenderRetries: delayRenderRetries, delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds, _remotionInternalRefForOutline: refForOutline })); } if (!src) { throw new Error('No "src" prop was passed to <Img>.'); } validateCanvasImageFallbackProps({ props, ref, width, height, }); const canvasWidth = typeof width === 'number' ? width : undefined; const canvasHeight = typeof height === 'number' ? height : undefined; const canvasProps = props; const canvasFit = (_a = getFitFromObjectFit(style)) !== null && _a !== void 0 ? _a : 'fill'; return (jsx_runtime_1.jsx(CanvasImageWithPrivateProps, { src: src, width: canvasWidth, height: canvasHeight, fit: canvasFit, effects: effects, className: className, style: style, id: id, pauseWhenLoading: pauseWhenLoading, maxRetries: maxRetries, delayRenderRetries: delayRenderRetries, delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds, from: from, durationInFrames: durationInFrames, hidden: hidden, name: name !== null && name !== void 0 ? name : '<Img>', showInTimeline: showInTimeline, stack: stack, _remotionInternalDocumentationLink: name === undefined ? 'https://www.remotion.dev/docs/img' : undefined, _experimentalControls: controls, _remotionInternalRefForOutline: refForOutline, ...canvasProps })); }; /* * @description Works just like a regular HTML img tag. When you use the <Img> tag, Remotion will ensure that the image is loaded before rendering the frame. * @see [Documentation](https://remotion.dev/docs/img) */ exports.Img = (0, wrap_in_schema_js_1.wrapInSchema)({ Component: ImgInner, schema: exports.imgSchema, supportsEffects: true, }); (0, enable_sequence_stack_traces_js_1.addSequenceStackTraces)(exports.Img);