mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-03 03:01:40 +00:00
Convert to user selected camera matching (#1556)
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, ref, onBeforeUnmount } from "vue";
|
||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import loadingImage from "@/assets/images/loading.svg";
|
||||
import type { StyleValue } from "vue/types/jsx";
|
||||
import PvIcon from "@/components/common/pv-icon.vue";
|
||||
import type { UiCameraConfiguration } from "@/types/SettingTypes";
|
||||
|
||||
const props = defineProps<{
|
||||
streamType: "Raw" | "Processed";
|
||||
id: string;
|
||||
cameraSettings: UiCameraConfiguration;
|
||||
}>();
|
||||
|
||||
const emptyStreamSrc = "//:0";
|
||||
const streamSrc = computed<string>(() => {
|
||||
const port =
|
||||
useCameraSettingsStore().currentCameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
|
||||
const port = props.cameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
|
||||
|
||||
if (!useStateStore().backendConnected || port === 0) {
|
||||
return emptyStreamSrc;
|
||||
@@ -32,8 +32,12 @@ const streamStyle = computed<StyleValue>(() => {
|
||||
});
|
||||
|
||||
const containerStyle = computed<StyleValue>(() => {
|
||||
const resolution = useCameraSettingsStore().currentVideoFormat.resolution;
|
||||
const rotation = useCameraSettingsStore().currentPipelineSettings.inputImageRotationMode;
|
||||
if (props.cameraSettings.validVideoFormats.length === 0) {
|
||||
return { aspectRatio: "1/1" };
|
||||
}
|
||||
const resolution =
|
||||
props.cameraSettings.validVideoFormats[props.cameraSettings.pipelineSettings.cameraVideoModeIndex].resolution;
|
||||
const rotation = props.cameraSettings.pipelineSettings.inputImageRotationMode;
|
||||
if (rotation === 1 || rotation === 3) {
|
||||
return {
|
||||
aspectRatio: `${resolution.height}/${resolution.width}`
|
||||
@@ -54,9 +58,9 @@ const overlayStyle = computed<StyleValue>(() => {
|
||||
|
||||
const handleCaptureClick = () => {
|
||||
if (props.streamType === "Raw") {
|
||||
useCameraSettingsStore().saveInputSnapshot();
|
||||
props.cameraSettings.pipelineSettings[props.cameraSettings.currentPipelineIndex].saveInputSnapshot();
|
||||
} else {
|
||||
useCameraSettingsStore().saveOutputSnapshot();
|
||||
props.cameraSettings.pipelineSettings[props.cameraSettings.currentPipelineIndex].saveOutputSnapshot();
|
||||
}
|
||||
};
|
||||
const handlePopoutClick = () => {
|
||||
@@ -69,6 +73,16 @@ const handleFullscreenRequest = () => {
|
||||
};
|
||||
|
||||
const mjpgStream: any = ref(null);
|
||||
|
||||
const handleStreamError = () => {
|
||||
if (streamSrc.value && streamSrc.value !== emptyStreamSrc) {
|
||||
console.error("Error loading stream:", streamSrc.value, " Trying again.");
|
||||
setTimeout(() => {
|
||||
mjpgStream.value.src = streamSrc.value;
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (!mjpgStream.value) return;
|
||||
mjpgStream.value["src"] = emptyStreamSrc;
|
||||
@@ -79,7 +93,6 @@ onBeforeUnmount(() => {
|
||||
<div class="stream-container" :style="containerStyle">
|
||||
<img :src="loadingImage" class="stream-loading" />
|
||||
<img
|
||||
v-show="streamSrc !== emptyStreamSrc"
|
||||
:id="id"
|
||||
ref="mjpgStream"
|
||||
class="stream-video"
|
||||
@@ -87,6 +100,7 @@ onBeforeUnmount(() => {
|
||||
:src="streamSrc"
|
||||
:alt="streamDesc"
|
||||
:style="streamStyle"
|
||||
@error="handleStreamError"
|
||||
/>
|
||||
<div class="stream-overlay" :style="overlayStyle">
|
||||
<pv-icon
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import { computed, getCurrentInstance } from "vue";
|
||||
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
|
||||
import { PlaceholderCameraSettings } from "@/types/SettingTypes";
|
||||
import { useRoute } from "vue2-helpers/vue-router";
|
||||
|
||||
const compact = computed<boolean>({
|
||||
get: () => {
|
||||
@@ -14,6 +17,12 @@ const compact = computed<boolean>({
|
||||
|
||||
// Vuetify2 doesn't yet support the useDisplay API so this is required to access the prop when using the Composition API
|
||||
const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndUp || false);
|
||||
|
||||
const needsCamerasConfigured = computed<boolean>(() => {
|
||||
return (
|
||||
useCameraSettingsStore().cameras.length === 0 || useCameraSettingsStore().cameras[0] === PlaceholderCameraSettings
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -35,14 +44,6 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
<v-list-item-title>Dashboard</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item ref="camerasTabOpener" link to="/cameras">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-camera</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Cameras</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item link to="/settings">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
@@ -51,6 +52,26 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
<v-list-item-title>Settings</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item ref="camerasTabOpener" link to="/cameras">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-camera</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Camera</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
link
|
||||
to="/cameraConfigs"
|
||||
:class="{ cameraicon: needsCamerasConfigured && useRoute().path !== '/cameraConfigs' }"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon :class="{ 'red--text': needsCamerasConfigured }">mdi-swap-horizontal-bold</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title :class="{ 'red--text': needsCamerasConfigured }">Camera Matching</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item link to="/docs">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-bookshelf</v-icon>
|
||||
@@ -119,4 +140,18 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
height: 70px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.cameraicon {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user