Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
adc6feb
WIP adding second websocket handling for cameras
gerth2 Oct 22, 2022
d4f9a9d
just more WIP
gerth2 Oct 22, 2022
8aded76
even more wip. Most java-side framework completed, but not yet debugged
gerth2 Oct 22, 2022
ddedeb4
IT LIVES. Still needs lots of cleanup. But we're transferring and dis…
gerth2 Oct 22, 2022
3645161
moved down an architecture layer. Improved multiple-camera handling
gerth2 Oct 22, 2022
856b249
Additional WIP to help improve smoothness and performance, though not…
gerth2 Oct 22, 2022
398f951
bugfixes galore
gerth2 Oct 23, 2022
728ebea
tweak compression
gerth2 Oct 23, 2022
81abe78
spotless
gerth2 Oct 23, 2022
50b258d
more tweaks for handling slow/intermittent streams
gerth2 Oct 23, 2022
df4733f
wpilibformat maybe?
gerth2 Oct 23, 2022
9b588d4
clang-format maybe?
gerth2 Oct 23, 2022
1465ec4
WIP - adding thinclient. I don't like it yet, it should be more auto-…
gerth2 Oct 23, 2022
6f3ab73
thinclient formatting fixups
gerth2 Oct 23, 2022
8be4df0
Reduced amount of empty send data by limiting to only one stream per …
gerth2 Oct 25, 2022
5439785
bugfixes, faster streaming, better mjpeg compression settings, thincl…
gerth2 Oct 25, 2022
0f76615
spotless and formatting
gerth2 Oct 25, 2022
a254b19
cmon wpiformat....
gerth2 Oct 25, 2022
d2e93a5
re-added mjpg streams
gerth2 Oct 25, 2022
1fa396d
added a loading GIF to imporve the feeling of responsiveness
gerth2 Oct 25, 2022
d50b9c5
formatting
gerth2 Oct 25, 2022
65cebde
urlparams and built-in thinclient
gerth2 Oct 25, 2022
1bb8b06
wpiformat
gerth2 Oct 25, 2022
7cde189
prevent wpiformat complaints
gerth2 Oct 25, 2022
d61aea5
Removed uint8 array and base64 conversion from client side
gerth2 Oct 28, 2022
2ed8c95
Synced up js implementations for ws streaming
gerth2 Oct 29, 2022
9d03566
formatting/spotless
gerth2 Oct 29, 2022
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
1 change: 1 addition & 0 deletions .styleguide
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ modifiableFileExclude {
\.jpg$
\.jpeg$
\.png$
\.gif$
\.so$
\.dll$
}
Expand Down
Binary file added photon-client/src/assets/loading.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified photon-client/src/assets/noStream.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 38 additions & 8 deletions photon-client/src/components/common/cv-image.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
:style="styleObject"
:src="src"
alt=""
@click="e => $emit('click', e)"
@click="e => {this.openThinclientStream(e)}"
>
</template>

