116 lines
3.0 KiB
TypeScript
116 lines
3.0 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
|
import { MediaSet } from './generated/media_set';
|
|
import { Frames, VideoPosition } from './App';
|
|
import { WaveformCanvas } from './WaveformCanvas';
|
|
import { HudCanvas, EmptySelectionAction } from './HudCanvas';
|
|
import { Observable } from 'rxjs';
|
|
|
|
export interface Selection {
|
|
start: number;
|
|
end: number;
|
|
}
|
|
|
|
interface Props {
|
|
peaks: Observable<number[]>;
|
|
mediaSet: MediaSet;
|
|
height: number;
|
|
offsetPixels: number;
|
|
position: VideoPosition;
|
|
viewport: Frames;
|
|
onSelectionChange: (selection: Selection) => void;
|
|
}
|
|
|
|
export const CanvasLogicalWidth = 2_000;
|
|
export const CanvasLogicalHeight = 500;
|
|
|
|
export const Overview: React.FC<Props> = ({
|
|
peaks,
|
|
mediaSet,
|
|
height,
|
|
offsetPixels,
|
|
position,
|
|
viewport,
|
|
onSelectionChange,
|
|
}: Props) => {
|
|
const [selectedPixels, setSelectedPixels] = useState({ start: 0, end: 0 });
|
|
const [positionPixels, setPositionPixels] = useState(0);
|
|
|
|
// side effects
|
|
|
|
// convert viewport from frames to canvas pixels.
|
|
useEffect(() => {
|
|
setSelectedPixels({
|
|
start: Math.round(
|
|
(viewport.start / mediaSet.audioFrames) * CanvasLogicalWidth
|
|
),
|
|
end: Math.round(
|
|
(viewport.end / mediaSet.audioFrames) * CanvasLogicalWidth
|
|
),
|
|
});
|
|
}, [viewport, mediaSet]);
|
|
|
|
// convert position from frames to canvas pixels:
|
|
useEffect(() => {
|
|
const ratio =
|
|
position.currentTime / (mediaSet.audioFrames / mediaSet.audioSampleRate);
|
|
setPositionPixels(Math.round(ratio * CanvasLogicalWidth));
|
|
frames;
|
|
}, [mediaSet, position]);
|
|
|
|
// handlers
|
|
|
|
// convert selection change from canvas pixels to frames, and trigger callback.
|
|
const handleSelectionChange = useCallback(
|
|
({ start, end }: Selection) => {
|
|
onSelectionChange({
|
|
start: Math.round((start / CanvasLogicalWidth) * mediaSet.audioFrames),
|
|
end: Math.round((end / CanvasLogicalWidth) * mediaSet.audioFrames),
|
|
});
|
|
},
|
|
[mediaSet]
|
|
);
|
|
|
|
// render component
|
|
|
|
const containerStyles = {
|
|
flexGrow: 0,
|
|
position: 'relative',
|
|
margin: `0 ${offsetPixels}px`,
|
|
height: `${height}px`,
|
|
} as React.CSSProperties;
|
|
|
|
const hudStyles = {
|
|
borderLineWidth: 4,
|
|
borderStrokeStyle: 'red',
|
|
positionLineWidth: 4,
|
|
positionStrokeStyle: 'red',
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div style={containerStyles}>
|
|
<WaveformCanvas
|
|
peaks={peaks}
|
|
channels={mediaSet.audioChannels}
|
|
width={CanvasLogicalWidth}
|
|
height={CanvasLogicalHeight}
|
|
strokeStyle="black"
|
|
fillStyle="#003300"
|
|
zIndex={1}
|
|
alpha={1}
|
|
></WaveformCanvas>
|
|
<HudCanvas
|
|
width={CanvasLogicalWidth}
|
|
height={CanvasLogicalHeight}
|
|
zIndex={1}
|
|
emptySelectionAction={EmptySelectionAction.SelectPrevious}
|
|
styles={hudStyles}
|
|
position={positionPixels}
|
|
selection={selectedPixels}
|
|
onSelectionChange={handleSelectionChange}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|