Rearranged main components
This commit is contained in:
parent
250fd07e9a
commit
eff03bfa2c
34 changed files with 734 additions and 938 deletions
|
|
@ -1,338 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
id="stream-display"
|
||||
ref="streamDisplay"
|
||||
v-observe-visibility="visibilityChanged"
|
||||
class="stream-display uk-width-1-1 uk-height-1-1 scrollTarget"
|
||||
>
|
||||
<img
|
||||
v-if="isVisible"
|
||||
ref="click-frame"
|
||||
class="uk-align-center uk-margin-remove-bottom"
|
||||
:hidden="!streamEnabled"
|
||||
:src="streamImgUri"
|
||||
alt="Stream"
|
||||
@dblclick="clickMonitor"
|
||||
/>
|
||||
|
||||
<div v-if="!streamEnabled" class="uk-height-1-1">
|
||||
<div v-if="$store.state.waiting" class="uk-position-center">
|
||||
<div uk-spinner="ratio: 4.5"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="$store.state.globalSettings.disableStream"
|
||||
class="uk-position-center position-relative text-center"
|
||||
>
|
||||
Stream preview disabled
|
||||
</div>
|
||||
|
||||
<div v-else class="uk-position-center position-relative text-center">
|
||||
No active connection
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "StreamDisplay",
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
isVisible: false,
|
||||
displaySize: [0, 0],
|
||||
displayPosition: [0, 0],
|
||||
fov: [0, 0],
|
||||
resizeTimeoutId: setTimeout(this.doneResizing, 500)
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
streamEnabled: function() {
|
||||
return (
|
||||
this.$store.getters.ready &&
|
||||
!this.$store.state.globalSettings.disableStream
|
||||
);
|
||||
},
|
||||
thisStreamOpen: function() {
|
||||
// Only a single MJPEG connection should be open at a time
|
||||
return !(this.displaySize[0] == 0) && !(this.displaySize[1] == 0);
|
||||
},
|
||||
streamImgUri: function() {
|
||||
return `${this.$store.getters.baseUri}/api/v2/streams/mjpeg`;
|
||||
},
|
||||
startPreviewUri: function() {
|
||||
return `${this.$store.getters.baseUri}/api/v2/actions/camera/preview/start`;
|
||||
},
|
||||
stopPreviewUri: function() {
|
||||
return `${this.$store.getters.baseUri}/api/v2/actions/camera/preview/stop`;
|
||||
},
|
||||
settingsUri: function() {
|
||||
return `${this.$store.getters.baseUri}/api/v2/instrument/settings`;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
console.log(`${this._uid} mounted`);
|
||||
// A global signal listener to change the GPU preview state
|
||||
this.$root.$on("globalTogglePreview", state => {
|
||||
this.previewRequest(state);
|
||||
});
|
||||
this.$root.$on("globalSafeTogglePreview", state => {
|
||||
this.safePreviewRequest(state);
|
||||
});
|
||||
// A global signal listener to flash the stream element
|
||||
this.$root.$on("globalFlashStream", () => {
|
||||
this.flashStream();
|
||||
});
|
||||
|
||||
// Mutation observer
|
||||
this.sizeObserver = new ResizeObserver(() => {
|
||||
this.handleResize(); // For any element attached to the observer, run handleResize() on change
|
||||
});
|
||||
// Fetch streamDisplay component by ref
|
||||
const streamDisplayElement = this.$refs.streamDisplay.parentNode;
|
||||
// Attach streamDisplay to the size observer
|
||||
this.sizeObserver.observe(streamDisplayElement);
|
||||
},
|
||||
|
||||
created: function() {
|
||||
console.log(`${this._uid} created`);
|
||||
// Send a request to start/stop GPU preview based on global setting
|
||||
this.safePreviewRequest(this.$store.state.globalSettings.autoGpuPreview);
|
||||
// Get FOV from settings
|
||||
this.updateFov();
|
||||
},
|
||||
|
||||
beforeDestroy: function() {
|
||||
console.log(`${this._uid} being destroyed`);
|
||||
// Remove global signal listener to change the GPU preview state
|
||||
this.$root.$off("globalTogglePreview");
|
||||
// Remove global signal listener to flash the stream element
|
||||
this.$root.$off("globalFlashStream");
|
||||
// Disconnect the size observer
|
||||
this.sizeObserver.disconnect();
|
||||
// Remove from the array of active streams
|
||||
this.$store.commit("removeStream", this._uid);
|
||||
},
|
||||
|
||||
methods: {
|
||||
visibilityChanged(isVisible) {
|
||||
this.isVisible = isVisible;
|
||||
},
|
||||
flashStream: function() {
|
||||
// Run an animation that flashes the stream (for capture feedback)
|
||||
let element = this.$refs.streamDisplay;
|
||||
element.classList.remove("uk-animation-fade");
|
||||
element.offsetHeight; /* trigger reflow */
|
||||
element.classList.add("uk-animation-fade");
|
||||
setTimeout(function() {
|
||||
element.classList.remove("uk-animation-fade");
|
||||
}, 800);
|
||||
},
|
||||
|
||||
clickMonitor: function(event) {
|
||||
// Calculate steps from event coordinates and store config FOV
|
||||
let xCoordinate = event.offsetX;
|
||||
let yCoordinate = event.offsetY;
|
||||
|
||||
// Simply scaling by naturalHeight/offsetHeight may give the wrong answer!
|
||||
// because we use content-fit: contain in the stylesheet, the img element
|
||||
// may be larger than the picture. So, we must determine whether the
|
||||
// width or height is setting the scaling factor, and use a uniform scale
|
||||
// factor.
|
||||
let scale = Math.max(
|
||||
event.target.naturalWidth / event.target.offsetWidth,
|
||||
event.target.naturalHeight / event.target.offsetHeight
|
||||
);
|
||||
|
||||
let xRelative = (0.5 * event.target.offsetWidth - xCoordinate) * scale;
|
||||
let yRelative = (0.5 * event.target.offsetHeight - yCoordinate) * scale;
|
||||
|
||||
// Emit a signal to move, acted on by panelNavigate.vue
|
||||
this.$root.$emit(
|
||||
"globalMoveInImageCoordinatesEvent",
|
||||
-xRelative,
|
||||
-yRelative
|
||||
);
|
||||
},
|
||||
|
||||
handleResize: function() {
|
||||
// Only fires resize event after no resize in 500ms (prevents resize event spam)
|
||||
clearTimeout(this.resizeTimeoutId);
|
||||
this.resizeTimeoutId = setTimeout(this.handleDoneResize, 250);
|
||||
},
|
||||
|
||||
handleDoneResize: function() {
|
||||
// Recalculate size
|
||||
console.log(`Recalculating frame size for ${this._uid}`);
|
||||
this.recalculateSize();
|
||||
// Handle closed stream
|
||||
if (this.displaySize[0] == 0 && this.displaySize[1] == 0) {
|
||||
console.log(`${this._uid} is zero`);
|
||||
// If this stream was previously active
|
||||
if (this.$store.state.activeStreams[this._uid] == true) {
|
||||
console.log(`${this._uid} was active`);
|
||||
console.log(`STREAM ${this._uid} CLOSED`);
|
||||
this.$store.commit("removeStream", this._uid);
|
||||
// If all streams are closed, request the GPU preview close
|
||||
var a = Object.values(this.$store.state.activeStreams);
|
||||
let allClosed = a.every(v => v === false);
|
||||
if (allClosed) {
|
||||
this.safePreviewRequest(false);
|
||||
}
|
||||
}
|
||||
// If resized to anything other than zero
|
||||
} else {
|
||||
console.log(`STREAM ${this._uid} OPEN`);
|
||||
this.$store.commit("addStream", this._uid);
|
||||
if (this.$store.state.globalSettings.autoGpuPreview == true) {
|
||||
// Start the preview immediately
|
||||
this.safePreviewRequest(true);
|
||||
// Send another start preview request after a timeout
|
||||
/*
|
||||
The internal logic of this component means that a stop GPU preview
|
||||
request will only ever be sent if ALL stream components are invisible.
|
||||
However, when switching tabs, sometimes the previous component will
|
||||
react to becoming invisible before the new tab has become visible.
|
||||
This results in a stop-preview request being sent JUST before the new
|
||||
start preview request is sent. In an ideal network, this is fine, however
|
||||
due to network jitter, these requests will occasionally be recieved out
|
||||
of order, causing the preview to start in the new location, and then
|
||||
quickly stop again.
|
||||
Preventing this properly will involve some clever server-side logic
|
||||
probably, however as a pit-stop solution, we always send another
|
||||
start-preview request after 1 second. This means that either:
|
||||
1. The initial requests happened in order, in which case this follow-up
|
||||
request does nothing, or
|
||||
2. The requests leapfrog eachother, stopping the preview, in which case
|
||||
the follow-up request restarts the preview in the correct location.
|
||||
*/
|
||||
setTimeout(() => this.safePreviewRequest(true), 250);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
recalculateSize: function() {
|
||||
// Calculate stream size
|
||||
let element = this.$refs.streamDisplay.parentNode;
|
||||
let bound = element.getBoundingClientRect();
|
||||
|
||||
let elementSize = [bound.width, bound.height];
|
||||
|
||||
let elementPositionOnWindow = [bound.left, bound.top];
|
||||
let windowPositionOnDisplay = [window.screenX, window.screenY];
|
||||
let windowChromeHeight = window.outerHeight - window.innerHeight;
|
||||
let elementPositionOnDisplay = [
|
||||
Math.max(0, windowPositionOnDisplay[0] + elementPositionOnWindow[0]),
|
||||
Math.max(
|
||||
0,
|
||||
windowPositionOnDisplay[1] +
|
||||
elementPositionOnWindow[1] +
|
||||
windowChromeHeight
|
||||
)
|
||||
];
|
||||
|
||||
this.displaySize = elementSize;
|
||||
this.displayPosition = elementPositionOnDisplay;
|
||||
},
|
||||
|
||||
safePreviewRequest: function(state) {
|
||||
// previewRequest, but only stopping preview if all streams are invisible
|
||||
// and only starting preview if any stream is visible
|
||||
var a = Object.values(this.$store.state.activeStreams);
|
||||
let allClosed = a.every(v => v === false);
|
||||
// If all streams are closed, don't start GPU preview
|
||||
if (state === true && allClosed) {
|
||||
return false;
|
||||
}
|
||||
// If not all streams are closed, don't stop GPU preview
|
||||
if (state === false && !allClosed) {
|
||||
return false;
|
||||
}
|
||||
return this.previewRequest(state);
|
||||
},
|
||||
|
||||
previewRequest: function(state) {
|
||||
// If requesting starting the stream, but this component is inactive, skip
|
||||
if (this.$store.getters.ready == true) {
|
||||
var requestUri = null;
|
||||
// Create URI
|
||||
if (state == true) {
|
||||
requestUri = this.startPreviewUri;
|
||||
} else {
|
||||
requestUri = this.stopPreviewUri;
|
||||
}
|
||||
|
||||
// Generate payload if tracking window position
|
||||
var payload = {};
|
||||
if (
|
||||
this.$store.state.globalSettings.trackWindow == true &&
|
||||
state == true
|
||||
) {
|
||||
// Recalculate frame dimensions and position
|
||||
this.recalculateSize();
|
||||
// Copy data into payload array
|
||||
payload = {
|
||||
window: [
|
||||
this.displayPosition[0],
|
||||
this.displayPosition[1],
|
||||
this.displaySize[0],
|
||||
this.displaySize[1]
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// Send preview request
|
||||
console.log(`${this._uid} toggled preview to ${state}`);
|
||||
axios
|
||||
.post(requestUri, payload)
|
||||
.then(() => {})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateFov: function() {
|
||||
console.log("Updating FOV");
|
||||
// Get the current field-of-view setting from the server
|
||||
axios
|
||||
.get(`${this.settingsUri}/fov`)
|
||||
.then(response => {
|
||||
if (response.data) {
|
||||
this.fov = response.data;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.modalError(error); // Let mixin handle error
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.stream-display img {
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.stream-display {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.position-relative {
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue