import { useEffect, useState } from 'react'; import { Frames, VideoPosition, newRPC } from './App'; import { MediaSetServiceClientImpl, MediaSet } from './generated/media_set'; import { WaveformCanvas } from './WaveformCanvas'; import { HudCanvas, SelectionChangeEvent } from './HudCanvas'; import { EmptySelectionAction, SelectionMode } from './HudCanvasState'; import { from, Observable } from 'rxjs'; import { bufferCount } from 'rxjs/operators'; interface Props { mediaSet: MediaSet; position: VideoPosition; viewport: Frames; onSelectionChange: (selectionState: SelectionChangeEvent) => void; } export const CanvasLogicalWidth = 2000; export const CanvasLogicalHeight = 500; export const Waveform: React.FC = ({ mediaSet, position, viewport, onSelectionChange, }: Props) => { const [peaks, setPeaks] = useState>(from([])); const [selectedFrames, setSelectedFrames] = useState({ start: 0, end: 0 }); // selectedPixels are the currently selected waveform canvas pixels. They may // not match the actual selection due to the viewport setting, and probably // shouldn't be leaking outside of the HudCanvasState. 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; } 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, mediaSet]); // 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 // convert selection change from canvas pixels to frames, and trigger callback. const handleSelectionChange = (selectionState: SelectionChangeEvent) => { const { mode, prevMode, selection } = selectionState; 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), }; if (mode == SelectionMode.Normal && prevMode != SelectionMode.Normal) { setSelectedPixels(selection); setSelectedFrames(selectedFrames); } onSelectionChange({ ...selectionState, selection: selectedFrames, }); }; // helpers const frameToCanvasX = (frame: number): number => { const numFrames = viewport.end - viewport.start; if (numFrames == 0) { return 0; } const pixelsPerFrame = CanvasLogicalWidth / numFrames; return Math.round((frame - viewport.start) * pixelsPerFrame); }; // render component const hudStyles = { borderLineWidth: 0, borderStrokeStyle: 'transparent', positionLineWidth: 6, positionStrokeStyle: 'red', hoverPositionStrokeStyle: '#666666', }; return ( <>
); };