Extract OverviewWaveform to its own component
This commit is contained in:
parent
5a08bd62bf
commit
712fbd3142
|
@ -1,5 +1,6 @@
|
||||||
import { useEffect, useState, useRef, MouseEvent } from 'react';
|
import { useEffect, useState, useRef, MouseEvent } from 'react';
|
||||||
import { WaveformCanvas } from './WaveformCanvas';
|
import { Waveform as WaveformOverview } from './Waveform/Overview';
|
||||||
|
import { Canvas as WaveformCanvas } from './Waveform/Canvas';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
audioContext: AudioContext;
|
audioContext: AudioContext;
|
||||||
|
@ -19,18 +20,20 @@ type ZoomSettings = {
|
||||||
|
|
||||||
const defaultZoomSettings: ZoomSettings = { startFrame: 0, endFrame: 0 };
|
const defaultZoomSettings: ZoomSettings = { startFrame: 0, endFrame: 0 };
|
||||||
|
|
||||||
|
export const CanvasLogicalWidth = 2000;
|
||||||
|
export const CanvasLogicalHeight = 500;
|
||||||
|
|
||||||
export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
||||||
const [audioFile, setAudioFile] = useState<AudioFile | null>(null);
|
const [audioFile, setAudioFile] = useState<AudioFile | null>(null);
|
||||||
const [currentTime, setCurrentTime] = useState(0);
|
const [currentTime, setCurrentTime] = useState(0);
|
||||||
|
// TODO: fix linter error
|
||||||
const [audio, setAudio] = useState(new Audio());
|
const [audio, setAudio] = useState(new Audio());
|
||||||
const [zoomSettings, setZoomSettings] = useState(defaultZoomSettings);
|
const [zoomSettings, setZoomSettings] = useState(defaultZoomSettings);
|
||||||
const [waveformPeaks, setWaveformPeaks] = useState(null);
|
const [waveformPeaks, setWaveformPeaks] = useState(null);
|
||||||
const [overviewPeaks, setOverviewPeaks] = useState(null);
|
const [overviewPeaks, setOverviewPeaks] = useState(null);
|
||||||
|
|
||||||
const hudCanvasRef = useRef<HTMLCanvasElement>(null);
|
const hudCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const canvasLogicalWidth = 2000;
|
|
||||||
const canvasLogicalHeight = 500;
|
|
||||||
|
|
||||||
|
// TODO: error handling
|
||||||
const videoID = new URLSearchParams(window.location.search).get('video_id');
|
const videoID = new URLSearchParams(window.location.search).get('video_id');
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
@ -47,7 +50,7 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
||||||
if (audioFile == null) {
|
if (audioFile == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return Math.floor((x / canvasLogicalWidth) * audioFile.frames);
|
return Math.floor((x / CanvasLogicalWidth) * audioFile.frames);
|
||||||
};
|
};
|
||||||
|
|
||||||
const secsToCanvasX = (canvasWidth: number, secs: number): number => {
|
const secsToCanvasX = (canvasWidth: number, secs: number): number => {
|
||||||
|
@ -112,7 +115,7 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const resp = await fetch(
|
const resp = await fetch(
|
||||||
`http://localhost:8888/api/peaks?video_id=${videoID}&start=${zoomSettings.startFrame}&end=${endFrame}&bins=${canvasLogicalWidth}`
|
`http://localhost:8888/api/peaks?video_id=${videoID}&start=${zoomSettings.startFrame}&end=${endFrame}&bins=${CanvasLogicalWidth}`
|
||||||
);
|
);
|
||||||
const peaks = await resp.json();
|
const peaks = await resp.json();
|
||||||
console.log('respBody from peaks =', peaks);
|
console.log('respBody from peaks =', peaks);
|
||||||
|
@ -240,13 +243,10 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
} as React.CSSProperties;
|
} as React.CSSProperties;
|
||||||
|
|
||||||
const overviewCanvasProps = {
|
const overviewStyles = { ...wrapperProps, height: '90px' };
|
||||||
width: '90%',
|
|
||||||
height: '90px',
|
|
||||||
margin: '0 auto',
|
|
||||||
display: 'block',
|
|
||||||
} as React.CSSProperties;
|
|
||||||
|
|
||||||
|
// TODO: why is the margin needed?
|
||||||
|
const controlPanelStyles = { margin: '1em' } as React.CSSProperties;
|
||||||
const clockTextAreaProps = { color: '#999', width: '400px' };
|
const clockTextAreaProps = { color: '#999', width: '400px' };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -255,35 +255,31 @@ export const Waveform: React.FC<Props> = ({ audioContext }: Props) => {
|
||||||
<div style={wrapperProps}>
|
<div style={wrapperProps}>
|
||||||
<WaveformCanvas
|
<WaveformCanvas
|
||||||
peaks={waveformPeaks}
|
peaks={waveformPeaks}
|
||||||
logicalWidth={canvasLogicalWidth}
|
|
||||||
logicalHeight={canvasLogicalHeight}
|
|
||||||
fillStyle="black"
|
fillStyle="black"
|
||||||
strokeStyle="green"
|
strokeStyle="green"
|
||||||
style={waveformCanvasProps}
|
style={waveformCanvasProps}
|
||||||
></WaveformCanvas>
|
></WaveformCanvas>
|
||||||
<canvas
|
<canvas
|
||||||
ref={hudCanvasRef}
|
ref={hudCanvasRef}
|
||||||
width={canvasLogicalWidth}
|
|
||||||
height={canvasLogicalHeight}
|
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleMouseUp}
|
||||||
style={hudCanvasProps}
|
style={hudCanvasProps}
|
||||||
|
width={CanvasLogicalWidth}
|
||||||
|
height={CanvasLogicalHeight}
|
||||||
></canvas>
|
></canvas>
|
||||||
</div>
|
</div>
|
||||||
<WaveformCanvas
|
<WaveformOverview
|
||||||
peaks={overviewPeaks}
|
peaks={overviewPeaks}
|
||||||
logicalWidth={canvasLogicalWidth}
|
style={overviewStyles}
|
||||||
logicalHeight={canvasLogicalHeight}
|
></WaveformOverview>
|
||||||
fillStyle="grey"
|
<div style={controlPanelStyles}>
|
||||||
strokeStyle="black"
|
<button onClick={handlePlay}>Play</button>
|
||||||
style={overviewCanvasProps}
|
<button onClick={handlePause}>Pause</button>
|
||||||
></WaveformCanvas>
|
<button onClick={handleZoomIn}>+</button>
|
||||||
<button onClick={handlePlay}>Play</button>
|
<button onClick={handleZoomOut}>-</button>
|
||||||
<button onClick={handlePause}>Pause</button>
|
<input type="readonly" style={clockTextAreaProps} />
|
||||||
<button onClick={handleZoomIn}>+</button>
|
</div>
|
||||||
<button onClick={handleZoomOut}>-</button>
|
|
||||||
<input type="readonly" style={clockTextAreaProps} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
import { useEffect, useState, useRef, MouseEvent } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { CanvasLogicalWidth, CanvasLogicalHeight } from '../Waveform';
|
||||||
|
|
||||||
const maxPeakValue = 32768;
|
const maxPeakValue = 32_768;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
peaks: number[][] | null;
|
peaks: number[][] | null;
|
||||||
logicalWidth: number;
|
|
||||||
logicalHeight: number;
|
|
||||||
strokeStyle: string;
|
strokeStyle: string;
|
||||||
fillStyle: string;
|
fillStyle: string;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WaveformCanvas: React.FC<Props> = (props: Props) => {
|
// Canvas is a generic component that renders a waveform to a canvas.
|
||||||
|
//
|
||||||
|
// Properties:
|
||||||
|
//
|
||||||
|
// peaks: a 2d array of uint16s representing the peak values. Each inner array length should match logicalWidth
|
||||||
|
// strokeStyle: waveform style
|
||||||
|
// fillStyle: background style
|
||||||
|
// style: React.CSSProperties applied to canvas element
|
||||||
|
export const Canvas: React.FC<Props> = (props: Props) => {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -57,8 +64,8 @@ export const WaveformCanvas: React.FC<Props> = (props: Props) => {
|
||||||
<>
|
<>
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
width={props.logicalWidth}
|
width={CanvasLogicalWidth}
|
||||||
height={props.logicalHeight}
|
height={CanvasLogicalHeight}
|
||||||
style={props.style}
|
style={props.style}
|
||||||
></canvas>
|
></canvas>
|
||||||
</>
|
</>
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { Canvas as WaveformCanvas } from './Canvas';
|
||||||
|
import { CanvasLogicalWidth, CanvasLogicalHeight } from '../Waveform';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
peaks: number[][] | null;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Waveform: React.FC<Props> = (props: Props) => {
|
||||||
|
const hudCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
|
// handlers
|
||||||
|
|
||||||
|
const handleMouseDown = () => {
|
||||||
|
console.log('mousedown');
|
||||||
|
};
|
||||||
|
|
||||||
|
// render component
|
||||||
|
|
||||||
|
const canvasStyles = {
|
||||||
|
width: '100%',
|
||||||
|
height: '100px',
|
||||||
|
margin: '0 auto',
|
||||||
|
display: 'block',
|
||||||
|
} as React.CSSProperties;
|
||||||
|
|
||||||
|
const hudCanvasStyles = {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
} as React.CSSProperties;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={props.style}>
|
||||||
|
<WaveformCanvas
|
||||||
|
peaks={props.peaks}
|
||||||
|
fillStyle="grey"
|
||||||
|
strokeStyle="black"
|
||||||
|
style={canvasStyles}
|
||||||
|
></WaveformCanvas>
|
||||||
|
<canvas
|
||||||
|
ref={hudCanvasRef}
|
||||||
|
style={hudCanvasStyles}
|
||||||
|
width={CanvasLogicalWidth}
|
||||||
|
height={CanvasLogicalHeight}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
></canvas>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue