Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix quirks with recording controls #6

Merged
merged 2 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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