Improve overview selection behaviour

This commit is contained in:
Rob Watson 2021-09-27 19:47:42 +02:00
parent 951abb608d
commit 8cade215af
3 changed files with 60 additions and 23 deletions

View File

@ -238,7 +238,6 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
} }
video.pause(); video.pause();
console.log('paused video');
}; };
const handleZoomIn = () => { const handleZoomIn = () => {
@ -267,15 +266,36 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
setZoomSettings(settings); setZoomSettings(settings);
}; };
const handleSelectionStart = (x: number) => {
const video = videoRef.current;
if (video == null) {
return;
}
const frame = canvasXToFrame(x, mediaSet.audio.frames);
if (video.paused) {
video.currentTime = frame / mediaSet.audio.sampleRate;
}
};
const handleSelectionChange = (selection: Selection) => { const handleSelectionChange = (selection: Selection) => {
console.log('in handleSelectionChange handler');
if (mediaSet == null) { if (mediaSet == null) {
return; return;
} }
const startFrame = canvasXToFrame(selection.x1, mediaSet.audio.frames);
const endFrame = canvasXToFrame(selection.x2, mediaSet.audio.frames);
const settings: ZoomSettings = { const settings: ZoomSettings = {
startFrame: canvasXToFrame(selection.x1, mediaSet.audio.frames), startFrame: startFrame,
endFrame: canvasXToFrame(selection.x2, mediaSet.audio.frames), endFrame: endFrame,
}; };
setZoomSettings(settings); setZoomSettings(settings);
const video = videoRef.current;
if (video == null) {
return;
}
video.currentTime = startFrame / mediaSet.audio.sampleRate;
}; };
// render component: // render component:
@ -335,6 +355,7 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
peaks={overviewPeaks} peaks={overviewPeaks}
numFrames={mediaSet.audio.frames} numFrames={mediaSet.audio.frames}
style={overviewStyles} style={overviewStyles}
onSelectionStart={handleSelectionStart}
onSelectionChange={handleSelectionChange} onSelectionChange={handleSelectionChange}
></WaveformOverview> ></WaveformOverview>
<div style={wrapperProps}> <div style={wrapperProps}>

View File

@ -16,12 +16,6 @@ export const canvasXToFrame = (x: number, numFrames: number): number => {
return Math.floor((x / CanvasLogicalWidth) * numFrames); return Math.floor((x / CanvasLogicalWidth) * numFrames);
}; };
// // TODO: add tests
// export const canvasXToSecs = (x: number, numFrames: number): number => {
// const frame := canvasXToFrame(x, numFrames)
// }
// TODO: add tests // TODO: add tests
export const secsToCanvasX = ( export const secsToCanvasX = (
secs: number, secs: number,

View File

@ -11,6 +11,7 @@ type Props = {
peaks: number[][] | null; peaks: number[][] | null;
numFrames: number; numFrames: number;
style: React.CSSProperties; style: React.CSSProperties;
onSelectionStart: (x1: number) => void;
onSelectionChange: (selection: Selection) => void; onSelectionChange: (selection: Selection) => void;
}; };
@ -19,17 +20,21 @@ enum Mode {
Selecting, Selecting,
} }
// TODO: render position marker during playback
export const Waveform: React.FC<Props> = (props: Props) => { export const Waveform: React.FC<Props> = (props: Props) => {
const hudCanvasRef = useRef<HTMLCanvasElement>(null); const hudCanvasRef = useRef<HTMLCanvasElement>(null);
const [mode, setMode] = useState(Mode.Normal); const [mode, setMode] = useState(Mode.Normal);
const defaultSelection: Selection = { x1: 0, x2: 0 }; const defaultSelection: Selection = { x1: 0, x2: 0 };
// selection is the current selection in canvas coordinates:
const [selection, setSelection] = useState(defaultSelection); const [selection, setSelection] = useState(defaultSelection);
// TODO: is it needed? // newSelection is a new selection in the process of being drawn by the user.
const [newStartSelection, setNewStartSelection] = useState(0); // It is only useful if Mode.Selecting is active.
const [newSelection, setNewSelection] = useState(defaultSelection);
// effects // effects
// draw the overview waveform
useEffect(() => { useEffect(() => {
(async function () { (async function () {
const canvas = hudCanvasRef.current; const canvas = hudCanvasRef.current;
@ -46,7 +51,13 @@ export const Waveform: React.FC<Props> = (props: Props) => {
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
if (selection.x1 >= selection.x2) { let currentSelection: Selection;
if (mode == Mode.Selecting) {
currentSelection = newSelection;
} else {
currentSelection = selection;
}
if (currentSelection.x1 >= currentSelection.x2) {
return; return;
} }
@ -54,7 +65,12 @@ export const Waveform: React.FC<Props> = (props: Props) => {
ctx.strokeStyle = 'red'; ctx.strokeStyle = 'red';
ctx.lineWidth = 2; ctx.lineWidth = 2;
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.rect(selection.x1, 2, selection.x2 - selection.x1, canvas.height - 8); ctx.rect(
currentSelection.x1,
2,
currentSelection.x2 - currentSelection.x1,
canvas.height - 8
);
ctx.fill(); ctx.fill();
ctx.stroke(); ctx.stroke();
})(); })();
@ -63,35 +79,41 @@ export const Waveform: React.FC<Props> = (props: Props) => {
// handlers // handlers
const handleMouseDown = (evt: MouseEvent<HTMLCanvasElement>) => { const handleMouseDown = (evt: MouseEvent<HTMLCanvasElement>) => {
console.log('mousedown');
if (mode != Mode.Normal) { if (mode != Mode.Normal) {
return; return;
} }
setMode(Mode.Selecting); setMode(Mode.Selecting);
setNewStartSelection(mouseEventToCanvasX(evt));
const x = mouseEventToCanvasX(evt);
setNewSelection({ x1: x, x2: x });
props.onSelectionStart(x);
}; };
const handleMouseMove = (evt: MouseEvent<HTMLCanvasElement>) => { const handleMouseMove = (evt: MouseEvent<HTMLCanvasElement>) => {
console.log('mousemove');
if (mode != Mode.Selecting) { if (mode != Mode.Selecting) {
return; return;
} }
const selection: Selection = { const x = mouseEventToCanvasX(evt);
x1: newStartSelection, if (x == newSelection.x2) {
x2: mouseEventToCanvasX(evt), return;
}; }
setSelection(selection);
setNewSelection({ ...newSelection, x2: x });
}; };
const handleMouseUp = () => { const handleMouseUp = () => {
console.log('mouseup');
if (mode != Mode.Selecting) { if (mode != Mode.Selecting) {
return; return;
} }
setMode(Mode.Normal); setMode(Mode.Normal);
props.onSelectionChange(selection);
// TODO: better shallow equality check?
if (selection.x1 !== newSelection.x1 || selection.x2 !== newSelection.x2) {
setSelection(newSelection);
props.onSelectionChange(newSelection);
}
}; };
// render component // render component