UNPKG

@remotion/player

Version:

React component for embedding a Remotion preview into your app

444 lines (443 loc) • 21.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importStar(require("react")); const remotion_1 = require("remotion"); const PlayerControls_js_1 = require("./PlayerControls.js"); const calculate_scale_js_1 = require("./calculate-scale.js"); const error_boundary_js_1 = require("./error-boundary.js"); const license_blacklist_js_1 = require("./license-blacklist.js"); const player_css_classname_js_1 = require("./player-css-classname.js"); const use_playback_js_1 = require("./use-playback.js"); const use_player_js_1 = require("./use-player.js"); const is_node_js_1 = require("./utils/is-node.js"); const use_click_prevention_on_double_click_js_1 = require("./utils/use-click-prevention-on-double-click.js"); const use_element_size_js_1 = require("./utils/use-element-size.js"); const reactVersion = react_1.default.version.split('.')[0]; if (reactVersion === '0') { throw new Error(`Version ${reactVersion} of "react" is not supported by Remotion`); } const doesReactVersionSupportSuspense = parseInt(reactVersion, 10) >= 18; const PlayerUI = ({ controls, style, loop, autoPlay, allowFullscreen, inputProps, clickToPlay, showVolumeControls, doubleClickToFullscreen, spaceKeyToPlayOrPause, errorFallback, playbackRate, renderLoading, renderPoster, className, moveToBeginningWhenEnded, showPosterWhenUnplayed, showPosterWhenEnded, showPosterWhenPaused, showPosterWhenBuffering, inFrame, outFrame, initiallyShowControls, renderFullscreen: renderFullscreenButton, renderPlayPauseButton, renderMuteButton, renderVolumeSlider, alwaysShowControls, showPlaybackRateControl, posterFillMode, bufferStateDelayInMilliseconds, hideControlsWhenPointerDoesntMove, overflowVisible, browserMediaControlsBehavior, overrideInternalClassName, noSuspense, }, ref) => { var _a, _b, _c; const config = remotion_1.Internals.useUnsafeVideoConfig(); const video = remotion_1.Internals.useVideo(); const container = (0, react_1.useRef)(null); const canvasSize = (0, use_element_size_js_1.useElementSize)(container, { triggerOnWindowResize: false, shouldApplyCssTransforms: false, }); const [hasPausedToResume, setHasPausedToResume] = (0, react_1.useState)(false); const [shouldAutoplay, setShouldAutoPlay] = (0, react_1.useState)(autoPlay); const [isFullscreen, setIsFullscreen] = (0, react_1.useState)(() => false); const [seeking, setSeeking] = (0, react_1.useState)(false); const supportsFullScreen = (0, react_1.useMemo)(() => { if (typeof document === 'undefined') { return false; } return Boolean(document.fullscreenEnabled || // @ts-expect-error Types not defined document.webkitFullscreenEnabled); }, []); const player = (0, use_player_js_1.usePlayer)(); const playerToggle = player.toggle; (0, use_playback_js_1.usePlayback)({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, outFrame, getCurrentFrame: player.getCurrentFrame, browserMediaControlsBehavior, }); (0, react_1.useEffect)(() => { if (hasPausedToResume && !player.playing) { setHasPausedToResume(false); player.play(); } }, [hasPausedToResume, player]); (0, react_1.useEffect)(() => { const { current } = container; if (!current) { return; } const onFullscreenChange = () => { const newValue = document.fullscreenElement === current || // @ts-expect-error Types not defined document.webkitFullscreenElement === current; setIsFullscreen(newValue); }; document.addEventListener('fullscreenchange', onFullscreenChange); document.addEventListener('webkitfullscreenchange', onFullscreenChange); return () => { document.removeEventListener('fullscreenchange', onFullscreenChange); document.removeEventListener('webkitfullscreenchange', onFullscreenChange); }; }, []); const toggle = (0, react_1.useCallback)((e) => { playerToggle(e); }, [playerToggle]); const requestFullscreen = (0, react_1.useCallback)(() => { if (!allowFullscreen) { throw new Error('allowFullscreen is false'); } if (!supportsFullScreen) { throw new Error('Browser doesnt support fullscreen'); } if (!container.current) { throw new Error('No player ref found'); } // @ts-expect-error Types not defined if (container.current.webkitRequestFullScreen) { // @ts-expect-error Types not defined container.current.webkitRequestFullScreen(); } else { container.current.requestFullscreen(); } }, [allowFullscreen, supportsFullScreen]); const exitFullscreen = (0, react_1.useCallback)(() => { // @ts-expect-error Types not defined if (document.webkitExitFullscreen) { // @ts-expect-error Types not defined document.webkitExitFullscreen(); } else { document.exitFullscreen(); } }, []); (0, react_1.useEffect)(() => { const { current } = container; if (!current) { return; } const fullscreenChange = () => { var _a; const element = // @ts-expect-error Types not defined (_a = document.webkitFullscreenElement) !== null && _a !== void 0 ? _a : // defined document.fullscreenElement; if (element && element === container.current) { player.emitter.dispatchFullscreenChange({ isFullscreen: true, }); } else { player.emitter.dispatchFullscreenChange({ isFullscreen: false, }); } }; current.addEventListener('webkitfullscreenchange', fullscreenChange); current.addEventListener('fullscreenchange', fullscreenChange); return () => { current.removeEventListener('webkitfullscreenchange', fullscreenChange); current.removeEventListener('fullscreenchange', fullscreenChange); }; }, [player.emitter]); const durationInFrames = (_a = config === null || config === void 0 ? void 0 : config.durationInFrames) !== null && _a !== void 0 ? _a : 1; const layout = (0, react_1.useMemo)(() => { if (!config || !canvasSize) { return null; } return (0, calculate_scale_js_1.calculateCanvasTransformation)({ canvasSize, compositionHeight: config.height, compositionWidth: config.width, previewSize: 'auto', }); }, [canvasSize, config]); const scale = (_b = layout === null || layout === void 0 ? void 0 : layout.scale) !== null && _b !== void 0 ? _b : 1; const initialScaleIgnored = (0, react_1.useRef)(false); (0, react_1.useEffect)(() => { if (!initialScaleIgnored.current) { initialScaleIgnored.current = true; return; } player.emitter.dispatchScaleChange(scale); }, [player.emitter, scale]); const { setMediaVolume, setMediaMuted } = (0, react_1.useContext)(remotion_1.Internals.SetMediaVolumeContext); const { mediaMuted, mediaVolume } = (0, react_1.useContext)(remotion_1.Internals.MediaVolumeContext); (0, react_1.useEffect)(() => { player.emitter.dispatchVolumeChange(mediaVolume); }, [player.emitter, mediaVolume]); const isMuted = mediaMuted || mediaVolume === 0; (0, react_1.useEffect)(() => { player.emitter.dispatchMuteChange({ isMuted, }); }, [player.emitter, isMuted]); const [showBufferIndicator, setShowBufferState] = (0, react_1.useState)(false); (0, react_1.useEffect)(() => { let timeout = null; let stopped = false; const onBuffer = () => { stopped = false; requestAnimationFrame(() => { if (bufferStateDelayInMilliseconds === 0) { setShowBufferState(true); } else { timeout = setTimeout(() => { if (!stopped) { setShowBufferState(true); } }, bufferStateDelayInMilliseconds); } }); }; const onResume = () => { requestAnimationFrame(() => { stopped = true; setShowBufferState(false); if (timeout) { clearTimeout(timeout); } }); }; player.emitter.addEventListener('waiting', onBuffer); player.emitter.addEventListener('resume', onResume); return () => { player.emitter.removeEventListener('waiting', onBuffer); player.emitter.removeEventListener('resume', onResume); setShowBufferState(false); if (timeout) { clearTimeout(timeout); } stopped = true; }; }, [bufferStateDelayInMilliseconds, player.emitter]); (0, react_1.useImperativeHandle)(ref, () => { const methods = { play: player.play, pause: () => { // If, after .seek()-ing, the player was explicitly paused, we don't resume setHasPausedToResume(false); player.pause(); }, toggle, getContainerNode: () => container.current, getCurrentFrame: player.getCurrentFrame, isPlaying: player.isPlaying, seekTo: (f) => { const lastFrame = durationInFrames - 1; const frameToSeekTo = Math.max(0, Math.min(lastFrame, f)); // continue playing after seeking if the player was playing before if (player.isPlaying()) { const pauseToResume = frameToSeekTo !== lastFrame || loop; setHasPausedToResume(pauseToResume); player.pause(); } if (frameToSeekTo === lastFrame && !loop) { player.emitter.dispatchEnded(); } player.seek(frameToSeekTo); }, isFullscreen: () => { const { current } = container; if (!current) { return false; } return (document.fullscreenElement === current || // @ts-expect-error Types not defined document.webkitFullscreenElement === current); }, requestFullscreen, exitFullscreen, getVolume: () => { if (mediaMuted) { return 0; } return mediaVolume; }, setVolume: (vol) => { if (typeof vol !== 'number') { throw new TypeError(`setVolume() takes a number, got value of type ${typeof vol}`); } if (isNaN(vol)) { throw new TypeError(`setVolume() got a number that is NaN. Volume must be between 0 and 1.`); } if (vol < 0 || vol > 1) { throw new TypeError(`setVolume() got a number that is out of range. Must be between 0 and 1, got ${typeof vol}`); } setMediaVolume(vol); }, isMuted: () => isMuted, mute: () => { setMediaMuted(true); }, unmute: () => { setMediaMuted(false); }, getScale: () => scale, pauseAndReturnToPlayStart: () => { player.pauseAndReturnToPlayStart(); }, }; return Object.assign(player.emitter, methods); }, [ durationInFrames, exitFullscreen, loop, mediaMuted, isMuted, mediaVolume, player, requestFullscreen, setMediaMuted, setMediaVolume, toggle, scale, ]); const VideoComponent = video ? video.component : null; const outerStyle = (0, react_1.useMemo)(() => { return (0, calculate_scale_js_1.calculateOuterStyle)({ canvasSize, config, style, overflowVisible, layout, }); }, [canvasSize, config, layout, overflowVisible, style]); const outer = (0, react_1.useMemo)(() => { return (0, calculate_scale_js_1.calculateOuter)({ config, layout, scale, overflowVisible }); }, [config, layout, overflowVisible, scale]); const containerStyle = (0, react_1.useMemo)(() => { return (0, calculate_scale_js_1.calculateContainerStyle)({ canvasSize, config, layout, scale, overflowVisible, }); }, [canvasSize, config, layout, overflowVisible, scale]); const playerPause = player.pause; const playerDispatchError = player.emitter.dispatchError; const onError = (0, react_1.useCallback)((error) => { playerPause(); // Pay attention to `this context` playerDispatchError(error); }, [playerDispatchError, playerPause]); const onFullscreenButtonClick = (0, react_1.useCallback)((e) => { e.stopPropagation(); requestFullscreen(); }, [requestFullscreen]); const onExitFullscreenButtonClick = (0, react_1.useCallback)((e) => { e.stopPropagation(); exitFullscreen(); }, [exitFullscreen]); const onSingleClick = (0, react_1.useCallback)((e) => { const rightClick = e instanceof MouseEvent ? e.button === 2 : e.nativeEvent.button; if (rightClick) { return; } toggle(e); }, [toggle]); const onSeekStart = (0, react_1.useCallback)(() => { setSeeking(true); }, []); const onSeekEnd = (0, react_1.useCallback)(() => { setSeeking(false); }, []); const onDoubleClick = (0, react_1.useCallback)(() => { if (isFullscreen) { exitFullscreen(); } else { requestFullscreen(); } }, [exitFullscreen, isFullscreen, requestFullscreen]); const { handlePointerDown, handleDoubleClick } = (0, use_click_prevention_on_double_click_js_1.useClickPreventionOnDoubleClick)(onSingleClick, onDoubleClick, doubleClickToFullscreen && allowFullscreen && supportsFullScreen); (0, react_1.useEffect)(() => { if (shouldAutoplay) { player.play(); setShouldAutoPlay(false); } }, [shouldAutoplay, player]); const loadingMarkup = (0, react_1.useMemo)(() => { return renderLoading ? renderLoading({ height: outerStyle.height, width: outerStyle.width, isBuffering: showBufferIndicator, }) : null; }, [outerStyle.height, outerStyle.width, renderLoading, showBufferIndicator]); const currentScale = (0, react_1.useMemo)(() => { return { type: 'scale', scale, }; }, [scale]); if (!config) { return null; } const poster = renderPoster ? renderPoster({ height: posterFillMode === 'player-size' ? outerStyle.height : config.height, width: posterFillMode === 'player-size' ? outerStyle.width : config.width, isBuffering: showBufferIndicator, }) : null; if (poster === undefined) { throw new TypeError('renderPoster() must return a React element, but undefined was returned'); } const shouldShowPoster = poster && [ showPosterWhenPaused && !player.isPlaying() && !seeking, showPosterWhenEnded && player.isLastFrame && !player.isPlaying(), showPosterWhenUnplayed && !player.hasPlayed && !player.isPlaying(), showPosterWhenBuffering && showBufferIndicator && player.isPlaying(), ].some(Boolean); const { left, top, width, height, ...outerWithoutScale } = outer; const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { style: outer, onPointerDown: clickToPlay ? handlePointerDown : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: [(0, jsx_runtime_1.jsxs)("div", { style: containerStyle, className: (0, player_css_classname_js_1.playerCssClassname)(overrideInternalClassName), children: [VideoComponent ? ((0, jsx_runtime_1.jsx)(error_boundary_js_1.ErrorBoundary, { onError: onError, errorFallback: errorFallback, children: (0, jsx_runtime_1.jsx)(remotion_1.Internals.CurrentScaleContext.Provider, { value: currentScale, children: (0, jsx_runtime_1.jsx)(VideoComponent, { ...((_c = video === null || video === void 0 ? void 0 : video.props) !== null && _c !== void 0 ? _c : {}), ...(inputProps !== null && inputProps !== void 0 ? inputProps : {}) }) }) })) : null, shouldShowPoster && posterFillMode === 'composition-size' ? ((0, jsx_runtime_1.jsx)("div", { style: { ...outerWithoutScale, width: config.width, height: config.height, }, onPointerDown: clickToPlay ? handlePointerDown : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: poster })) : null] }), (0, jsx_runtime_1.jsx)(license_blacklist_js_1.RenderWarningIfBlacklist, {})] }), shouldShowPoster && posterFillMode === 'player-size' ? ((0, jsx_runtime_1.jsx)("div", { style: outer, onPointerDown: clickToPlay ? handlePointerDown : undefined, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, children: poster })) : null, controls ? ((0, jsx_runtime_1.jsx)(PlayerControls_js_1.Controls, { fps: config.fps, playing: player.playing, toggle: player.toggle, durationInFrames: config.durationInFrames, containerRef: container, onFullscreenButtonClick: onFullscreenButtonClick, isFullscreen: isFullscreen, allowFullscreen: allowFullscreen, showVolumeControls: showVolumeControls, onExitFullscreenButtonClick: onExitFullscreenButtonClick, spaceKeyToPlayOrPause: spaceKeyToPlayOrPause, onSeekEnd: onSeekEnd, onSeekStart: onSeekStart, inFrame: inFrame, outFrame: outFrame, initiallyShowControls: initiallyShowControls, canvasSize: canvasSize, renderFullscreenButton: renderFullscreenButton, renderPlayPauseButton: renderPlayPauseButton, alwaysShowControls: alwaysShowControls, showPlaybackRateControl: showPlaybackRateControl, buffering: showBufferIndicator, hideControlsWhenPointerDoesntMove: hideControlsWhenPointerDoesntMove, onDoubleClick: doubleClickToFullscreen ? handleDoubleClick : undefined, onPointerDown: clickToPlay ? handlePointerDown : undefined, renderMuteButton: renderMuteButton, renderVolumeSlider: renderVolumeSlider })) : null] })); if (noSuspense || (is_node_js_1.IS_NODE && !doesReactVersionSupportSuspense)) { return ((0, jsx_runtime_1.jsx)("div", { ref: container, style: outerStyle, className: className, children: content })); } return ((0, jsx_runtime_1.jsx)("div", { ref: container, style: outerStyle, className: className, children: (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: loadingMarkup, children: content }) })); }; exports.default = (0, react_1.forwardRef)(PlayerUI);