@remotion/player
Version:
React component for embedding a Remotion preview into your app
144 lines (143 loc) • 10.5 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from 'react';
import { Composition, Internals } from 'remotion';
import { PlayerEmitterProvider } from './EmitterProvider.js';
import { PLAYER_CSS_CLASSNAME } from './player-css-classname.js';
import PlayerUI from './PlayerUI.js';
import { PLAYER_COMP_ID, SharedPlayerContexts } from './SharedPlayerContext.js';
import { validateInOutFrames } from './utils/validate-in-out-frame.js';
import { validateInitialFrame } from './utils/validate-initial-frame.js';
import { validatePlaybackRate } from './utils/validate-playbackrate.js';
import { validateDefaultAndInputProps, validateDimension, validateDurationInFrames, validateFps, } from './validate.js';
export const componentOrNullIfLazy = (props) => {
if ('component' in props) {
return props.component;
}
return null;
};
const PlayerFn = ({ durationInFrames, compositionHeight, compositionWidth, fps, inputProps, style, controls = false, loop = false, autoPlay = false, showVolumeControls = true, allowFullscreen = true, clickToPlay, doubleClickToFullscreen = false, spaceKeyToPlayOrPause = true, moveToBeginningWhenEnded = true, numberOfSharedAudioTags = 5, errorFallback = () => '⚠️', playbackRate = 1, renderLoading, className, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, showPosterWhenBuffering, initialFrame, renderPoster, inFrame, outFrame, initiallyShowControls, renderFullscreenButton, renderPlayPauseButton, alwaysShowControls = false, initiallyMuted = false, showPlaybackRateControl = false, posterFillMode = 'player-size', bufferStateDelayInMilliseconds, hideControlsWhenPointerDoesntMove = true, ...componentProps }, ref) => {
if (typeof window !== 'undefined') {
// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => {
window.remotion_isPlayer = true;
}, []);
}
// @ts-expect-error
if (componentProps.defaultProps !== undefined) {
throw new Error('The <Player /> component does not accept `defaultProps`, but some were passed. Use `inputProps` instead.');
}
const componentForValidation = componentOrNullIfLazy(componentProps);
// @ts-expect-error
if ((componentForValidation === null || componentForValidation === void 0 ? void 0 : componentForValidation.type) === Composition) {
throw new TypeError(`'component' should not be an instance of <Composition/>. Pass the React component directly, and set the duration, fps and dimensions as separate props. See https://www.remotion.dev/docs/player/examples for an example.`);
}
if (componentForValidation === Composition) {
throw new TypeError(`'component' must not be the 'Composition' component. Pass your own React component directly, and set the duration, fps and dimensions as separate props. See https://www.remotion.dev/docs/player/examples for an example.`);
}
const component = Internals.useLazyComponent(componentProps);
validateInitialFrame({ initialFrame, durationInFrames });
const [frame, setFrame] = useState(() => ({
[PLAYER_COMP_ID]: initialFrame !== null && initialFrame !== void 0 ? initialFrame : 0,
}));
const [playing, setPlaying] = useState(false);
const [rootId] = useState('player-comp');
const rootRef = useRef(null);
const audioAndVideoTags = useRef([]);
const imperativePlaying = useRef(false);
const [currentPlaybackRate, setCurrentPlaybackRate] = useState(playbackRate);
if (typeof compositionHeight !== 'number') {
throw new TypeError(`'compositionHeight' must be a number but got '${typeof compositionHeight}' instead`);
}
if (typeof compositionWidth !== 'number') {
throw new TypeError(`'compositionWidth' must be a number but got '${typeof compositionWidth}' instead`);
}
validateDimension(compositionHeight, 'compositionHeight', 'of the <Player /> component');
validateDimension(compositionWidth, 'compositionWidth', 'of the <Player /> component');
validateDurationInFrames(durationInFrames, {
component: 'of the <Player/> component',
allowFloats: false,
});
validateFps(fps, 'as a prop of the <Player/> component', false);
validateDefaultAndInputProps(inputProps, 'inputProps', null);
validateInOutFrames({
durationInFrames,
inFrame,
outFrame,
});
if (typeof controls !== 'boolean' && typeof controls !== 'undefined') {
throw new TypeError(`'controls' must be a boolean or undefined but got '${typeof controls}' instead`);
}
if (typeof autoPlay !== 'boolean' && typeof autoPlay !== 'undefined') {
throw new TypeError(`'autoPlay' must be a boolean or undefined but got '${typeof autoPlay}' instead`);
}
if (typeof loop !== 'boolean' && typeof loop !== 'undefined') {
throw new TypeError(`'loop' must be a boolean or undefined but got '${typeof loop}' instead`);
}
if (typeof doubleClickToFullscreen !== 'boolean' &&
typeof doubleClickToFullscreen !== 'undefined') {
throw new TypeError(`'doubleClickToFullscreen' must be a boolean or undefined but got '${typeof doubleClickToFullscreen}' instead`);
}
if (typeof showVolumeControls !== 'boolean' &&
typeof showVolumeControls !== 'undefined') {
throw new TypeError(`'showVolumeControls' must be a boolean or undefined but got '${typeof showVolumeControls}' instead`);
}
if (typeof allowFullscreen !== 'boolean' &&
typeof allowFullscreen !== 'undefined') {
throw new TypeError(`'allowFullscreen' must be a boolean or undefined but got '${typeof allowFullscreen}' instead`);
}
if (typeof clickToPlay !== 'boolean' && typeof clickToPlay !== 'undefined') {
throw new TypeError(`'clickToPlay' must be a boolean or undefined but got '${typeof clickToPlay}' instead`);
}
if (typeof spaceKeyToPlayOrPause !== 'boolean' &&
typeof spaceKeyToPlayOrPause !== 'undefined') {
throw new TypeError(`'spaceKeyToPlayOrPause' must be a boolean or undefined but got '${typeof spaceKeyToPlayOrPause}' instead`);
}
if (typeof numberOfSharedAudioTags !== 'number' ||
numberOfSharedAudioTags % 1 !== 0 ||
!Number.isFinite(numberOfSharedAudioTags) ||
Number.isNaN(numberOfSharedAudioTags) ||
numberOfSharedAudioTags < 0) {
throw new TypeError(`'numberOfSharedAudioTags' must be an integer but got '${numberOfSharedAudioTags}' instead`);
}
validatePlaybackRate(currentPlaybackRate);
useEffect(() => {
setCurrentPlaybackRate(playbackRate);
}, [playbackRate]);
useImperativeHandle(ref, () => rootRef.current, []);
const timelineContextValue = useMemo(() => {
return {
frame,
playing,
rootId,
playbackRate: currentPlaybackRate,
imperativePlaying,
setPlaybackRate: (rate) => {
setCurrentPlaybackRate(rate);
},
audioAndVideoTags,
};
}, [frame, currentPlaybackRate, playing, rootId]);
const setTimelineContextValue = useMemo(() => {
return {
setFrame,
setPlaying,
};
}, [setFrame]);
if (typeof window !== 'undefined') {
// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => {
// Inject CSS only on client, and also only after the Player has hydrated
Internals.CSSUtils.injectCSS(Internals.CSSUtils.makeDefaultCSS(`.${PLAYER_CSS_CLASSNAME}`, '#fff'));
}, []);
}
const actualInputProps = useMemo(() => inputProps !== null && inputProps !== void 0 ? inputProps : {}, [inputProps]);
return (_jsx(Internals.IsPlayerContextProvider, { children: _jsx(SharedPlayerContexts, { timelineContext: timelineContextValue, component: component, compositionHeight: compositionHeight, compositionWidth: compositionWidth, durationInFrames: durationInFrames, fps: fps, numberOfSharedAudioTags: numberOfSharedAudioTags, initiallyMuted: initiallyMuted, children: _jsx(Internals.Timeline.SetTimelineContext.Provider, { value: setTimelineContextValue, children: _jsx(PlayerEmitterProvider, { currentPlaybackRate: currentPlaybackRate, children: _jsx(PlayerUI, { ref: rootRef, posterFillMode: posterFillMode, renderLoading: renderLoading, autoPlay: Boolean(autoPlay), loop: Boolean(loop), controls: Boolean(controls), errorFallback: errorFallback, style: style, inputProps: actualInputProps, allowFullscreen: Boolean(allowFullscreen), moveToBeginningWhenEnded: Boolean(moveToBeginningWhenEnded), clickToPlay: typeof clickToPlay === 'boolean'
? clickToPlay
: Boolean(controls), showVolumeControls: Boolean(showVolumeControls), doubleClickToFullscreen: Boolean(doubleClickToFullscreen), spaceKeyToPlayOrPause: Boolean(spaceKeyToPlayOrPause), playbackRate: currentPlaybackRate, className: className !== null && className !== void 0 ? className : undefined, showPosterWhenUnplayed: Boolean(showPosterWhenUnplayed), showPosterWhenEnded: Boolean(showPosterWhenEnded), showPosterWhenPaused: Boolean(showPosterWhenPaused), showPosterWhenBuffering: Boolean(showPosterWhenBuffering), renderPoster: renderPoster, inFrame: inFrame !== null && inFrame !== void 0 ? inFrame : null, outFrame: outFrame !== null && outFrame !== void 0 ? outFrame : null, initiallyShowControls: initiallyShowControls !== null && initiallyShowControls !== void 0 ? initiallyShowControls : true, renderFullscreen: renderFullscreenButton !== null && renderFullscreenButton !== void 0 ? renderFullscreenButton : null, renderPlayPauseButton: renderPlayPauseButton !== null && renderPlayPauseButton !== void 0 ? renderPlayPauseButton : null, alwaysShowControls: alwaysShowControls, showPlaybackRateControl: showPlaybackRateControl, bufferStateDelayInMilliseconds: bufferStateDelayInMilliseconds !== null && bufferStateDelayInMilliseconds !== void 0 ? bufferStateDelayInMilliseconds : 300, hideControlsWhenPointerDoesntMove: hideControlsWhenPointerDoesntMove }) }) }) }) }));
};
const forward = forwardRef;
/**
* @description A component which can be rendered in a regular React App (for example: Vite, Next.js) to display a Remotion video.
* @see [Documentation](https://www.remotion.dev/docs/player/player)
*/
export const Player = forward(PlayerFn);