remotion
Version:
Make videos programmatically
205 lines (204 loc) • 9.75 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OffthreadVideoForRendering = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const Img_js_1 = require("../Img.js");
const RenderAssetManager_js_1 = require("../RenderAssetManager.js");
const SequenceContext_js_1 = require("../SequenceContext.js");
const absolute_src_js_1 = require("../absolute-src.js");
const use_audio_frame_js_1 = require("../audio/use-audio-frame.js");
const cancel_render_js_1 = require("../cancel-render.js");
const default_css_js_1 = require("../default-css.js");
const delay_render_js_1 = require("../delay-render.js");
const random_js_1 = require("../random.js");
const timeline_position_state_js_1 = require("../timeline-position-state.js");
const truthy_js_1 = require("../truthy.js");
const use_current_frame_js_1 = require("../use-current-frame.js");
const use_unsafe_video_config_js_1 = require("../use-unsafe-video-config.js");
const volume_prop_js_1 = require("../volume-prop.js");
const get_current_time_js_1 = require("./get-current-time.js");
const offthread_video_source_js_1 = require("./offthread-video-source.js");
const OffthreadVideoForRendering = ({ onError, volume: volumeProp, playbackRate, src, muted, allowAmplificationDuringRender, transparent = false, toneMapped = true, toneFrequency, name, loopVolumeCurveBehavior, delayRenderRetries, delayRenderTimeoutInMilliseconds, onVideoFrame,
// Remove crossOrigin prop during rendering
// https://discord.com/channels/809501355504959528/844143007183667220/1311639632496033813
crossOrigin, ...props }) => {
const absoluteFrame = (0, timeline_position_state_js_1.useTimelinePosition)();
const frame = (0, use_current_frame_js_1.useCurrentFrame)();
const volumePropsFrame = (0, use_audio_frame_js_1.useFrameForVolumeProp)(loopVolumeCurveBehavior !== null && loopVolumeCurveBehavior !== void 0 ? loopVolumeCurveBehavior : 'repeat');
const videoConfig = (0, use_unsafe_video_config_js_1.useUnsafeVideoConfig)();
const sequenceContext = (0, react_1.useContext)(SequenceContext_js_1.SequenceContext);
const mediaStartsAt = (0, use_audio_frame_js_1.useMediaStartsAt)();
const { registerRenderAsset, unregisterRenderAsset } = (0, react_1.useContext)(RenderAssetManager_js_1.RenderAssetManager);
if (!src) {
throw new TypeError('No `src` was passed to <OffthreadVideo>.');
}
// Generate a string that's as unique as possible for this asset
// but at the same time the same on all threads
const id = (0, react_1.useMemo)(() => `offthreadvideo-${(0, random_js_1.random)(src !== null && src !== void 0 ? src : '')}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom}-${sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.durationInFrames}`, [
src,
sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.cumulatedFrom,
sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom,
sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.durationInFrames,
]);
if (!videoConfig) {
throw new Error('No video config found');
}
const volume = (0, volume_prop_js_1.evaluateVolume)({
volume: volumeProp,
frame: volumePropsFrame,
mediaVolume: 1,
});
(0, react_1.useEffect)(() => {
var _a;
if (!src) {
throw new Error('No src passed');
}
if (!window.remotion_audioEnabled) {
return;
}
if (muted) {
return;
}
if (volume <= 0) {
return;
}
registerRenderAsset({
type: 'video',
src: (0, absolute_src_js_1.getAbsoluteSrc)(src),
id,
frame: absoluteFrame,
volume,
mediaFrame: frame,
playbackRate: playbackRate !== null && playbackRate !== void 0 ? playbackRate : 1,
toneFrequency: toneFrequency !== null && toneFrequency !== void 0 ? toneFrequency : null,
audioStartFrame: Math.max(0, -((_a = sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom) !== null && _a !== void 0 ? _a : 0)),
});
return () => unregisterRenderAsset(id);
}, [
muted,
src,
registerRenderAsset,
id,
unregisterRenderAsset,
volume,
frame,
absoluteFrame,
playbackRate,
toneFrequency,
sequenceContext === null || sequenceContext === void 0 ? void 0 : sequenceContext.relativeFrom,
]);
const currentTime = (0, react_1.useMemo)(() => {
return ((0, get_current_time_js_1.getExpectedMediaFrameUncorrected)({
frame,
playbackRate: playbackRate || 1,
startFrom: -mediaStartsAt,
}) / videoConfig.fps);
}, [frame, mediaStartsAt, playbackRate, videoConfig.fps]);
const actualSrc = (0, react_1.useMemo)(() => {
return (0, offthread_video_source_js_1.getOffthreadVideoSource)({
src,
currentTime,
transparent,
toneMapped,
});
}, [toneMapped, currentTime, src, transparent]);
const [imageSrc, setImageSrc] = (0, react_1.useState)(null);
(0, react_1.useLayoutEffect)(() => {
if (!window.remotion_videoEnabled) {
return;
}
const cleanup = [];
setImageSrc(null);
const controller = new AbortController();
const newHandle = (0, delay_render_js_1.delayRender)(`Fetching ${actualSrc} from server`, {
retries: delayRenderRetries !== null && delayRenderRetries !== void 0 ? delayRenderRetries : undefined,
timeoutInMilliseconds: delayRenderTimeoutInMilliseconds !== null && delayRenderTimeoutInMilliseconds !== void 0 ? delayRenderTimeoutInMilliseconds : undefined,
});
const execute = async () => {
try {
const res = await fetch(actualSrc, {
signal: controller.signal,
cache: 'no-store',
});
if (res.status !== 200) {
if (res.status === 500) {
const json = await res.json();
if (json.error) {
const cleanedUpErrorMessage = json.error.replace(/^Error: /, '');
throw new Error(cleanedUpErrorMessage);
}
}
throw new Error(`Server returned status ${res.status} while fetching ${actualSrc}`);
}
const blob = await res.blob();
const url = URL.createObjectURL(blob);
cleanup.push(() => URL.revokeObjectURL(url));
setImageSrc({
src: url,
handle: newHandle,
});
}
catch (err) {
// If component is unmounted, we should not throw
if (err.message.includes('aborted')) {
(0, delay_render_js_1.continueRender)(newHandle);
return;
}
if (controller.signal.aborted) {
(0, delay_render_js_1.continueRender)(newHandle);
return;
}
if (err.message.includes('Failed to fetch')) {
// eslint-disable-next-line no-ex-assign
err = new Error(`Failed to fetch ${actualSrc}. This could be caused by Chrome rejecting the request because the disk space is low. Consider increasing the disk size of your environment.`, { cause: err });
}
if (onError) {
onError(err);
}
else {
(0, cancel_render_js_1.cancelRender)(err);
}
}
};
execute();
cleanup.push(() => {
if (controller.signal.aborted) {
return;
}
controller.abort();
});
return () => {
cleanup.forEach((c) => c());
};
}, [
actualSrc,
delayRenderRetries,
delayRenderTimeoutInMilliseconds,
onError,
]);
const onErr = (0, react_1.useCallback)(() => {
if (onError) {
onError === null || onError === void 0 ? void 0 : onError(new Error('Failed to load image with src ' + imageSrc));
}
else {
(0, cancel_render_js_1.cancelRender)('Failed to load image with src ' + imageSrc);
}
}, [imageSrc, onError]);
const className = (0, react_1.useMemo)(() => {
return [default_css_js_1.OFFTHREAD_VIDEO_CLASS_NAME, props.className]
.filter(truthy_js_1.truthy)
.join(' ');
}, [props.className]);
const onImageFrame = (0, react_1.useCallback)((img) => {
if (onVideoFrame) {
onVideoFrame(img);
}
}, [onVideoFrame]);
if (!imageSrc || !window.remotion_videoEnabled) {
return null;
}
(0, delay_render_js_1.continueRender)(imageSrc.handle);
return ((0, jsx_runtime_1.jsx)(Img_js_1.Img, { src: imageSrc.src, className: className, delayRenderRetries: delayRenderRetries, delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds, onImageFrame: onImageFrame, ...props, onError: onErr }));
};
exports.OffthreadVideoForRendering = OffthreadVideoForRendering;