Skip to main content

Status Recording

Previous chapter recalls: Change the perspective

  • You know what is State, and how to get and modify.
  • Autoring features via State.
This chapter can learn to
  • Record user operations via State.
  • Restore the user action picture with State.

Preparatory work

Like the previous section, we create a new directory (src/3.recording-stateand corresponding html and jsx or tsx files.

jsx or tsx files can first copy the previous chapter.

src/3.recording-state/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>state recording | Recording state</title>
<style>
* {
margin: 0;
padding: 0;
}
html,
body #app {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index"></script>
</body>
</html>
src/3.recording-state/useFetchWork.js
import { useState, useEffect } from "react";
import { parseWork } from "@realsee/five";

/**
* React Hook: via work. json address get work object
* @param url work.json data address
* @returns work object, if getting, return null
*/
function useFetchWork(url) {
const [work, setWork] = useState(null);
useEffect(() => {
setWork(null);
fetch(url)
.then((response) => response.text())
.then((text) => setWork(parseWork(text)));
}, [url]);
return work;
}

export { useFetchWork };
src/3.recording-state/useWindowDimensions.js
import { useState, useEffect } from "react";

/**
* Get the size of the current window
*/
function getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}

/**
* React Hook: Get the size of the current window
*/
function useWindowDimensions() {
const [size, setSize] = useState(getWindowDimensions);
useEffect(() => {
const listener = () => setSize(getWindowDimensions());
window.addEventListener("resize", listener, false);
return () => window.removeEventListener("resize", listener, false);
});
return size;
}

export { useWindowDimensions };
src/3.recording-state/ModeController.jsx
import React from "react";
import { Five } from "@realsee/five";
import { useFiveCurrentState } from "@realsee/five/react";
import BottomNavigation from "@mui/material/BottomNavigation";
import BottomNavigationAction from "@mui/material/BottomNavigationAction";
import Paper from "@mui/material/Paper";
import DirectionsWalkIcon from "@mui/icons-material/DirectionsWalk";
import ViewInArIcon from "@mui/icons-material/ViewInAr";

/**
* React Component: Modal Control
*/
const ModeController = () => {
const [state, setState] = useFiveCurrentState();
return (
<Paper sx={{ position: "fixed", bottom: 0, left: 0, right: 0 }}>
<BottomNavigation
showLabels
value={state.mode}
onChange={(_, newValue) => {
setState({ mode: newValue });
}}
>
<BottomNavigationAction
label="Panorama roaming"
icon={<DirectionsWalkIcon />}
value={Five.Mode.Panorama}
/>
<BottomNavigationAction
label="Space overview"
icon={<ViewInArIcon />}
value={Five.Mode.Floorplan}
/>
</BottomNavigation>
</Paper>
);
};

export { ModeController };
src/3.recording-state/LookAroundController.jsx
import React, { useState, useEffect } from "react";
import { useFiveCurrentState } from "@realsee/five/react";
import IconButton from "@mui/material/IconButton";
import Paper from "@mui/material/Paper";
import FlipCameraAndroidIcon from "@mui/icons-material/FlipCameraAndroid";
import PauseIcon from "@mui/icons-material/Pause";

/**
* ReactComponent: Auto Ring Button
*/
const LookAroundController = () => {
const [currentState, setState] = useFiveCurrentState();
const [active, toggleActive] = useState(false);
useEffect(() => {
if (active) {
const timer = window.setInterval(() => {
setState((prevState) => {
return { longitude: prevState.longitude + Math.PI / 360 };
});
}, 16);
return () => window.clearInterval(timer);
}
}, [active]);
return (
<Paper sx={{ position: "fixed", top: 10, right: 10 }}>
{active ? (
<IconButton onClick={() => toggleActive(false)}>
<PauseIcon />
</IconButton>
) : (
<IconButton onClick={() => toggleActive(true)}>
<FlipCameraAndroidIcon />
</IconButton>
)}
</Paper>
);
};

export { LookAroundController };
src/3.recording-state/App.jsx
import React from "react";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { useFetchWork } from "./useFetchWork";
import { useWindowDimensions } from "./useWindowDimensions";
import { ModeController } from "./ModeController";
import { LookAroundController } from "./LookAroundController";

/** of work.json Data URL */
const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";

const FiveProvider = createFiveProvider();
const App = () => {
const work = useFetchWork(workURL);
const size = useWindowDimensions();
return (
work && (
<FiveProvider initialWork={work}>
<FiveCanvas {...size} />
<ModeController />
<LookAroundController />
</FiveProvider>
)
);
};

export { App };
src/3.recording-state/index.jsx
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";

ReactDOM.render(<App />, document.querySelector("#app"));

export {};

Start service npm run dev and jump to the current page "http://localhost:3000/src/3.recording-state/index.html".

info

Please see your console. The port number will change due to your configuration and current port occupancy, please check the console output. If you use another development build tool, please start the service as required by your own development build tool.

Recording / Playback