<script>
export default {
name: "CvImage",
// eslint-disable-next-line vue/require-prop-types
props: ['address', 'scale', 'maxHeight', 'maxHeightMd', 'maxHeightLg', 'maxHeightXl', 'colorPicking', 'id', 'disconnected'],
props: ['idx', 'scale', 'maxHeight', 'maxHeightMd', 'maxHeightLg', 'maxHeightXl', 'colorPicking', 'id', 'disconnected'],
data() {
return {
seed: 1.0,
Expand Down Expand Up @@ -46,18 +46,48 @@
return ret;
}
},
src: {
port: {
get() {
return this.disconnected ? require("../../assets/noStream.jpg") : this.address + "?" + this.seed // This prevents caching
},
},
if(this.idx == 0){
return this.$store.state.cameraSettings[this.$store.state.currentCameraIndex].inputStreamPort;
} else {
return this.$store.state.cameraSettings[this.$store.state.currentCameraIndex].outputStreamPort;
}
}
}
},
watch : {
port(newPort, oldPort){
newPort;
oldPort;
this.reload();
},
disconnected(newVal, oldVal){
oldVal;
if(newVal){
this.wsStream.stopStream();
} else {
this.wsStream.startStream();
}
}
},
mounted() {
this.reload(); // Force reload image on creation
var wsvs = require('../../plugins/WebsocketVideoStream');
this.wsStream = new wsvs.WebsocketVideoStream(this.id, this.port, window.location.host);
},
unmounted() {
this.wsStream.stopStream();
this.wsStream.ws_close();
},
methods: {
reload() {
this.seed = new Date().getTime();
console.log("Reloading " + this.id + " with port " + String(this.port));
this.wsStream.setPort(this.port);
},
openThinclientStream(e){
e;
var URL = "/thinclient.html?port=" + String(this.port) + "&host=" + window.location.hostname;
window.open(URL, '_blank');
}
},
}
Expand Down
4 changes: 2 additions & 2 deletions photon-client/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ if (process.env.NODE_ENV === "production") {
Vue.prototype.$address = location.hostname + ":5800";
}

const wsURL = '//' + Vue.prototype.$address + '/websocket';
const wsDataURL = '//' + Vue.prototype.$address + '/websocket_data';

import VueNativeSock from 'vue-native-websocket';

