@remotion/player
Version:
React component for embedding a Remotion preview into your app
177 lines (176 loc) • 9.33 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Internals } from 'remotion';
import { DefaultPlayPauseButton } from './DefaultPlayPauseButton.js';
import { formatTime } from './format-time.js';
import { FullscreenIcon } from './icons.js';
import { MediaVolumeSlider } from './MediaVolumeSlider.js';
import { PlaybackrateControl, playerButtonStyle } from './PlaybackrateControl.js';
import { PlayerSeekBar } from './PlayerSeekBar.js';
import { useHoverState } from './use-hover-state.js';
import { useVideoControlsResize, X_PADDING, } from './use-video-controls-resize.js';
const gradientSteps = [
0, 0.013, 0.049, 0.104, 0.175, 0.259, 0.352, 0.45, 0.55, 0.648, 0.741, 0.825,
0.896, 0.951, 0.987,
];
const gradientOpacities = [
0, 8.1, 15.5, 22.5, 29, 35.3, 41.2, 47.1, 52.9, 58.8, 64.7, 71, 77.5, 84.5,
91.9,
];
const globalGradientOpacity = 1 / 0.7;
const containerStyle = {
boxSizing: 'border-box',
position: 'absolute',
bottom: 0,
width: '100%',
paddingTop: 40,
paddingBottom: 10,
backgroundImage: `linear-gradient(to bottom,${gradientSteps
.map((g, i) => {
return `hsla(0, 0%, 0%, ${g}) ${gradientOpacities[i] * globalGradientOpacity}%`;
})
.join(', ')}, hsl(0, 0%, 0%) 100%)`,
backgroundSize: 'auto 145px',
display: 'flex',
paddingRight: X_PADDING,
paddingLeft: X_PADDING,
flexDirection: 'column',
transition: 'opacity 0.3s',
};
const controlsRow = {
display: 'flex',
flexDirection: 'row',
width: '100%',
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none',
};
const leftPartStyle = {
display: 'flex',
flexDirection: 'row',
userSelect: 'none',
alignItems: 'center',
};
const xSpacer = {
width: 12,
};
const ySpacer = {
height: 8,
};
const flex1 = {
flex: 1,
};
const fullscreen = {};
export const Controls = ({ durationInFrames, isFullscreen, fps, player, showVolumeControls, onFullscreenButtonClick, allowFullscreen, onExitFullscreenButtonClick, spaceKeyToPlayOrPause, onSeekEnd, onSeekStart, inFrame, outFrame, initiallyShowControls, canvasSize, renderPlayPauseButton, renderFullscreenButton, alwaysShowControls, showPlaybackRateControl, containerRef, buffering, hideControlsWhenPointerDoesntMove, onPointerUp, onDoubleClick, }) => {
var _a, _b;
const playButtonRef = useRef(null);
const frame = Internals.Timeline.useTimelinePosition();
const [supportsFullscreen, setSupportsFullscreen] = useState(false);
const hovered = useHoverState(containerRef, hideControlsWhenPointerDoesntMove);
const { maxTimeLabelWidth, displayVerticalVolumeSlider } = useVideoControlsResize({
allowFullscreen,
playerWidth: (_a = canvasSize === null || canvasSize === void 0 ? void 0 : canvasSize.width) !== null && _a !== void 0 ? _a : 0,
});
const [shouldShowInitially, setInitiallyShowControls] = useState(() => {
if (typeof initiallyShowControls === 'boolean') {
return initiallyShowControls;
}
if (typeof initiallyShowControls === 'number') {
if (initiallyShowControls % 1 !== 0) {
throw new Error('initiallyShowControls must be an integer or a boolean');
}
if (Number.isNaN(initiallyShowControls)) {
throw new Error('initiallyShowControls must not be NaN');
}
if (!Number.isFinite(initiallyShowControls)) {
throw new Error('initiallyShowControls must be finite');
}
if (initiallyShowControls <= 0) {
throw new Error('initiallyShowControls must be a positive integer');
}
return initiallyShowControls;
}
throw new TypeError('initiallyShowControls must be a number or a boolean');
});
const containerCss = useMemo(() => {
// Hide if playing and mouse outside
const shouldShow = hovered || !player.playing || shouldShowInitially || alwaysShowControls;
return {
...containerStyle,
opacity: Number(shouldShow),
};
}, [hovered, shouldShowInitially, player.playing, alwaysShowControls]);
useEffect(() => {
if (playButtonRef.current && spaceKeyToPlayOrPause) {
// This switches focus to play button when player.playing flag changes
playButtonRef.current.focus({
preventScroll: true,
});
}
}, [player.playing, spaceKeyToPlayOrPause]);
useEffect(() => {
var _a;
// Must be handled client-side to avoid SSR hydration mismatch
setSupportsFullscreen((_a = (typeof document !== 'undefined' &&
(document.fullscreenEnabled || document.webkitFullscreenEnabled))) !== null && _a !== void 0 ? _a : false);
}, []);
useEffect(() => {
if (shouldShowInitially === false) {
return;
}
const time = shouldShowInitially === true ? 2000 : shouldShowInitially;
const timeout = setTimeout(() => {
setInitiallyShowControls(false);
}, time);
return () => {
clearInterval(timeout);
};
}, [shouldShowInitially]);
const timeLabel = useMemo(() => {
return {
color: 'white',
fontFamily: 'sans-serif',
fontSize: 14,
maxWidth: maxTimeLabelWidth === null ? undefined : maxTimeLabelWidth,
overflow: 'hidden',
textOverflow: 'ellipsis',
};
}, [maxTimeLabelWidth]);
const playbackRates = useMemo(() => {
if (showPlaybackRateControl === true) {
return [0.5, 0.8, 1, 1.2, 1.5, 1.8, 2, 2.5, 3];
}
if (Array.isArray(showPlaybackRateControl)) {
for (const rate of showPlaybackRateControl) {
if (typeof rate !== 'number') {
throw new Error('Every item in showPlaybackRateControl must be a number');
}
if (rate <= 0) {
throw new Error('Every item in showPlaybackRateControl must be positive');
}
}
return showPlaybackRateControl;
}
return null;
}, [showPlaybackRateControl]);
const ref = useRef(null);
const flexRef = useRef(null);
const onPointerUpIfContainer = useCallback((e) => {
// Only if pressing the container
if (e.target === ref.current || e.target === flexRef.current) {
onPointerUp === null || onPointerUp === void 0 ? void 0 : onPointerUp(e);
}
}, [onPointerUp]);
const onDoubleClickIfContainer = useCallback((e) => {
// Only if pressing the container
if (e.target === ref.current || e.target === flexRef.current) {
onDoubleClick === null || onDoubleClick === void 0 ? void 0 : onDoubleClick(e);
}
}, [onDoubleClick]);
return (_jsxs("div", { ref: ref, style: containerCss, onPointerUp: onPointerUpIfContainer, onDoubleClick: onDoubleClickIfContainer, children: [_jsxs("div", { ref: flexRef, style: controlsRow, children: [_jsxs("div", { style: leftPartStyle, children: [_jsx("button", { ref: playButtonRef, type: "button", style: playerButtonStyle, onClick: player.playing ? player.pause : player.play, "aria-label": player.playing ? 'Pause video' : 'Play video', title: player.playing ? 'Pause video' : 'Play video', children: renderPlayPauseButton === null ? (_jsx(DefaultPlayPauseButton, { buffering: buffering, playing: player.playing })) : ((_b = renderPlayPauseButton({
playing: player.playing,
isBuffering: buffering,
})) !== null && _b !== void 0 ? _b : (_jsx(DefaultPlayPauseButton, { buffering: buffering, playing: player.playing }))) }), showVolumeControls ? (_jsxs(_Fragment, { children: [_jsx("div", { style: xSpacer }), _jsx(MediaVolumeSlider, { displayVerticalVolumeSlider: displayVerticalVolumeSlider })] })) : null, _jsx("div", { style: xSpacer }), _jsxs("div", { style: timeLabel, children: [formatTime(frame / fps), " / ", formatTime(durationInFrames / fps)] }), _jsx("div", { style: xSpacer })] }), _jsx("div", { style: flex1 }), playbackRates && canvasSize && (_jsx(PlaybackrateControl, { canvasSize: canvasSize, playbackRates: playbackRates })), playbackRates && supportsFullscreen && allowFullscreen ? (_jsx("div", { style: xSpacer })) : null, _jsx("div", { style: fullscreen, children: supportsFullscreen && allowFullscreen ? (_jsx("button", { type: "button", "aria-label": isFullscreen ? 'Exit fullscreen' : 'Enter Fullscreen', title: isFullscreen ? 'Exit fullscreen' : 'Enter Fullscreen', style: playerButtonStyle, onClick: isFullscreen
? onExitFullscreenButtonClick
: onFullscreenButtonClick, children: renderFullscreenButton === null ? (_jsx(FullscreenIcon, { isFullscreen: isFullscreen })) : (renderFullscreenButton({ isFullscreen })) })) : null })] }), _jsx("div", { style: ySpacer }), _jsx(PlayerSeekBar, { onSeekEnd: onSeekEnd, onSeekStart: onSeekStart, durationInFrames: durationInFrames, inFrame: inFrame, outFrame: outFrame })] }));
};