lossless-cut/src/components/BigWaveform.tsx

119 wiersze
3.8 KiB
TypeScript

import { memo, useEffect, useState, useCallback, useRef } from 'react';
import { ffmpegExtractWindow } from '../util/constants';
import { RenderableWaveform } from '../types';
const BigWaveform = memo(({ waveforms, relevantTime, playing, durationSafe, zoom, seekRel }: {
waveforms: RenderableWaveform[], relevantTime: number, playing: boolean, durationSafe: number, zoom: number, seekRel: (a: number) => void,
}) => {
const windowSize = ffmpegExtractWindow * 2;
const windowStart = Math.max(0, relevantTime - windowSize);
const windowEnd = relevantTime + windowSize;
const filtered = waveforms.filter((waveform) => waveform.from >= windowStart && waveform.to <= windowEnd);
const scaleFactor = zoom;
const [smoothTimeRaw, setSmoothTime] = useState<number | undefined>(relevantTime);
const smoothTime = smoothTimeRaw ?? relevantTime;
const mouseDownRef = useRef<{ relevantTime: number, x }>();
const containerRef = useRef<HTMLDivElement>(null);
const getRect = useCallback(() => containerRef.current!.getBoundingClientRect(), []);
const handleMouseDown = useCallback((e) => {
const rect = e.target.getBoundingClientRect();
const x = e.clientX - rect.left;
mouseDownRef.current = { relevantTime, x };
e.preventDefault();
}, [relevantTime]);
const scaleToTime = useCallback((v) => (((v) / getRect().width) * windowSize) / zoom, [getRect, windowSize, zoom]);
const handleMouseMove = useCallback((e) => {
if (mouseDownRef.current == null) return;
seekRel(-scaleToTime(e.movementX));
e.preventDefault();
}, [scaleToTime, seekRel]);
const handleWheel = useCallback((e) => {
seekRel(scaleToTime(e.deltaX));
}, [scaleToTime, seekRel]);
const handleMouseUp = useCallback((e) => {
if (!mouseDownRef.current) return;
mouseDownRef.current = undefined;
e.preventDefault();
}, []);
useEffect(() => {
const startTime = Date.now();
if (playing) {
let raf;
// eslint-disable-next-line no-inner-declarations
function render() {
raf = window.requestAnimationFrame(() => {
setSmoothTime(relevantTime + (Date.now() - startTime) / 1000);
render();
});
}
render();
return () => window.cancelAnimationFrame(raf);
}
setSmoothTime(undefined);
return undefined;
}, [relevantTime, playing]);
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
ref={containerRef}
style={{ height: '100%', width: '100%', position: 'relative', cursor: 'grab' }}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
onMouseMove={handleMouseMove}
onWheel={handleWheel}
>
{filtered.map((waveform) => {
const left = 0.5 + ((waveform.from - smoothTime) / windowSize) * scaleFactor;
const width = ((waveform.to - waveform.from) / windowSize) * scaleFactor;
const leftPercent = `${left * 100}%`;
const widthPercent = `${width * 100}%`;
return (
<img
key={`${waveform.from}-${waveform.to}`}
src={waveform.url}
draggable={false}
alt=""
style={{
pointerEvents: 'none',
backgroundColor: 'var(--gray3)',
position: 'absolute',
height: '100%',
width: widthPercent,
left: leftPercent,
borderLeft: waveform.from === 0 ? '1px solid var(--gray11)' : undefined,
borderRight: waveform.to >= durationSafe ? '1px solid var(--gray11)' : undefined,
}}
/>
);
})}
<div style={{ pointerEvents: 'none', position: 'absolute', height: '100%', backgroundColor: 'var(--red11)', width: 1, left: '50%', top: 0 }} />
</div>
);
});
export default BigWaveform;