clipper/frontend/src/VideoPreview.tsx

131 lines
3.1 KiB
TypeScript
Raw Normal View History

2021-11-21 19:43:40 +00:00
import { MediaSet, MediaSetServiceClientImpl } from './generated/media_set';
import { newRPC } from './App';
2021-10-08 14:38:35 +00:00
import { useEffect, useRef } from 'react';
interface Props {
2021-11-21 19:43:40 +00:00
mediaSet: MediaSet;
2021-10-08 14:38:35 +00:00
position: number;
duration: number;
height: number;
video: HTMLVideoElement;
}
export const VideoPreview: React.FC<Props> = ({
2021-11-21 19:43:40 +00:00
mediaSet,
2021-10-08 14:38:35 +00:00
position,
duration,
height,
video,
}: Props) => {
const videoCanvasRef = useRef<HTMLCanvasElement>(null);
// effects
2021-11-21 19:43:40 +00:00
// load thumbnail, to display when the component is loaded for the first
// time. This is needed because of browser autoplay limitations.
useEffect(() => {
(async function () {
if (mediaSet == null) {
return;
}
const canvas = videoCanvasRef.current;
if (canvas == null) {
console.error('no canvas ref available');
return;
}
const ctx = canvas.getContext('2d');
if (ctx == null) {
console.error('no 2d context available');
return;
}
// Set aspect ratio.
canvas.width = canvas.height * (canvas.clientWidth / canvas.clientHeight);
console.log('getting video thumbnail...');
const rpc = newRPC();
const service = new MediaSetServiceClientImpl(rpc);
const thumbnail = await service.GetVideoThumbnail({ id: mediaSet.id });
console.log('got thumbnail', thumbnail);
const url = URL.createObjectURL(
new Blob([thumbnail.image], { type: 'image/jpeg' })
);
const img = new Image(thumbnail.width, thumbnail.height);
img.src = url;
console.log('img', img);
img.onerror = console.error;
img.onload = () => {
ctx.drawImage(img, 0, 0, 177, 100);
};
console.log('set src to', url);
})();
}, [mediaSet]);
2021-10-08 14:38:35 +00:00
// render canvas
useEffect(() => {
// TODO: not sure if requestAnimationFrame is recommended here.
requestAnimationFrame(() => {
const canvas = videoCanvasRef.current;
if (canvas == null) {
console.error('no canvas ref available');
return;
}
const ctx = canvas.getContext('2d');
if (ctx == null) {
console.error('no 2d context available');
return;
}
// Set aspect ratio.
canvas.width = canvas.height * (canvas.clientWidth / canvas.clientHeight);
const durSecs = duration / 1000;
const ratio = position / durSecs;
const x = (canvas.width - 177) * ratio;
ctx.clearRect(0, 0, x, canvas.height);
ctx.clearRect(x + 177, 0, canvas.width - 177 - x, canvas.height);
ctx.drawImage(video, x, 0, 177, 100);
});
}, [position]);
// render component
const containerStyles = {
height: height + 'px',
position: 'relative',
flexGrow: 0,
} as React.CSSProperties;
const canvasStyles = {
position: 'absolute',
width: '100%',
height: '100%',
display: 'block',
zIndex: 1,
} as React.CSSProperties;
return (
<>
<div style={containerStyles}>
<canvas
width="500"
height="100"
ref={videoCanvasRef}
style={canvasStyles}
></canvas>
<canvas style={canvasStyles}></canvas>
</div>
</>
);
};