poc: Add SeekBar
This commit is contained in:
parent
d5df962627
commit
084cabaca9
|
@ -1,6 +1,11 @@
|
||||||
import { CanvasLogicalWidth } from './Waveform';
|
import { CanvasLogicalWidth } from './Waveform';
|
||||||
import { MouseEvent } from 'react';
|
import { MouseEvent } from 'react';
|
||||||
|
|
||||||
|
interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: add tests
|
// TODO: add tests
|
||||||
export const mouseEventToCanvasX = (
|
export const mouseEventToCanvasX = (
|
||||||
evt: MouseEvent<HTMLCanvasElement>
|
evt: MouseEvent<HTMLCanvasElement>
|
||||||
|
@ -10,6 +15,20 @@ export const mouseEventToCanvasX = (
|
||||||
return (elementX * CanvasLogicalWidth) / rect.width;
|
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
|
// TODO: add tests
|
||||||
export const canvasXToFrame = (x: number, numFrames: number): number => {
|
export const canvasXToFrame = (x: number, numFrames: number): number => {
|
||||||
return Math.floor((x / CanvasLogicalWidth) * numFrames);
|
return Math.floor((x / CanvasLogicalWidth) * numFrames);
|
||||||
|
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -2,6 +2,7 @@ import { useEffect, useState, useRef, MouseEvent } from 'react';
|
||||||
import { Waveform as WaveformOverview } from './Overview';
|
import { Waveform as WaveformOverview } from './Overview';
|
||||||
import { Thumbnails } from './Thumbnails';
|
import { Thumbnails } from './Thumbnails';
|
||||||
import { Canvas as WaveformCanvas } from './Canvas';
|
import { Canvas as WaveformCanvas } from './Canvas';
|
||||||
|
import { SeekBar } from './SeekBar';
|
||||||
import { canvasXToFrame, mouseEventToCanvasX } from './Helpers';
|
import { canvasXToFrame, mouseEventToCanvasX } from './Helpers';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -323,6 +324,12 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
||||||
margin: '10px auto 0 auto',
|
margin: '10px auto 0 auto',
|
||||||
display: 'block',
|
display: 'block',
|
||||||
};
|
};
|
||||||
|
const seekBarStyles = {
|
||||||
|
width: '90%',
|
||||||
|
height: '50px',
|
||||||
|
margin: '0 auto',
|
||||||
|
display: 'block',
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -351,6 +358,10 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
||||||
height={CanvasLogicalHeight}
|
height={CanvasLogicalHeight}
|
||||||
></canvas>
|
></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
<SeekBar
|
||||||
|
duration={mediaSet.audio.frames / mediaSet.audio.sampleRate}
|
||||||
|
style={seekBarStyles}
|
||||||
|
/>
|
||||||
<div style={controlPanelStyles}>
|
<div style={controlPanelStyles}>
|
||||||
<button onClick={handlePlay}>Play</button>
|
<button onClick={handlePlay}>Play</button>
|
||||||
<button onClick={handlePause}>Pause</button>
|
<button onClick={handlePause}>Pause</button>
|
||||||
|
|
Loading…
Reference in New Issue