Skip to content

Commit

Permalink
Merge pull request #6 from VIDA-NYU/recordings-fix
Browse files Browse the repository at this point in the history
Fix quirks with recording controls
  • Loading branch information
soniacq authored Sep 12, 2022
2 parents 8068eaf + a6196e1 commit bf850d4
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 104 deletions.
62 changes: 60 additions & 2 deletions frontend/src/api/rest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import axios, {AxiosResponse, AxiosRequestConfig} from 'axios';
import React, { useRef, useEffect, useState } from 'react';
import React, { useEffect, useRef, useCallback, useState } from 'react';
import useSWR, { Key } from 'swr';
import { DeleteInfo } from '../components/HistoricalDataView';
import { API_URL, WS_API_URL, RECORDINGS_STATIC_PATH } from '../config';
import { RequestStatus } from './types';
import { useToken } from '../api/TokenContext';
import { useToken } from './TokenContext';
import useWebSocket, { ReadyState } from 'react-use-websocket';

/*
Expand Down Expand Up @@ -145,6 +145,64 @@ export function useDeleteRecording(token, fetchAuth, delData: DeleteInfo) {
};
}


const useInterval = (callback, delay) => {
const cb = useRef(callback);
useEffect(() => { cb.current = callback; }, [callback]);
useEffect(() => {
const id = delay && setInterval(() => cb.current?.(), delay);
return () => id && clearInterval(id);
}, [delay]);
};

export const useRecordingControls = () => {
const { fetchAuth } = useToken();
// store errors for starting and stopping requests
const [ [startError, stopError], setClickError ] = useState([null, null]);
const [ loading, setLoading ] = useState(null);
// used to store the meta about a recording that just finished
const [ finishedRecording, setFinishedRecording ] = useState(null);

// stay up to date on the current recording info
const { data: recordingData, error: recordingDataError, mutate } = useSWR(
fetchAuth && `${API_URL}/recordings/current?info=true`,
url => fetchAuth && fetchAuth(url).then(r=>r.json()));
const recordingId = recordingData?.name;

// update the recording info while it's active
useInterval(() => { mutate() }, recordingId ? 1000 : null)

// start/stop
const startRecording = () => {
setLoading(true);
fetchAuth && fetchAuth(`${API_URL}/recordings/start`, { method: 'PUT' })
.then(r=>r.text())
.then(d=>{ console.log(d);mutate();setClickError([null,null]) })
.catch(e=>setClickError([e,null]));
setFinishedRecording(null);
}
// useCallback(, [fetchAuth])
const stopRecording = () => {
setLoading(true);
fetchAuth && fetchAuth(`${API_URL}/recordings/stop`, { method: 'PUT' })
.then(r=>r.text()).then(d=>{ mutate(); setClickError([null,null]); setLoading(false) })
.catch(e=>{ setClickError([null,e]); setLoading(false) });
fetchAuth && recordingId && fetchAuth(`${API_URL}/recordings/${recordingId}`)
.then(r=>r.json())
.then(d=>setFinishedRecording(d)).catch(e=>console.error(e));
}
// useCallback(, [fetchAuth, recordingId])

return {
recordingId,
recordingData, finishedRecording,
loading,
recordingDataError, startError, stopError,
startRecording,
stopRecording,
}
}

/* ************* End SWR React hooks ***************** */


Expand Down
118 changes: 16 additions & 102 deletions frontend/src/components/LiveDataView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,98 +7,31 @@ import { TEST_PASS, TEST_USER } from '../config';
import LoadingButton from '@mui/lab/LoadingButton';
import VideocamOutlinedIcon from '@mui/icons-material/VideocamOutlined';
import StopCircleIcon from '@mui/icons-material/StopCircle';
import { getLiveVideo, useGetCurrentRecordingInfo, useGetRecording, useStartRecording, useStopRecording } from '../api/rest';
import { getLiveVideo, useGetCurrentRecordingInfo, useGetRecording, useStartRecording, useStopRecording, useRecordingControls } from '../api/rest';
import { RequestStatus, responseServer } from '../api/types';
import { LogsView, ImageView } from './LiveStream';

let interval = null;