Vue.use(VueNativeSock, wsURL, {
Vue.use(VueNativeSock, wsDataURL, {
reconnection: true,
reconnectionDelay: 100,
connectManually: true,
Expand Down
2 changes: 1 addition & 1 deletion photon-client/src/plugins/ColorPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function initColorPicker() {
if (!canvas)
canvas = document.createElement('canvas');

image = document.querySelector('#normal-stream');
image = document.querySelector('#raw-stream');
if (image !== null) {
canvas.width = image.width;
canvas.height = image.height;
Expand Down
148 changes: 148 additions & 0 deletions photon-client/src/plugins/WebsocketVideoStream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@


export class WebsocketVideoStream{


constructor(drawDiv, streamPort, host) {

this.drawDiv = drawDiv;
this.image = document.getElementById(this.drawDiv);
this.streamPort = streamPort;
this.serverAddr = "ws://" + host + "/websocket_cameras";
this.noStream = false;
this.noStreamPrev = false;
this.setNoStream();
this.ws_connect();
this.imgData = null;
this.imgDataTime = -1;
this.imgObjURL = null;
this.frameRxCount = 0;

requestAnimationFrame(()=>this.animationLoop());

}

animationLoop(){
var now = window.performance.now();

if((now - this.imgDataTime) > 2500 && this.imgData != null){
//Handle websocket send timeouts by restarting
this.setNoStream();
this.stopStream();
setTimeout(this.startStream.bind(this), 1000); //restart stream one second later
} else {
if(this.streamPort == null){
this.setNoStream();
} else if (this.imgData != null) {
//From https://stackoverflow.com/questions/67507616/set-image-src-from-image-blob/67507685#67507685
if(this.imgObjURL != null){
URL.revokeObjectURL(this.imgObjURL)
}
this.imgObjURL = URL.createObjectURL(this.imgData);

//Update the image with the new mimetype and image
this.image.src = this.imgObjURL;
this.noStream = false;

} else {
//Nothing, hold previous image while waiting for next frame
}
}


requestAnimationFrame(()=>this.animationLoop());
}

setNoStream() {
this.noStreamPrev = this.noStream;
this.noStream = true;
if(this.noStreamPrev == false && this.noStream == true){
//One-shot background change to preserve animation
this.image.src = require("../assets/loading.gif");
}
}

startStream() {
if(this.serverConnectionActive == true && this.streamPort > 0){
this.ws.send(JSON.stringify({"cmd": "subscribe", "port":this.streamPort}));
this.noStream = false;
}
}

stopStream() {
if(this.serverConnectionActive == true && this.streamPort > 0){
this.ws.send(JSON.stringify({"cmd": "unsubscribe"}));
this.noStream = true;
}
}

setPort(streamPort){
this.stopStream();
this.frameRxCount = 0;
this.streamPort = streamPort;
this.startStream();
}

ws_onOpen() {
// Set the flag allowing general server communication
this.serverConnectionActive = true;
console.log("Connected!");
this.startStream();
}

ws_onClose(e) {
this.setNoStream();

//Clear flags to stop server communication
this.ws = null;
this.serverConnectionActive = false;

console.log('Camera Socket is closed. Reconnect will be attempted in 0.5 second.', e.reason);
setTimeout(this.ws_connect.bind(this), 500);

if(!e.wasClean){
console.error('Socket encountered error!');
}

}

ws_onError(e){
e; //prevent unused failure
this.ws.close();
}

ws_onMessage(e){
if(typeof e.data === 'string'){
//string data from host
//TODO - anything to receive info here? Maybe "available streams?"
} else {
if(e.data.size > 0){
//binary data - a frame
this.imgData = e.data;
this.imgDataTime = window.performance.now();
this.frameRxCount++;
} else {
//TODO - server is sending empty frames?
}
}

}

ws_connect() {
this.ws = new WebSocket(this.serverAddr);
this.ws.binaryType = "blob";
this.ws.onopen = this.ws_onOpen.bind(this);
this.ws.onmessage = this.ws_onMessage.bind(this);
this.ws.onclose = this.ws_onClose.bind(this);
this.ws.onerror = this.ws_onError.bind(this);
console.log("Connecting to server " + this.serverAddr);
}

ws_close(){
this.ws.close();
}

}


export default {WebsocketVideoStream}
4 changes: 2 additions & 2 deletions photon-client/src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export default new Vuex.Store({
tiltDegrees: 0.0,
currentPipelineIndex: 0,
pipelineNicknames: ["Unknown"],
outputStreamPort: 1181,
inputStreamPort: 1182,
outputStreamPort: 0,
inputStreamPort: 0,
nickname: "Unknown",
videoFormatList: [
{
Expand Down
3 changes: 2 additions & 1 deletion photon-client/src/views/CamerasView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@
>
<template>
<CVimage
:address="$store.getters.streamAddress[1]"
:id="cameras-cal"
:idx=1
:disconnected="!$store.state.backendConnected"
scale="100"
style="border-radius: 5px;"
Expand Down
8 changes: 4 additions & 4 deletions photon-client/src/views/PipelineView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@
>
<div style="position: relative; width: 100%; height: 100%;">
<cv-image
:id="idx === 0 ? 'normal-stream' : ''"
:id="idx === 0 ? 'raw-stream' : 'processed-stream'"
ref="streams"
:address="$store.getters.streamAddress[idx]"
:idx=idx
:disconnected="!$store.state.backendConnected"
scale="100"
:max-height="$store.getters.isDriverMode ? '40vh' : '300px'"
:max-height-md="$store.getters.isDriverMode ? '50vh' : '380px'"
:max-height-lg="$store.getters.isDriverMode ? '55vh' : '390px'"
:max-height-xl="$store.getters.isDriverMode ? '60vh' : '450px'"
:alt="'Stream' + idx"
:alt="'Stream ' + idx"
:color-picking="$store.state.colorPicking && idx === 0"
@click="onImageClick"
/>
Expand All @@ -85,7 +85,7 @@
<v-card
color="primary"
>
<camera-and-pipeline-select @camera-name-changed="reloadStreams" />
<camera-and-pipeline-select />
</v-card>
<v-card
:disabled="$store.getters.isDriverMode || $store.state.colorPicking"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ private void saveAndWriteTask() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
logger.error("Exception waiting for settings semaphor", e);
logger.error("Exception waiting for settings semaphore", e);
}
}
}
Expand Down
Loading