import { grpc } from '@improbable-eng/grpc-web'; // import { // MediaSet as MediaSetPb, // GetRequest, // GetAudioRequest, // GetAudioProgress, // } from './generated/media_set_pb'; import { MediaSet, GrpcWebImpl, MediaSetServiceClientImpl, } from './generated/media_set'; import { useState, useEffect } from 'react'; import { VideoPreview } from './VideoPreview'; import { Overview } from './Overview'; import { Waveform } from './Waveform'; import { ControlBar } from './ControlBar'; import { SeekBar } from './SeekBar'; import './App.css'; import { Duration } from './generated/google/protobuf/duration'; const grpcHost = 'http://localhost:8888'; // ported from backend, where should they live? const thumbnailWidth = 177; const thumbnailHeight = 100; // Frames represents a selection of audio frames. export interface Frames { start: number; end: number; } function App(): JSX.Element { const [mediaSet, setMediaSet] = useState(null); const [video, _setVideo] = useState(document.createElement('video')); const [position, setPosition] = useState(0); const [viewport, setViewport] = useState({ start: 0, end: 0 }); // effects // TODO: error handling const videoID = new URLSearchParams(window.location.search).get('video_id'); if (videoID == null) { return <>; } // fetch mediaset on page load: useEffect(() => { (async function () { const rpc = new GrpcWebImpl('http://localhost:8888', {}); const service = new MediaSetServiceClientImpl(rpc); const mediaSet = await service.Get({ youtubeId: videoID }); console.log('got media set:', mediaSet); setMediaSet(mediaSet); // const handleProgress = (progress: GetAudioProgress) => { // console.log('got progress', progress); // }; // const audioRequest = new GetAudioRequest(); // audioRequest.setId(videoID); // audioRequest.setNumBins(1000); // GetMediaSetAudio(grpcHost, audioRequest, handleProgress); // console.log('fetching media...'); // const resp = await fetch( // `http://localhost:8888/api/media_sets/${videoID}` // ); // const respBody = await resp.json(); // if (respBody.error) { // console.log('error fetching media set:', respBody.error); // return; // } // const mediaSet = { // id: respBody.id, // source: respBody.source, // audio: { // sampleRate: respBody.audio.sample_rate, // bytes: respBody.audio.bytes, // frames: respBody.audio.frames, // channels: respBody.audio.channels, // }, // video: { // bytes: respBody.video.bytes, // thumbnailWidth: respBody.video.thumbnail_width, // thumbnailHeight: respBody.video.thumbnail_height, // durationMillis: Math.floor(respBody.video.duration / 1000 / 1000), // }, // }; // setMediaSet(mediaSet); })(); }, []); // setup player on first page load only: useEffect(() => { setInterval(() => { setPosition(video.currentTime); }, 100); }, []); // load video when MediaSet is loaded: useEffect(() => { if (mediaSet == null) { return; } return; video.src = `http://localhost:8888/api/media_sets/${videoID}/video`; video.muted = false; video.volume = 1; console.log('set video src', video.src); }, [mediaSet]); // set viewport when MediaSet is loaded: useEffect(() => { if (mediaSet == null) { return; } setViewport({ start: 0, end: mediaSet.audioFrames }); }, [mediaSet]); useEffect(() => { console.debug('viewport updated', viewport); }, [viewport]); // handlers const handleOverviewSelectionChange = (selection: Frames) => { console.log('in handleOverviewSelectionChange', selection); if (mediaSet == null) { return; } if (selection.start >= selection.end) { setViewport({ start: 0, end: mediaSet.audioFrames }); return; } setViewport({ ...selection }); }; // render component const containerStyles = { border: '1px solid black', width: '90%', margin: '1em auto', minHeight: '500px', height: '700px', display: 'flex', flexDirection: 'column', } as React.CSSProperties; const offsetPixels = Math.floor(thumbnailWidth / 2); if (mediaSet == null) { // TODO: improve return <>; } return ( <>
{ video.play(); }} onPause={() => { video.pause(); }} /> { console.log('onSelectionStart', x1); }} onSelectionChange={handleOverviewSelectionChange} /> { video.currentTime = position; }} />
); } export default App; function millisFromDuration(dur?: Duration): number { if (dur == undefined) { return 0; } return Math.floor(dur.seconds * 1000.0 + dur.nanos / 1000.0 / 1000.0); }