// the app - once you're authenticated
function LiveVideo() {
const { recordingId, recordingData, recordingDataError, startError, stopError, finishedRecording, startRecording, stopRecording } = useRecordingControls();

const [recording, setRecording] = React.useState(false);
const [count, setCurrentRecordInfo] = React.useState<number>(0);
const [startStatus, setStartStatus] = React.useState<RequestStatus | undefined>(undefined);
const [stopStatus, setStopStatus] = React.useState<RequestStatus | undefined>(undefined);
const [startData, setStartData] = React.useState<responseServer>({response: undefined, error: undefined, status: undefined});
const [stopData, setStopData] = React.useState<responseServer>({response: undefined, error: undefined, status: undefined});



// get the token and authenticated fetch function
const { token, fetchAuth } = useToken();
const { response: respStartData, error: respStartError, status: respStartStatus } = useStartRecording(token, fetchAuth, startStatus);
const { response: respStopData, error: respStopError, status: respStopStatus } = useStopRecording(token, fetchAuth, stopStatus);
const {response: recordingData} = useGetRecording(token, fetchAuth, startData.response);

useEffect(() => {
if(recording && startStatus !== RequestStatus.IN_PROGRESS && stopStatus !== RequestStatus.ERROR){
setStartStatus(RequestStatus.STARTED)
setStopStatus(undefined);
}
if(!recording && startStatus === RequestStatus.IN_PROGRESS){
setStopStatus(RequestStatus.STARTED);
}
}, [recording])

useEffect(() => {
if (respStartStatus === RequestStatus.SUCCESS) {

if(respStartData === undefined){ //Something get wrong! Try to start again!
setRecording(false);
setStartStatus(RequestStatus.ERROR);
} else {
setStartStatus(RequestStatus.IN_PROGRESS);
interval = null;
let count_ = 0;
interval = setInterval(function() {
count_++
setCurrentRecordInfo(count_++);
}, 5000);
}
setStartData({response: respStartData, error: respStartError, status: respStartStatus});
}
}, [respStartStatus, respStartData])

useEffect(() => {
if (respStopStatus === RequestStatus.SUCCESS) {
if(respStopData === 0 || respStopData === undefined){ //Something get wrong! respStopData should be 1 if it was successful! Try to stop again!
setStopStatus(RequestStatus.ERROR);
setRecording(true);
} else {
setStartStatus(undefined);
setStopStatus(RequestStatus.IN_PROGRESS);
clearInterval(interval);
}
setStopData({response: respStopData, error: respStopError, status: respStopStatus});
}
}, [respStopStatus, respStopData])

function handleStartLoadingClick(value) {
setRecording(value);
}

useEffect(() => {
console.log("Refresh Page (total): ", count)
}, [count])

const currentRecordingInfo = recordingData && recordingData.name !== "undefined" && recordingData.name === startData.response &&
<>
Recording name: {startData.response}<br/>
const formatRecording = (recordingData) => recordingData?.name && <>
Recording name: {recordingData.name}<br/>
Duration: {recordingData.duration} <br/>
First Entry Time: {recordingData["first-entry-time"]} <br/>
Last Entry Time{recordingData["last-entry-time"]}
</>;
Last Entry Time: {recordingData["last-entry-time"]}
</>;

return (
<div className="mt-2 mr-2 ml-2">
{!recording && <Alert severity="info">Before starting a new recording, please refresh the page!</Alert>}
<Box sx={{ '& > button': { m: 1 } }}>
<LoadingButton
startIcon={<VideocamOutlinedIcon />}
size="small"
onClick={() => handleStartLoadingClick(true)}
loading={recording}
onClick={() => startRecording()}
loading={!!recordingId}
loadingIndicator="Recording…"
variant="contained"
>
Expand All @@ -108,41 +41,22 @@ function LiveVideo() {
startIcon={<StopCircleIcon />}
size="small"
color="primary"
onClick={() => handleStartLoadingClick(false)}
onClick={() => stopRecording()}
variant="contained"
disabled={!recording}
disabled={!recordingId}
>
Stop Recording
</Button>
</Box>
<Box style={{margin: 22}}>
{
(startStatus === undefined && startStatus !== RequestStatus.ERROR) || stopStatus === RequestStatus.IN_PROGRESS?
<></> :
startStatus === RequestStatus.STARTED ?
<Alert severity="info">Connecting to server ... </Alert> :
startStatus === RequestStatus.IN_PROGRESS && startData.response !== undefined ?
<Alert severity="success">SUCCESSFUL server connection. The video is being recorded.<br/><br/>
{currentRecordingInfo}
</Alert> :
<Alert severity="error">We couldn't connect with the server. Please try again!</Alert>
}
{
stopStatus === RequestStatus.ERROR && <Alert severity="error">Server Connection Issues: Please click again on the 'Stop Recording' button to finish your recording!</Alert>
}
{
stopStatus === RequestStatus.IN_PROGRESS && <Alert severity="success">Your recording was saved.<br/><br/>
{currentRecordingInfo}
</Alert>
}
{startError && <Alert severity="error">We couldn't connect with the server. Please try again!<br/><pre>{startError}</pre></Alert>}
{stopError && <Alert severity="error">Server Connection Issues: Please click again on the 'Stop Recording' button to finish your recording!<br/><pre>{stopError}</pre></Alert>}
{recordingDataError && <Alert severity="error">Error retrieving recording data: {recordingDataError}</Alert>}
{recordingData && <Alert severity="success">SUCCESSFUL server connection. The video is being recorded.<br/><br/>{formatRecording(recordingData)}</Alert>}
{finishedRecording && <Alert severity="success">Your recording was saved.<br/><br/>{formatRecording(finishedRecording)}</Alert>}
</Box>
<div style={{margin: 22}}
>
<div style={{margin: 22}}>
<div><span style={{fontWeight: "bold"}}>Live View</span></div>
<></>
{/* <img alt='live' src={getLiveVideo()} /> */}
{/* <img alt='live' src={`${API_URL}/mjpeg/main?last_entry_id=0`} /> */}
{/* <VideoCard title="Live Data" subtitle={""} path={"http://localhost:4000/video"}/> */}
<ImageView streamId='main' />
<LogsView streamId={'clip:action:steps'} formatter={str => (<ClipOutputsView data={JSON.parse(str)} />)} />
<LogsView streamId={'detic:image'} />
Expand Down

0 comments on commit bf850d4

Please sign in to comment.