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'; // Audio corresponds to media.Audio. export interface Audio { bytes: number; channels: number; frames: number; sampleRate: number; } // Video corresponds to media.Video. export interface Video { bytes: number; thumbnailWidth: number; thumbnailHeight: number; durationMillis: number; } // MediaSet corresponds to media.MediaSet. export interface MediaSet { id: string; source: string; audio: Audio; video: Video; } // 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'); // fetch mediaset on page load: useEffect(() => { (async function () { 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; } 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.audio.frames }); }, [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.audio.frames }); 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; let offsetPixels = 75; if (mediaSet != null) { offsetPixels = Math.floor(mediaSet.video.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;