poc: Add SeekBar

This commit is contained in:
Rob Watson 2021-09-30 21:09:09 +02:00
parent d5df962627
commit 084cabaca9
3 changed files with 112 additions and 0 deletions

View File

@ -1,6 +1,11 @@
import { CanvasLogicalWidth } from './Waveform';
import { MouseEvent } from 'react';
interface Point {
x: number;
y: number;
}
// TODO: add tests
export const mouseEventToCanvasX = (
evt: MouseEvent<HTMLCanvasElement>
@ -10,6 +15,20 @@ export const mouseEventToCanvasX = (
return (elementX * CanvasLogicalWidth) / rect.width;
};
// TODO: add tests
export const mouseEventToCanvasPoint = (
evt: MouseEvent<HTMLCanvasElement>
): Point => {
const rect = evt.currentTarget.getBoundingClientRect();
const elementX = evt.clientX - rect.left;
const elementY = evt.clientY - rect.top;
return {
x: (elementX * evt.currentTarget.width) / rect.width,
y: (elementY * evt.currentTarget.height) / rect.height,
};
};
// TODO: add tests
export const canvasXToFrame = (x: number, numFrames: number): number => {
return Math.floor((x / CanvasLogicalWidth) * numFrames);

View File

@ -0,0 +1,82 @@
import { useRef, useEffect, useState, MouseEvent } from 'react';
import { mouseEventToCanvasPoint } from './Helpers';
interface Props {
duration: number;
style: React.CSSProperties;
}
const LogicalHeight = 200;
const MarginX = 0;
const MarginY = 85;
const KnobRadius = 40;
export const SeekBar: React.FC<Props> = (props: Props) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [position, _setPosition] = useState(100);
const [cursor, setCursor] = useState('auto');
const secsToCanvasX = (secs: number, width: number): number => {
return (secs / props.duration) * width;
};
// draw the canvas
useEffect(() => {
const canvas = canvasRef.current;
if (canvas == null) {
return;
}
const ctx = canvas.getContext('2d');
if (ctx == null) {
console.error('no seekbar 2d context available');
return;
}
// Set aspect ratio.
canvas.width = canvas.height * (canvas.clientWidth / canvas.clientHeight);
ctx.fillStyle = '#333333';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.fillRect(
MarginX,
MarginY,
canvas.width - MarginX * 2,
canvas.height - MarginY * 2
);
const x = secsToCanvasX(position, canvas.width);
const y = LogicalHeight / 2;
ctx.beginPath();
ctx.arc(x, y, KnobRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
});
const style = { ...props.style, cursor: cursor };
// handlers
const handleMouseMove = (evt: MouseEvent<HTMLCanvasElement>) => {
const { x: _x, y: y } = mouseEventToCanvasPoint(evt);
// TODO: improve mouse detection around knob.
if (y > MarginY && y < LogicalHeight - MarginY) {
setCursor('pointer');
} else {
setCursor('auto');
}
};
return (
<>
<canvas
style={style}
ref={canvasRef}
height={LogicalHeight}
onMouseMove={handleMouseMove}
></canvas>
</>
);
};

View File

@ -2,6 +2,7 @@ import { useEffect, useState, useRef, MouseEvent } from 'react';
import { Waveform as WaveformOverview } from './Overview';
import { Thumbnails } from './Thumbnails';
import { Canvas as WaveformCanvas } from './Canvas';
import { SeekBar } from './SeekBar';
import { canvasXToFrame, mouseEventToCanvasX } from './Helpers';
interface Props {
@ -323,6 +324,12 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
margin: '10px auto 0 auto',
display: 'block',
};
const seekBarStyles = {
width: '90%',
height: '50px',
margin: '0 auto',
display: 'block',
};
return (
<>
@ -351,6 +358,10 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
height={CanvasLogicalHeight}
></canvas>
</div>
<SeekBar
duration={mediaSet.audio.frames / mediaSet.audio.sampleRate}
style={seekBarStyles}
/>
<div style={controlPanelStyles}>
<button onClick={handlePlay}>Play</button>
<button onClick={handlePause}>Pause</button>