Status Recording
Previous chapter recalls: Change the perspective
- You know what is State, and how to get and modify.
- Autoring features via State.
- 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-state
) and corresponding html and jsx or tsx files.
js or ts files can first copy the previous chapter.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Change perspective | Knowing state</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css"
rel="stylesheet"
crossorigin="anonymous"
/>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"
rel="stylesheet"
/>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="app"></div>
<! - Mode switch -->
<nav class="navbar fixed-bottom navbar-light bg-light">
<div class="container-fluid justify-content-center">
<div class="btn-group">
<button class="btn btn-primary active js-Panorama">
Panoramic roaming
</button>
<button class="btn btn-primary js-Floorplan">
Overview of space
</button>
</div>
</div>
</nav>
<!-- Ring -->
<div class="card position-fixed m-2 top-0 end-0">
<button class="btn btn-light js-lookAround-start">
<i class="bi bi-arrow-repeat"></i>
</button>
<button class="btn btn-light js-lookAround-stop d-none">
<i class="bi bi-pause"></i>
</button>
</div>
<script type="module" src="./index"></script>
</body>
</html>
- JavaScript
- TypeScript
import { Five, parseWork } from "@realsee/five";
const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const five = new Five();
five.appendTo(document.querySelector("#app"));
fetch(workURL)
.then((res) => res.json())
.then((json) => {
const work = parseWork(json);
five.load(work);
});
window.addEventListener("resize", () => five.refresh(), false);
{
// === mode switching ===
const buttons = {
Panorama: document.querySelector(".js-Panorama"),
Floorplan: document.querySelector(".js-Floorplan"),
};
for (const [modeName, element] of Object.entries(buttons)) {
element.addEventListener(
"click",
() => {
five.setState({ mode: modeName });
},
false
);
}
five.on("stateChange", (state) => {
for (const [modeName, element] of Object.entries(buttons)) {
if (modeName === state.mode) {
element.classList.add("active");
} else {
element.classList.remove("active");
}
}
});
}
{
// === look around ===
let timer;
const startButton = document.querySelector(".js-lookAround-start");
const stopButton = document.querySelector(".js-lookAround-stop");
startButton.addEventListener(
"click",
() => {
window.clearInterval(timer);
timer = window.setInterval(() => {
five.setState({ longitude: five.state.longitude + Math.PI / 360 });
}, 16);
startButton.classList.add("d-none");
stopButton.classList.remove("d-none");
},
false
);
stopButton.addEventListener(
"click",
() => {
window.clearInterval(timer);
startButton.classList.remove("d-none");
stopButton.classList.add("d-none");
},
false
);
}
export {};
import { Five, Mode, parseWork } from "@realsee/five";
const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const five = new Five();
five.appendTo(document.querySelector("#app")!);
fetch(workURL)
.then((res) => res.json())
.then((json) => {
const work = parseWork(json);
five.load(work);
});
window.addEventListener("resize", () => five.refresh(), false);
{
// === mode switching ===
const buttons: Partial<Record<Mode, Element>> = {
Panorama: document.querySelector(".js-Panorama")!,
Floorplan: document.querySelector(".js-Floorplan")!,
};
for (const [modeName, element] of Object.entries(buttons)) {
element.addEventListener(
"click",
() => {
five.setState({ mode: modeName as Mode });
},
false
);
}
five.on("stateChange", (state) => {
for (const [modeName, element] of Object.entries(buttons)) {
if (modeName === state.mode) {
element.classList.add("active");
} else {
element.classList.remove("active");
}
}
});
}
{
// === look around ===
let timer: number | undefined;
const startButton = document.querySelector(".js -lookAround-start")!;
const stopButton = document.querySelector(".js-lookAround-stop")!;
startButton.addEventListener(
"click",
() => {
window.clearInterval(timer);
timer = window.setInterval(() => {
five.setState({ longitude: five.state.longitude + Math.PI / 360 });
}, 16);
startButton.classList.add("d-none");
stopButton.classList.remove("d-none");
},
false
);
stopButton.addEventListener(
"click",
() => {
window.clearInterval(timer);
startButton.classList.remove("d-none");
stopButton.classList.add("d-none");
},
false
);
}
export {};
Start service npm run dev
and jump to the current page "http://localhost:3000/src/3.recording-state/index.html".
Please see your console. The port number will change due to your configuration and current port occupancy pattern. Please check the console output.
If you use other development build tools, 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.
Build Recording UI
We added the UI button in the top left corner of the page, we designed:
- Start recording button
- Stop Recording Button
- Replay Button
and two status:
- Recording
- Playing
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>状态录制 | Recording state</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css"
rel="stylesheet"
crossorigin="anonymous"
/>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"
rel="stylesheet"
/>
<style>
html,
body,
#app {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- 模式切换 -->
<nav class="navbar fixed-bottom navbar-light bg-light">
<div class="container-fluid justify-content-center">
<div class="btn-group">
<button class="btn btn-primary active js-Panorama">Panoramic roaming</button>
<button class="btn btn-primary js-Floorplan">Overview of space</button>
</div>
</div>
</nav>
<!-- 环视 -->
<div class="card position-fixed m-2 top-0 end-0">
<button class="btn btn-light js-lookAround-start">
<i class="bi bi-arrow-repeat"></i>
</button>
<button class="btn btn-light js-lookAround-stop d-none">
<i class="bi bi-pause"></i>
</button>
</div>
<!-- 录制 -->
<div class="card position-fixed m-2 top-0 start-0">
<div class="btn-group align-items-center">
<button class="btn btn-light js-recording-start">
<i class="bi bi-record-fill"></i>
</button>
<button class="btn btn-light js-recording-stop d-none">
<i class="bi bi-stop-fill"></i>
</button>
<button class="btn btn-light js-recording-play">
<i class="bi bi-play-fill"></i>
</button>
<p class="badge bg-primary m-2 js-state-recording d-none">录制中</p>
<p class="badge bg-primary m-2 js-state-playing d-none">播放中</p>
</div>
</div>
<script type="module" src="./index"></script>
</body>
</html>
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.
- Implement the startRecording / endRecording method to start recording and end recording.
- Implementing record(state: State) method, recording content.Record between startRecording / endRecording.
- Implement play(callback) method, for playbacks, after calling play, install record content, call callback method,play State.
- JavaScript
- TypeScript
/**
* Recording class
*/
class Recorder {
constructor() {
this.startTime = 0;
this.records = null;
}
/**
* Whether
has been recorded */
hasRecords() {
return this.records !== null;
}
/**
* Record key frame
* @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 };
import { State } from "@realsee/five";
/**
* Recording class
*/
class Recorder {
private records: { state: State; time: number }[] | null = null;
private startTime: number;
constructor() {
this.startTime = 0;
this.records = null;
}
/**
* Whether
has been recorded */
hasRecords() {
return this.records !== null;
}
/**
* record key frame
* @param state five's state
* @returns
*/
record(state: 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 Keyframe callback
* @returns Whether there is a recording currently
*/
play(callback: (state: State, isFinal: boolean) => void) {
if (this.records === null || this.records.length === 0) return false;
const records = this.records.slice();
const keyframe = (keyIndex: number) => {
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 Logic
Add:after the code of looking around in the previous chapter
- JavaScript
- TypeScript
{
//=== 录制 ===
const recorder = new Recorder();
const startRecordingButton = document.querySelector(".js-recording-start");
const stopRecordingButton = document.querySelector(".js-recording-stop");
const playRecordingButton = document.querySelector(".js-recording-play");
const recordingState = document.querySelector(".js-state-recording");
const playingState = document.querySelector(".js-state-playing");
five.on("stateChange", (state) => {
if (recordingState.classList.contains("d-none")) return;
recorder.record(state);
});
startRecordingButton.addEventListener(
"click",
() => {
recorder.startRecording();
startRecordingButton.classList.add("d-none");
stopRecordingButton.classList.remove("d-none");
playRecordingButton.classList.add("d-none");
recordingState.classList.remove("d-none");
playingState.classList.add("d-none");
},
false
);
stopRecordingButton.addEventListener(
"click",
() => {
recorder.endRecording();
startRecordingButton.classList.remove("d-none");
stopRecordingButton.classList.add("d-none");
playRecordingButton.classList.remove("d-none");
recordingState.classList.add("d-none");
playingState.classList.add("d-none");
},
false
);
playRecordingButton.addEventListener(
"click",
() => {
const hasReocrd = recorder.play((state, isFinal) => {
five.setState(state);
if (isFinal) {
startRecordingButton.classList.remove("d-none");
stopRecordingButton.classList.add("d-none");
playRecordingButton.classList.remove("d-none");
recordingState.classList.add("d-none");
playingState.classList.add("d-none");
}
});
if (hasReocrd) {
startRecordingButton.classList.add("d-none");
stopRecordingButton.classList.add("d-none");
playRecordingButton.classList.add("d-none");
recordingState.classList.add("d-none");
playingState.classList.remove("d-none");
}
},
false
);
}
{
//=== 录制 ===
const recorder = new Recorder();
const startRecordingButton = document.querySelector(".js-recording-start")!;
const stopRecordingButton = document.querySelector(".js-recording-stop")!;
const playRecordingButton = document.querySelector(".js-recording-play")!;
const recordingState = document.querySelector(".js-state-recording")!;
const playingState = document.querySelector(".js-state-playing")!;
five.on("stateChange", (state) => {
if (recordingState.classList.contains("d-none")) return;
recorder.record(state);
});
startRecordingButton.addEventListener(
"click",
() => {
recorder.startRecording();
startRecordingButton.classList.add("d-none");
stopRecordingButton.classList.remove("d-none");
playRecordingButton.classList.add("d-none");
recordingState.classList.remove("d-none");
playingState.classList.add("d-none");
},
false
);
stopRecordingButton.addEventListener(
"click",
() => {
recorder.endRecording();
startRecordingButton.classList.remove("d-none");
stopRecordingButton.classList.add("d-none");
playRecordingButton.classList.remove("d-none");
recordingState.classList.add("d-none");
playingState.classList.add("d-none");
},
false
);
playRecordingButton.addEventListener(
"click",
() => {
const hasReocrd = recorder.play((state, isFinal) => {
five.setState(state);
if (isFinal) {
startRecordingButton.classList.remove("d-none");
stopRecordingButton.classList.add("d-none");
playRecordingButton.classList.remove("d-none");
recordingState.classList.add("d-none");
playingState.classList.add("d-none");
}
});
if (hasReocrd) {
startRecordingButton.classList.add("d-none");
stopRecordingButton.classList.add("d-none");
playRecordingButton.classList.add("d-none");
recordingState.classList.add("d-none");
playingState.classList.remove("d-none");
}
},
false
);
}
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
- Learn about the Live SDK gesture interaction.
- Gets a three-dimensional position to a point.