This chapter continues to complete an interesting app with State. This chapter will complete such an app, log users into Stateon the page and can replay these actions.

Write Recorder

First, we need to write Recorder to support records and playbacks. Recorder category is not the content of Five , it is only written to achieve the effect of this chapter.

  1. Implement the startRecording / endRecording method to start recording and end recording.
  2. Implementing record(state: State) method, recording content.Record content between startRecording / endRecording.
  3. Implement play(callback) method, for playbacks, after calling play, install record content, call callback method,play State.
src/3.recording-state/recorder.js
/**
* Recording class
*/
class Recorder {
constructor() {
this.startTime = 0;
this.records = null;
}
/**
* Whether
has been recorded */
hasRecords() {
return this.records !== null;
}
/**
* Record keyframe
* @param state five of state
* @returns
*/
record(state) {
if (this.records === null) return;
this.records.push({
state: Object.assign({}, state),
time : Date.now() - this.startTime
});
}
/**
* start recording
*/
startRecording() {
this.startTime = Date.now();
this.records = [] ;
}
/**
* End recording
*/
endRecording() {
this.startTime = 0;
}
/**
* Playback recording
* @param callback key frame callback
* @returns whether the current There are records
*/
play(callback) {
if (this.records === null || this.records.length === 0) return false;
const records = this.records.slice();
const keyframe = (keyIndex) => {
const current = records[keyIndex];
const next = records[keyIndex + 1];
callback(current.state, next === undefined);
if (next) {
const delay = next.time - current.time;
setTimeout(() => keyframe(keyIndex + 1), delay);
}
};
keyframe(0);
return true;
}
}

export { Recorder };

Write Recording Component

Put Recorder into React components.

  1. Add a RecorderController file to write components.

  2. Two React state states in the component recording and playing.Separate status in recording/playback.

  3. useFiveEventCallback Gets the Built-in method callback.
    Here we call the stateChange event, which will be triggered when state changes, and then call recorder.record(state) method to record State.

    More event descriptions see Five events list

  4. When the playback button is pressed, call the recorder.play(callback) method, then call the previously recorded state arc call callback method.

  5. The setState method to call the record and apply the replay content to change the picture.

src/3.recording-state/RecorderController.jsx
import React, { useState, useCallback } from "react";
import { useFiveEventCallback, useFiveState } from "@realsee/five/react";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import Paper from "@mui/material/Paper";
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
import StopIcon from "@mui/icons-material/Stop";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import { Recorder } from "./recorder";

/**
* ReactComponent: state recording
*/
const RecorderController = () => {
const [state, setState] = useFiveState();
const [recording, toggleRecording] = useState(false);
const [playing, togglePlaying] = useState(false);
const [recorder] = useState(() => new Recorder());
const startRecording = useCallback(() => {
recorder.startRecording();
toggleRecording(true);
}, [recorder]);
const endRecording = useCallback(() => {
recorder.endRecording();
toggleRecording(false);
}, [recorder]);
const play = useCallback(() => {
const hasRecord = recorder.play((state, isFinal) => {
setState(state);
togglePlaying(!isFinal);
});
togglePlaying(hasRecord);
}, []);
useFiveEventCallback("stateChange", (state) => {
recorder.record(state);
});
if (recording) {
return (
<Paper sx={{ position: "fixed", top: 10, left: 10 }}>
<IconButton onClick={endRecording}>
<StopIcon />
</IconButton>
<Button disabled>录制中</Button>
</Paper>
);
}
if (playing) {
return (
<Paper sx={{ position: "fixed", top: 10, left: 10 }}>
<Button disabled>回放中</Button>
</Paper>
);
}
return (
<Paper sx={{ position: "fixed", top: 10, left: 10 }}>
<IconButton onClick={startRecording}>
<FiberManualRecordIcon />
</IconButton>
<IconButton onClick={play}>
<PlayArrowIcon />
</IconButton>
</Paper>
);
};

export { RecorderController };

Use Status Recording Component

Insert into App file GiveProvider

src/3.recording-state/App.jsx
import React from "react";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { useFetchWork } from "./useFetchWork";
import { useWindowDimensions } from "./useWindowDimensions";
import { ModeController } from "./ModeController";
import { LookAroundController } from "./LookAroundController";
import { RecorderController } from "./RecorderController";
// highlight -end

/** Data URL of work.json */
const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";

const FiveProvider = createFiveProvider();
const App = () => {
const work = useFetchWork(workURL);
const size = useWindowDimensions();
return (
work && (
<FiveProvider initialWork={work}>
<FiveCanvas {...size} />
<ModeController />
<LookAroundController />
<RecorderController />
</FiveProvider>
)
);
};

export { App };

Back to your browser to view will find a record and play button in the upper left corner of your page.Try whether the trial function meets expectations.

Yes, you’re and can write what complex programs are🥳

The next section will be completed by you

Next chapter we need to deal with the three-dimensional spatial model.
  • Learn about the Live SDK gesture interaction.
  • Gets a three-dimensional position to a point.