import { useEffect, useState, useCallback } from 'react'; import { Frames, VideoPosition, newRPC } from './App'; import { MediaSetServiceClientImpl, MediaSet } from './generated/media_set'; import { WaveformCanvas } from './WaveformCanvas'; import { Selection, HudCanvas, EmptySelectionAction } from './HudCanvas'; import { from, Observable } from 'rxjs'; import { bufferCount } from 'rxjs/operators'; interface Props { mediaSet: MediaSet; position: VideoPosition; viewport: Frames; offsetPixels: number; onSelectionChange: (selection: Selection) => void; } export const CanvasLogicalWidth = 2000; export const CanvasLogicalHeight = 500; export const Waveform: React.FC = ({ mediaSet, position, viewport, offsetPixels, onSelectionChange, }: Props) => { const [peaks, setPeaks] = useState>(from([])); const [selectedFrames, setSelectedFrames] = useState({ start: 0, end: 0 }); const [selectedPixels, setSelectedPixels] = useState({ start: 0, end: 0, }); const [positionPixels, setPositionPixels] = useState(0); // effects // load peaks on MediaSet change useEffect(() => { (async function () { if (mediaSet == null) { return; } if (viewport.start >= viewport.end) { return; } console.log( 'fetch audio segment, range', viewport, 'numFrames', viewport.end - viewport.start ); const service = new MediaSetServiceClientImpl(newRPC()); const segment = await service.GetPeaksForSegment({ id: mediaSet.id, numBins: CanvasLogicalWidth, startFrame: viewport.start, endFrame: viewport.end, }); console.log('got segment', segment); const peaks = from(segment.peaks).pipe( bufferCount(mediaSet.audioChannels) ); setPeaks(peaks); })(); }, [viewport]); // convert position to canvas pixels useEffect(() => { const frame = Math.round(position.currentTime * mediaSet.audioSampleRate); if (frame < viewport.start || frame > viewport.end) { setPositionPixels(null); return; } const pixelsPerFrame = CanvasLogicalWidth / (viewport.end - viewport.start); const positionPixels = (frame - viewport.start) * pixelsPerFrame; setPositionPixels(positionPixels); }, [mediaSet, position, viewport]); // update selectedPixels on viewport change useEffect(() => { const start = Math.max(frameToCanvasX(selectedFrames.start), 0); const end = Math.min( frameToCanvasX(selectedFrames.end), CanvasLogicalWidth ); setSelectedPixels({ start, end }); }, [viewport, selectedFrames]); // handlers const handleSelectionChange = useCallback( (selection: Selection) => { setSelectedPixels(selection); const framesPerPixel = (viewport.end - viewport.start) / CanvasLogicalWidth; const selectedFrames = { start: Math.round(viewport.start + selection.start * framesPerPixel), end: Math.round(viewport.start + selection.end * framesPerPixel), }; setSelectedFrames(selectedFrames); onSelectionChange(selectedFrames); }, [viewport, selectedFrames] ); // helpers const frameToCanvasX = useCallback( (frame: number): number => { const pixelsPerFrame = CanvasLogicalWidth / (viewport.end - viewport.start); return Math.round((frame - viewport.start) * pixelsPerFrame); }, [viewport] ); // render component const containerStyles = { background: 'black', margin: '0 ' + offsetPixels + 'px', flexGrow: 1, position: 'relative', } as React.CSSProperties; const hudStyles = { borderLineWidth: 0, borderStrokeStyle: 'transparent', positionLineWidth: 6, positionStrokeStyle: 'red', }; return ( <>
); };