Skip to content

Commit 798b3f9

Browse files
Patrick Schustertobifalk
Patrick Schuster
authored andcommitted
ui: Add canvas recording functionality
Signed-off-by: Tobias Falkenstein <[email protected]>
1 parent f88eba5 commit 798b3f9

File tree

4 files changed

+120
-2
lines changed

4 files changed

+120
-2
lines changed

ui/src/App.js

+36-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { withCookies } from "react-cookie";
1818
import axios from "axios";
1919
import { DragDropContext, Draggable } from "react-beautiful-dnd";
2020
import { Spin, Progress } from "antd";
21-
21+
import RecordService from "./services/recordService";
2222
import Sidebar from "./components/sidebar";
2323
import ErrorBoundary from "./components/errorBoundary";
2424
import NavBar from "./components/navbar";
@@ -63,6 +63,7 @@ class App extends Component {
6363
replayIndex: 0,
6464
replayState: REPLAYSTATES.PAUSED,
6565
showErrorMessage: false,
66+
isRecoding: false
6667
};
6768

6869
// Helper variable to declare if Cloe-UI should perform one-time fetches,
@@ -87,6 +88,7 @@ class App extends Component {
8788
this.componentsOnLeftSide = cookies.get("firstColumn") || [0];
8889
this.componentsOnRightSide = cookies.get("secondColumn") || [1, 2, 3, 4, 5];
8990
this.componentOrder = ["left"];
91+
this.recordService = null;
9092

9193
this.stepWidth = null;
9294
this.replaySpeed = 1;
@@ -142,9 +144,11 @@ class App extends Component {
142144
sensors={cloeDataLive.sensors}
143145
replayState={this.state.replayState}
144146
streamingType={this.state.streamingType}
147+
isRecording={this.state.isRecoding}
145148
toggleReplay={this.toggleReplay}
146149
fastForward={this.fastForward}
147150
rewind={this.rewind}
151+
recordCanvas={this.recordCanvas}
148152
/>
149153
)
150154
},
@@ -606,6 +610,37 @@ class App extends Component {
606610
this.setState({ sideBarOpen: !this.state.sideBarOpen });
607611
};
608612

613+
recordCanvas = () => {
614+
let canvas = document.getElementById("scene").getElementsByTagName("canvas")[0];
615+
this.setState({ isRecoding: !this.state.isRecoding }, () => {
616+
const recordSettings = {
617+
timeslice: 100,
618+
mimeType: "video/webm",
619+
canvas: canvas
620+
};
621+
if (this.recordService === null) {
622+
this.recordService = new RecordService(recordSettings);
623+
}
624+
if (this.state.isRecoding) {
625+
this.recordService.startRecording();
626+
} else {
627+
this.recordService.stopRecording();
628+
let videoData = this.recordService.getData();
629+
let a = document.createElement("a");
630+
videoData.then((data) => {
631+
const url = URL.createObjectURL(data);
632+
//temporary creating an element for automatic download
633+
a.setAttribute("href", url);
634+
a.setAttribute("download", "cloe-replay-" + this.uuid);
635+
document.body.appendChild(a);
636+
a.click();
637+
document.body.removeChild(a);
638+
this.recordService = null;
639+
});
640+
}
641+
});
642+
};
643+
609644
rewind = () => {
610645
let currentIndex = this.state.replayIndex;
611646
this.setState({ replayState: REPLAYSTATES.PAUSED }, () => {

ui/src/components/rendering.jsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class Rendering extends Component {
106106

107107
render() {
108108
// Make sensors available for input fields.
109-
const { sensors, replayState, streamingType } = this.props;
109+
const { sensors, replayState, streamingType, isRecording } = this.props;
110110
const selectObjSensors = this.getAvailSensors(sensors, this.sensorType.Object);
111111
const selectLaneSensors = this.getAvailSensors(sensors, this.sensorType.Lane);
112112

@@ -186,6 +186,7 @@ class Rendering extends Component {
186186
toggleReplay={this.toggleReplay}
187187
fastForward={this.fastForward}
188188
rewind={this.rewind}
189+
recordCanvas={this.recordCanvas}
189190
></ReplayControlls>
190191
<div
191192
className="m-0 mt-2 text-light"
@@ -958,6 +959,10 @@ class Rendering extends Component {
958959
this.props.rewind();
959960
};
960961

962+
recordCanvas = () => {
963+
this.props.recordCanvas();
964+
};
965+
961966
resetRenderOptions = () => {
962967
if (this.state.helperAxesVisible) {
963968
this.scene.remove(this.axes);

ui/src/css/icon.scss

+17
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@
2828
cursor: pointer;
2929
}
3030

31+
.rec-icon-pending {
32+
width:10px;
33+
height:10px;
34+
border-radius:50px;
35+
line-height:100px;
36+
background:#e05048;
37+
38+
@keyframes glowing {
39+
50% {
40+
opacity: 0%;
41+
box-shadow: 0 0 20px #e05048;
42+
}
43+
}
44+
45+
animation: glowing 1500ms infinite;
46+
}
47+
3148
.icon-clickable {
3249
color: #6c757d;
3350
cursor: pointer;

ui/src/services/recordService.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2021 Robert Bosch GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
export default class RecordService {
17+
recorder = null;
18+
stream = null;
19+
blobs = [];
20+
constructor(recordSettings) {
21+
// Read the data in chunks.
22+
this.timeslice = recordSettings.timeslice;
23+
this.mimeType = recordSettings.mimeType === void 0 ? "video/webm" : recordSettings.mimeType;
24+
// Default value is 2.5mbps.
25+
this.videoBitsPerSecond = 2500000;
26+
this.canvas = recordSettings.canvas;
27+
}
28+
// Start the recording by capturing the canvas stream and collecting the data in the blobs[] array.
29+
startRecording() {
30+
this.stream = this.canvas.captureStream();
31+
this.recorder = new MediaRecorder(this.stream, {
32+
mimeType: this.mimeType,
33+
videoBitsPerSecond: this.videoBitsPerSecond
34+
});
35+
let self = this;
36+
this.recorder.ondataavailable = function(event) {
37+
if (event.data && event.data.size > 0) {
38+
self.blobs.push(event.data);
39+
}
40+
};
41+
this.recorder.start(this.timeslice);
42+
}
43+
// Stop the recording.
44+
stopRecording() {
45+
// if recording multiple times, consider choosing right track - getTracks()[?].
46+
this.stream.getTracks()[0].stop();
47+
this.recorder.stop();
48+
}
49+
50+
// return the blobs[] array.
51+
getData() {
52+
let self = this;
53+
return new Promise((resolve) => {
54+
resolve(
55+
new Blob(self.blobs, {
56+
type: self.mimeType
57+
})
58+
);
59+
});
60+
}
61+
}

0 commit comments

Comments
 (0)