clipper/frontend/src/Waveform/Thumbnails.tsx

102 lines
2.3 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { CanvasLogicalWidth, MediaSet } from './Waveform';
interface Props {
mediaSet: MediaSet;
style: React.CSSProperties;
}
enum State {
Loading,
Ready,
Error,
}
export const Thumbnails: React.FC<Props> = ({ mediaSet, style }: Props) => {
const [image, _setImage] = useState(new Image());
const [state, setState] = useState(State.Loading);
const canvasRef = useRef<HTMLCanvasElement>(null);
// load thumbnail image when available:
useEffect(() => {
if (mediaSet == null) return;
image.src = `http://localhost:8888/api/media_sets/${mediaSet.id}/thumbnails`;
image.onload = () => {
setState(State.Ready);
};
}, []);
// render canvas if image has been loaded successfully:
useEffect(() => {
if (state != State.Ready) return;
if (mediaSet == null) return;
const canvas = canvasRef.current;
if (canvas == null) {
console.error('no canvas available');
return;
}
const ctx = canvas.getContext('2d');
if (ctx == null) {
console.error('no thumbnail 2d context available');
return;
}
const tw = mediaSet.video.thumbnailWidth;
const th = mediaSet.video.thumbnailHeight;
const iw = image.width;
const { width: pw, height: ph } = canvas.getBoundingClientRect();
// set canvas logical width to suit the aspect ratio:
// TODO: confirm this is needed.
const ar = tw / th;
const par = pw / ph;
canvas.width = tw * (par / ar);
const durationSecs = mediaSet.video.durationMillis / 1000;
for (let dx = 0; dx < canvas.width; dx += tw) {
const secs = Math.floor((dx / canvas.width) * durationSecs);
const sx = (secs * tw) % iw;
const sy = Math.floor(secs / (iw / tw)) * th;
ctx.drawImage(image, sx, sy, tw, th, dx, 0, tw, th);
}
}, [state]);
// rendering
if (mediaSet == null || mediaSet.video == null) {
console.error('unexpected null video');
return null;
}
if (state == State.Loading) {
return (
<>
<div>Loading...</div>
</>
);
}
if (state == State.Error) {
return (
<>
<span>Something went wrong</span>
</>
);
}
return (
<>
<canvas
ref={canvasRef}
style={style}
width={CanvasLogicalWidth}
height={100}
></canvas>
</>
);
};