@remotion/player
Version:
React component for embedding a Remotion preview into your app
146 lines (145 loc) • 5.79 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Internals, interpolate } from 'remotion';
import { useHoverState } from './use-hover-state.js';
import { usePlayer } from './use-player.js';
import { useElementSize } from './utils/use-element-size.js';
const getFrameFromX = (clientX, durationInFrames, width) => {
var _a;
const pos = clientX;
const frame = Math.round(interpolate(pos, [0, width], [0, (_a = durationInFrames - 1) !== null && _a !== void 0 ? _a : 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
}));
return frame;
};
const BAR_HEIGHT = 5;
const KNOB_SIZE = 12;
const VERTICAL_PADDING = 4;
const containerStyle = {
userSelect: 'none',
paddingTop: VERTICAL_PADDING,
paddingBottom: VERTICAL_PADDING,
boxSizing: 'border-box',
cursor: 'pointer',
position: 'relative',
touchAction: 'none',
};
const barBackground = {
height: BAR_HEIGHT,
backgroundColor: 'rgba(255, 255, 255, 0.25)',
width: '100%',
borderRadius: BAR_HEIGHT / 2,
};
const findBodyInWhichDivIsLocated = (div) => {
let current = div;
while (current.parentElement) {
current = current.parentElement;
}
return current;
};
export const PlayerSeekBar = ({ durationInFrames, onSeekEnd, onSeekStart, inFrame, outFrame }) => {
var _a;
const containerRef = useRef(null);
const barHovered = useHoverState(containerRef, false);
const size = useElementSize(containerRef, {
triggerOnWindowResize: true,
shouldApplyCssTransforms: true,
});
const { seek, play, pause, playing } = usePlayer();
const frame = Internals.Timeline.useTimelinePosition();
const [dragging, setDragging] = useState({
dragging: false,
});
const width = (_a = size === null || size === void 0 ? void 0 : size.width) !== null && _a !== void 0 ? _a : 0;
const onPointerDown = useCallback((e) => {
var _a;
if (e.button !== 0) {
return;
}
const posLeft = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().left;
const _frame = getFrameFromX(e.clientX - posLeft, durationInFrames, width);
pause();
seek(_frame);
setDragging({
dragging: true,
wasPlaying: playing,
});
onSeekStart();
}, [durationInFrames, width, pause, seek, playing, onSeekStart]);
const onPointerMove = useCallback((e) => {
var _a;
if (!size) {
throw new Error('Player has no size');
}
if (!dragging.dragging) {
return;
}
const posLeft = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().left;
const _frame = getFrameFromX(e.clientX - posLeft, durationInFrames, size.width);
seek(_frame);
}, [dragging.dragging, durationInFrames, seek, size]);
const onPointerUp = useCallback(() => {
setDragging({
dragging: false,
});
if (!dragging.dragging) {
return;
}
if (dragging.wasPlaying) {
play();
}
else {
pause();
}
onSeekEnd();
}, [dragging, onSeekEnd, pause, play]);
useEffect(() => {
if (!dragging.dragging) {
return;
}
const body = findBodyInWhichDivIsLocated(containerRef.current);
body.addEventListener('pointermove', onPointerMove);
body.addEventListener('pointerup', onPointerUp);
return () => {
body.removeEventListener('pointermove', onPointerMove);
body.removeEventListener('pointerup', onPointerUp);
};
}, [dragging.dragging, onPointerMove, onPointerUp]);
const knobStyle = useMemo(() => {
return {
height: KNOB_SIZE,
width: KNOB_SIZE,
borderRadius: KNOB_SIZE / 2,
position: 'absolute',
top: VERTICAL_PADDING - KNOB_SIZE / 2 + 5 / 2,
backgroundColor: 'white',
left: Math.max(0, (frame / Math.max(1, durationInFrames - 1)) * width - KNOB_SIZE / 2),
boxShadow: '0 0 2px black',
opacity: Number(barHovered),
};
}, [barHovered, durationInFrames, frame, width]);
const fillStyle = useMemo(() => {
return {
height: BAR_HEIGHT,
backgroundColor: 'rgba(255, 255, 255, 1)',
width: ((frame - (inFrame !== null && inFrame !== void 0 ? inFrame : 0)) / (durationInFrames - 1)) * 100 + '%',
marginLeft: ((inFrame !== null && inFrame !== void 0 ? inFrame : 0) / (durationInFrames - 1)) * 100 + '%',
borderRadius: BAR_HEIGHT / 2,
};
}, [durationInFrames, frame, inFrame]);
const active = useMemo(() => {
return {
height: BAR_HEIGHT,
backgroundColor: 'rgba(255, 255, 255, 0.25)',
width: (((outFrame !== null && outFrame !== void 0 ? outFrame : durationInFrames - 1) - (inFrame !== null && inFrame !== void 0 ? inFrame : 0)) /
(durationInFrames - 1)) *
100 +
'%',
marginLeft: ((inFrame !== null && inFrame !== void 0 ? inFrame : 0) / (durationInFrames - 1)) * 100 + '%',
borderRadius: BAR_HEIGHT / 2,
position: 'absolute',
};
}, [durationInFrames, inFrame, outFrame]);
return (_jsxs("div", { ref: containerRef, onPointerDown: onPointerDown, style: containerStyle, children: [_jsxs("div", { style: barBackground, children: [_jsx("div", { style: active }), _jsx("div", { style: fillStyle })] }), _jsx("div", { style: knobStyle })] }));
};