cleanup multi webcam

Signed-off-by: Stefan Dej <meteyou@gmail.com>
This commit is contained in:
Stefan Dej 2021-03-28 22:58:27 +02:00
parent 85de19ffeb
commit cca33770fc
10 changed files with 470 additions and 519 deletions

16
package-lock.json generated
View File

@ -32,7 +32,7 @@
"vuex": "^3.6.2"
},
"devDependencies": {
"@mdi/font": "^5.8.55",
"@mdi/font": "^5.9.55",
"@vue/cli-plugin-babel": "^4.5.10",
"@vue/cli-plugin-eslint": "^4.5.3",
"@vue/cli-plugin-pwa": "^4.5.10",
@ -1537,9 +1537,9 @@
"integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="
},
"node_modules/@mdi/font": {
"version": "5.8.55",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-5.8.55.tgz",
"integrity": "sha512-8mrwfFBsmj+D67ZiGQSe5TU/lcWCtDyli2eshQ2fvLCZGRPqFMM23YQp4+JMOTpk5yMZKTeAwNWIYfITy76OHA==",
"version": "5.9.55",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-5.9.55.tgz",
"integrity": "sha512-jswRF6q3eq8NWpWiqct6q+6Fg/I7nUhrxYJfiEM8JJpap0wVJLQdbKtyS65GdlK7S7Ytnx3TTi/bmw+tBhkGmg==",
"dev": true,
"license": "Apache-2.0"
},
@ -19950,9 +19950,9 @@
"integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="
},
"@mdi/font": {
"version": "5.8.55",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-5.8.55.tgz",
"integrity": "sha512-8mrwfFBsmj+D67ZiGQSe5TU/lcWCtDyli2eshQ2fvLCZGRPqFMM23YQp4+JMOTpk5yMZKTeAwNWIYfITy76OHA==",
"version": "5.9.55",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-5.9.55.tgz",
"integrity": "sha512-jswRF6q3eq8NWpWiqct6q+6Fg/I7nUhrxYJfiEM8JJpap0wVJLQdbKtyS65GdlK7S7Ytnx3TTi/bmw+tBhkGmg==",
"dev": true
},
"@mrmlnc/readdir-enhanced": {
@ -36489,4 +36489,4 @@
}
}
}
}
}

View File

@ -33,7 +33,7 @@
"vuex": "^3.6.2"
},
"devDependencies": {
"@mdi/font": "^5.8.55",
"@mdi/font": "^5.9.55",
"@vue/cli-plugin-babel": "^4.5.10",
"@vue/cli-plugin-eslint": "^4.5.3",
"@vue/cli-plugin-pwa": "^4.5.10",

View File

@ -21,7 +21,7 @@
<v-select
v-model="currentLanguage"
:items="availableLanguages"
:label="$t('Settings.LanguagePanel.Language')"
:label="$t('Settings.GeneralPanel.Language')"
hide-details
></v-select>
</v-col>

View File

@ -1,338 +1,319 @@
<template>
<div>
<v-card>
<v-toolbar flat dense>
<v-toolbar-title>
<span class="subheading"
><v-icon left>mdi-webcam</v-icon>{{ $t('Settings.WebcamPanel.Webcam') }}</span
>
</v-toolbar-title>
</v-toolbar>
<v-card-text class="py-3">
<v-container>
<v-row>
<v-col class="py-2 pb-5">
<v-switch
v-model="boolNavi"
hide-details
label="Show in navigation"
class="mt-0"
></v-switch>
</v-col>
</v-row>
<v-row
v-for="(webcam, index) in this.webcams"
v-bind:key="index"
>
<v-col
class="rounded transition-swing secondary py-2 px-2 mb-3"
style="cursor: pointer;"
>
<v-row align="center">
<v-col class="px-4 mr-2 col-1">
<v-icon left>{{webcam.icon}}</v-icon>
</v-col>
<v-col class="col-7">
<strong>{{ webcam.name }}</strong>
</v-col>
<v-col class="col-auto text-right" style="position:absolute;right:12px"
><v-btn
small
class="minwidth-0 float-right"
v-on:click.stop.prevent="editWebcam(webcam)"
><v-icon small>mdi-pencil</v-icon></v-btn
></v-col
>
</v-row>
</v-col>
</v-row>
<v-row>
<v-col class="text-center mt-0">
<v-btn @click="createWebcam">add webcam</v-btn>
</v-col>
</v-row>
</v-container>
</v-card-text>
</v-card>
<v-dialog v-model="dialog.bool" persistent :width="600">
<v-card dark>
<v-toolbar flat dense color="primary">
<v-toolbar-title>
<span class="subheading">
<v-icon class="mdi mdi-webcam" left></v-icon>
{{ dialog.index === null ? "Create" : "Edit" }} Webcam
</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn small class="minwidth-0" @click="dialog.bool = false"
><v-icon small>mdi-close-thick</v-icon></v-btn
>
</v-toolbar>
<v-card-text class="pt-3">
<v-container class="pb-0">
<v-form v-model="dialog.valid" @submit.prevent="saveWebcam">
<template v-if="dialog.bool">
<v-row>
<v-col class="col-12 col-sm-6">
<div>
<v-card>
<v-toolbar flat dense>
<v-toolbar-title>
<span class="subheading">
<v-icon left>mdi-webcam</v-icon>{{ $t('Settings.WebcamPanel.Webcams') }}
</span>
</v-toolbar-title>
</v-toolbar>
<v-card-text class="py-3">
<v-container>
<v-row>
<v-col class="col-2">
<v-item-group>
<v-menu :offset-y="true" title="Icon">
<template v-slot:activator="{ on, attrs }">
<v-btn class="px-2 minwidth-0" color="transparent" v-bind="attrs" v-on="on" elevation="0"><v-icon>{{getCurrentIcon()}}</v-icon></v-btn>
</template>
<v-list dense class="py-0">
<v-list-item v-for="icon of this.iconItems" v-bind:key="icon.value" link @click="setCurrentIcon(icon.value)">
<v-list-item-icon class="mr-0">
<v-icon small>{{icon.value}}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="icon.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</v-item-group>
</v-col>
<v-col class="col-10">
<v-text-field
v-model="dialog.name"
label="Name"
hide-details="auto"
:rules="[rules.required, rules.unique]"
dense
></v-text-field>
</v-col>
<v-col class="py-2 pb-5">
<v-switch
v-model="boolNavi"
hide-details
:label="$t('Settings.WebcamPanel.ShowInNavigation')"
class="mt-0"
></v-switch>
</v-col>
</v-row>
<v-row class="mt-2 mx-0 mb-2" align="center">
<v-text-field
v-model="dialog.config.url"
label="URL"
hide-details="auto"
:rules="[rules.required, rules.urlvalid]"
></v-text-field>
<v-row v-for="(webcam, index) in this.webcams" v-bind:key="index">
<v-col class="rounded transition-swing secondary py-2 px-2 mb-3" style="cursor: pointer;" >
<v-row align="center">
<v-col class="px-4 mr-2 col-1">
<v-icon left>{{ webcam.icon }}</v-icon>
</v-col>
<v-col class="col-7">
<strong>{{ webcam.name }}</strong>
</v-col>
<v-col class="col-auto text-right" style="position:absolute;right:12px">
<v-btn
small
class="minwidth-0 float-right"
v-on:click.stop.prevent="editWebcam(webcam)"
>
<v-icon small>mdi-pencil</v-icon>
</v-btn>
</v-col>
</v-row>
</v-col>
</v-row>
<v-row class="mt-2 mx-0 mb-2" align="center">
<v-select
v-model="dialog.config.service"
:items="serviceItems"
hide-details
label="Service"
class="mt-0"
></v-select>
<v-row>
<v-col class="text-center mt-0">
<v-btn @click="createWebcam">{{ $t("Settings.WebcamPanel.AddWebcam")}}</v-btn>
</v-col>
</v-row>
<v-row
class="mt-2 mx-0 mb-2"
align="center"
v-if="dialog.config.service === 'mjpegstreamer-adaptive'"
>
<v-text-field
v-model="dialog.config.targetFps"
hide-details
label="Target FPS"
class="mt-0"
></v-text-field>
</v-row>
<v-row class="mt-2 mx-0 mb-2" align="center">
<v-checkbox
v-model="dialog.config.flipX"
hide-details
class="shrink mt-0"
label="Flip webcam horizontally"
></v-checkbox>
</v-row>
<v-row class="mt-2 mx-0 mb-2" align="center">
<v-checkbox
v-model="dialog.config.flipY"
hide-details
class="shrink mt-0"
label="Flip webcam vertically"
></v-checkbox>
</v-row>
</v-col>
<v-col class="col-12 col-sm-6">
<v-row class="mt-2 mx-0 mb-2" align="center" style="
height: 100%;
margin-top: auto!important;
margin-bottom: auto!important;">
<img
:src="dialog.config.url"
class="webcamImage"
:style="webcamStyle"
alt="Webcam"
v-if="
['mjpegstreamer', 'mjpegstreamer-adaptive'].includes(
dialog.config.service
)
"
/>
</v-row>
<v-row class="mt-2 mx-0 mb-2" align="center">
</v-row>
</v-col>
</v-row>
<v-row class="mt-3">
<v-col class="text-center">
<v-btn
color="red"
outlined
class="float-left minwidth-0"
@click="deleteWebcam"
v-if="this.dialog.index !== null"
>
<v-icon>mdi-delete</v-icon>
</v-container>
</v-card-text>
</v-card>
<v-dialog v-model="dialog.bool" persistent :width="600">
<v-card dark>
<v-toolbar flat dense color="primary">
<v-toolbar-title>
<span class="subheading">
<v-icon class="mdi mdi-webcam" left></v-icon> {{ dialog.index === null ? $t("Settings.WebcamPanel.CreateWebcam") : $t("Settings.WebcamPanel.EditWebcam") }}
</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn small class="minwidth-0" @click="dialog.bool = false">
<v-icon small>mdi-close-thick</v-icon>
</v-btn>
<v-btn
color="white"
outlined
:class="dialog.index !== null ? 'float-right' : ''"
type="submit"
>
{{ dialog.index === null ? "save" : "update" }} webcam
</v-btn>
</v-col>
</v-row>
</template>
</v-form>
</v-container>
</v-card-text>
</v-card>
</v-dialog>
</div>
</v-toolbar>
<v-card-text class="pt-3">
<v-container class="pb-0">
<v-form v-model="dialog.valid" @submit.prevent="saveWebcam">
<template v-if="dialog.bool">
<v-row>
<v-col class="col-12 col-sm-6">
<v-row>
<v-col class="col-2">
<v-item-group>
<v-menu :offset-y="true" title="Icon">
<template v-slot:activator="{ on, attrs }">
<v-btn class="px-2 minwidth-0" color="transparent" v-bind="attrs" v-on="on" elevation="0">
<v-icon>{{ getCurrentIcon() }}</v-icon>
</v-btn>
</template>
<v-list dense class="py-0">
<v-list-item v-for="icon of this.iconItems" v-bind:key="icon.value" link @click="setCurrentIcon(icon.value)">
<v-list-item-icon class="mr-0">
<v-icon small>{{ icon.value }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="icon.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</v-item-group>
</v-col>
<v-col class="col-10">
<v-text-field
v-model="dialog.name"
:label="$t('Settings.WebcamPanel.Name')"
hide-details="auto"
:rules="[rules.required, rules.unique]"
dense
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col class="py-1">
<v-text-field
v-model="dialog.url"
:label="$t('Settings.WebcamPanel.WebcamURL')"
hide-details="auto"
:rules="[rules.required]"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col class="py-1">
<v-select
v-model="dialog.service"
:items="serviceItems"
hide-details
:label="$t('Settings.WebcamPanel.Service')"
></v-select>
</v-col>
</v-row>
<v-row v-if="dialog.service === 'mjpegstreamer-adaptive'">
<v-col class="py-1">
<v-text-field
v-model="dialog.targetFps"
hide-details
:label="$t('Settings.WebcamPanel.TargetFPS')"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col class="py-1">
<v-checkbox
v-model="dialog.flipX"
hide-details
class="mt-1"
:label="$t('Settings.WebcamPanel.FlipHorizontally')"
></v-checkbox>
</v-col>
</v-row>
<v-row>
<v-col class="py-1">
<v-checkbox
v-model="dialog.flipY"
hide-details
class="mt-1"
:label="$t('Settings.WebcamPanel.FlipVertically')"
></v-checkbox>
</v-col>
</v-row>
</v-col>
<v-col class="col-12 col-sm-6 text-center" align-self="center">
<vue-load-image v-if="['mjpegstreamer', 'mjpegstreamer-adaptive'].includes(dialog.service)">
<img slot="image" :src="dialog.url" alt="Preview" :style="webcamStyle" class="webcamImage" />
<v-progress-circular slot="preloader" indeterminate color="primary"></v-progress-circular>
<template slot="error">
<v-icon x-large>mdi-webcam-off</v-icon>
<div class="subtitle-1 mt-2">{{ $t('Settings.WebcamPanel.UrlNotAvailable') }}</div>
</template>
</vue-load-image>
</v-col>
</v-row>
<v-row class="mt-3">
<v-col class="text-center">
<v-btn
color="red"
outlined
class="float-left minwidth-0"
@click="deleteWebcam"
v-if="this.dialog.index !== null"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-btn
color="white"
outlined
:class="dialog.index !== null ? 'float-right' : ''"
type="submit"
>
{{ dialog.index === null ? $t("Settings.WebcamPanel.SaveWebcam") : $t("Settings.WebcamPanel.UpdateWebcam") }}
</v-btn>
</v-col>
</v-row>
</template>
</v-form>
</v-container>
</v-card-text>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import VueLoadImage from "vue-load-image"
export default {
components: {},
data: function() {
return {
dialog: {
bool: false,
index: null,
valid: false,
name: "",
icon: "mdi-webcam",
config: {
service: "mjpegstreamer",
targetFps: 25,
url: "/webcam/?action=stream",
flipX: false,
flipY: false,
},
},
rules: {
required: (value) => value !== "" || "required",
unique: (value) =>
!this.existsWebcamName(value) || "Name already exists",
},
iconItems: [
{ value: "mdi-printer-3d", text: "Printer" },
{ value: "mdi-printer-3d-nozzle", text: "Nozzle" },
{ value: "mdi-radiator-disabled", text: "Bed" },
{ value: "mdi-webcam", text: "Cam" },
{ value: "mdi-album", text: "Filament" },
{ value: "mdi-door", text: "Door" },
{ value: "mdi-raspberry-pi", text: "MCU" },
{ value: "mdi-campfire", text: "Hot" },
],
serviceItems: [
{ value: "mjpegstreamer", text: "MJPEG-Streamer" },
{
value: "mjpegstreamer-adaptive",
text: "Adaptive MJPEG-Streamer (experimental)",
},
],
};
},
computed: {
...mapGetters({
webcams: 'gui/getWebcams'
}),
boolNavi: {
get() {
return this.$store.state.gui.webcam.bool;
},
set(showNav) {
return this.$store.dispatch("gui/setSettings", {
webcam: { bool: showNav },
});
},
components: {
'vue-load-image': VueLoadImage
},
webcamStyle() {
let transforms = "";
if (this.dialog.config.flipX) {
transforms += " scaleX(-1)";
}
if (this.dialog.config.flipY) {
transforms += " scaleY(-1)";
}
if (transforms.trimLeft().length) {
data: function () {
return {
transform: transforms.trimLeft(),
dialog: {
bool: false,
index: null,
valid: false,
name: "",
icon: "mdi-webcam",
service: "mjpegstreamer-adaptiv",
targetFps: 15,
url: "/webcam/?action=stream",
flipX: false,
flipY: false,
},
rules: {
required: (value) => value !== "" || this.$t("Settings.WebcamPanel.Required"),
unique: (value) => !this.existsWebcamName(value) || this.$t("Settings.WebcamPanel.NameAlreadyExists"),
},
iconItems: [
{ value: "mdi-printer-3d", text: this.$t("Settings.WebcamPanel.IconPrinter") },
{ value: "mdi-printer-3d-nozzle", text: this.$t("Settings.WebcamPanel.IconNozzle") },
{ value: "mdi-radiator-disabled", text: this.$t("Settings.WebcamPanel.IconBed") },
{ value: "mdi-webcam", text: this.$t("Settings.WebcamPanel.IconCam") },
{ value: "mdi-album", text: this.$t("Settings.WebcamPanel.IconFilament") },
{ value: "mdi-door", text: this.$t("Settings.WebcamPanel.IconDoor") },
{ value: "mdi-raspberry-pi", text: this.$t("Settings.WebcamPanel.IconMcu") },
{ value: "mdi-campfire", text: this.$t("Settings.WebcamPanel.IconHot") },
],
serviceItems: [
{ value: "mjpegstreamer", text: this.$t("Settings.WebcamPanel.Mjpegstreamer")},
{ value: "mjpegstreamer-adaptive", text: this.$t("Settings.WebcamPanel.MjpegstreamerAdaptive") },
],
};
}
return "";
},
},
mounted() {
this.clearDialog();
},
methods: {
getDefaultConfig(){
return {
service: "mjpegstreamer",
targetFps: 25,
url: "/webcam/?action=stream",
flipX: false,
flipY: false,
}
},
getCurrentIcon(){
return this.dialog.icon
},
setCurrentIcon(icon){
this.dialog.icon = icon
},
existsWebcamName(name) {
return (
this.webcams.findIndex(
(webcam) => webcam.name === name && webcam.index !== this.dialog.index
) >= 0
);
},
createWebcam() {
this.clearDialog();
this.dialog.bool = true;
},
clearDialog() {
this.dialog.bool = false;
this.dialog.index = null;
this.dialog.name = "";
this.dialog.icon = "mdi-webcam"
this.dialog.config = this.getDefaultConfig();
},
editWebcam(webcam) {
this.dialog.name = webcam.name;
this.dialog.icon = webcam.icon;
this.dialog.index = webcam.index;
this.dialog.config = webcam.config;
computed: {
webcams: {
get() {
return this.$store.getters["gui/getWebcams"]
},
},
boolNavi: {
get() {
return this.$store.state.gui.webcam.bool
},
set(showNav) {
return this.$store.dispatch("gui/setSettings", {
webcam: {bool: showNav},
})
},
},
webcamStyle() {
let transforms = "";
if (this.dialog.flipX) transforms += " scaleX(-1)"
if (this.dialog.flipY) transforms += " scaleY(-1)"
if (transforms.trimLeft().length) {
return { transform: transforms.trimLeft() }
}
this.dialog.bool = true;
return ""
},
},
saveWebcam() {
if (this.dialog.valid) {
if (this.dialog.index) {
this.$store.dispatch("gui/updateWebcam", this.dialog);
} else this.$store.dispatch("gui/addWebcam", this.dialog);
this.clearDialog();
}
mounted() {
this.clearDialog()
},
deleteWebcam() {
this.$store.dispatch("gui/deleteWebcam", this.dialog);
this.clearDialog();
methods: {
getCurrentIcon() {
return this.dialog.icon
},
setCurrentIcon(icon) {
this.dialog.icon = icon
},
existsWebcamName(name) {
return (this.webcams.findIndex(webcam => webcam.name === name && webcam.index !== this.dialog.index) !== -1)
},
createWebcam() {
this.clearDialog()
this.dialog.bool = true
},
clearDialog() {
this.dialog.bool = false
this.dialog.index = null
this.dialog.name = ""
this.dialog.icon = "mdi-webcam"
this.dialog.service = "mjpegstreamer-adaptive"
this.dialog.targetFps = 15
this.dialog.url = "/webcam/?action=stream"
this.dialog.flipX = false
this.dialog.flipY = false
},
editWebcam(webcam) {
this.dialog.name = webcam.name
this.dialog.icon = webcam.icon
this.dialog.index = webcam.index
this.dialog.service = webcam.service
this.dialog.targetFps = webcam.targetFps
this.dialog.url = webcam.url
this.dialog.flipX = webcam.flipX
this.dialog.flipY = webcam.flipY
this.dialog.bool = true
},
saveWebcam() {
if (this.dialog.valid) {
if (this.dialog.index) this.$store.dispatch("gui/updateWebcam", this.dialog)
else this.$store.dispatch("gui/addWebcam", this.dialog)
this.clearDialog()
}
},
deleteWebcam() {
this.$store.dispatch("gui/deleteWebcam", this.dialog)
this.clearDialog()
},
},
},
};
}
</script>

View File

@ -10,19 +10,19 @@
<v-toolbar-title>
<span class="subheading">
<v-icon left>mdi-webcam</v-icon> {{ $t('Panels.WebcamPanel.Webcam')}}
<small v-if="this.webcamConfig.service === 'mjpegstreamer-adaptive' && this.time">( {{ $t('Panels.WebcamPanel.FPS')}}: {{ currentFPS }})</small>
<small v-if="'service' in this.currentCam && this.currentCam.service === 'mjpegstreamer-adaptive' && this.time">( {{ $t('Panels.WebcamPanel.FPS')}}: {{ currentFPS }})</small>
</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-item-group v-if="this.webcams.length>=2">
<v-item-group v-if="this.webcams.length > 1">
<v-menu :offset-y="true" title="Webcam">
<template v-slot:activator="{ on, attrs }">
<v-btn small class="px-2 minwidth-0" color="primary" v-bind="attrs" v-on="on">{{currentCam().name}} <v-icon small>mdi-menu-down</v-icon></v-btn>
<v-btn small class="px-2 minwidth-0" color="primary" v-bind="attrs" v-on="on">{{ 'name' in currentCam ? currentCam.name : "unknown" }} <v-icon small>mdi-menu-down</v-icon></v-btn>
</template>
<v-list dense class="py-0">
<v-list-item v-for="webcam of this.webcams" v-bind:key="webcam.index" link @click="selectCam(webcam.name)" :disabled="checkSelectedCam(webcam.name)">
<v-list-item v-for="webcam of this.webcams" v-bind:key="webcam.index" link @click="currentCamName = webcam.name">
<v-list-item-icon class="mr-0">
<v-icon small>{{webcam.icon}}</v-icon>
<v-icon small>{{ webcam.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="webcam.name"></v-list-item-title>
@ -33,25 +33,30 @@
</v-item-group>
</v-toolbar>
<v-card-text class="px-0 py-0 content">
<img :src="url" class="webcamImage" :style="webcamStyle" @load="onLoad" alt="Webcam" v-if="streamValid()" @error="streamNotFound()"/>
<div style="width: 100%;padding-bottom: 56.75%!important;" v-if="!streamValid()">
<div style="position:absolute;text-align:center;width:100%;top: 53%;transform: translateY(-50%);" v-if="this.webcams.length === 0">
<span><v-icon size="100">mdi-camera-off</v-icon></span><br><br>
<span>Please configure a Webcam in the Settings!</span>
</div>
<div style="position:absolute;text-align:center;width:100%;top: 53%;transform: translateY(-50%);" v-if="this.webcams.length !== 0">
<span><v-icon size="100">mdi-camera-off</v-icon></span><br><br>
<span>Please check if the Stream URL is valid!</span>
</div>
</div>
<template v-if="'service' in this.currentCam && this.currentCam.service === 'mjpegstreamer'">
<vue-load-image>
<img slot="image" :src="url" alt="Preview" :style="webcamStyle" class="webcamImage" @load="onLoad" />
<v-progress-circular slot="preloader" indeterminate color="primary"></v-progress-circular>
<div slot="error" class="text-center py-5">
<v-icon x-large>mdi-webcam-off</v-icon>
<div class="subtitle-1 mt-2">{{ $t('Settings.WebcamPanel.UrlNotAvailable') }}</div>
</div>
</vue-load-image>
</template>
<template v-else>
<img slot="image" :src="url" alt="Preview" :style="webcamStyle" class="webcamImage" @load="onLoad" />
</template>
</v-card-text>
</v-card>
</template>
<script>
import { mapGetters,mapState } from 'vuex'
import VueLoadImage from "vue-load-image";
export default {
components: {
'vue-load-image': VueLoadImage
},
data: function() {
return {
validURL: true,
@ -70,120 +75,73 @@
};
},
created: function () {
document.addEventListener("focus", () => this.handleRefreshChange(), false);
document.addEventListener("visibilitychange", this.handleRefreshChange, false);
if(this.webcams.length>=1){
let currentWebcamConfig = this.currentCam().config
if(currentWebcamConfig.service === 'mjpegstreamer-adaptive') {
this.requestMjpeg()
}
}
},
components: {
document.addEventListener("focus", () => this.handleRefreshChange(), false)
document.addEventListener("visibilitychange", this.handleRefreshChange, false)
},
computed: {
...mapState({
'webcamConfig': state => state.gui.webcam,
}),
...mapGetters({
webcams: 'gui/getWebcams'
}),
currentCamName: {
get() {
let currentCamName = this.$store.state.gui.webcam.selectedCam
if (currentCamName !== undefined && this.webcams.findIndex(webcam => webcam.name === currentCamName) !== -1)
return currentCamName
if (this.webcams.length) return this.webcams[0].name
return ""
},
set(newVal) {
return this.$store.dispatch('gui/setSettings', { webcam: { selectedCam: newVal } })
}
},
currentCam() {
const currentCam = this.webcams.find(webcam => webcam.name === this.currentCamName)
if (currentCam) return currentCam
return {}
},
webcams: {
get() {
return this.$store.state.gui.webcam.configs
}
},
url() {
if(this.webcams.length>=1){
let currentWebcamConfig = this.currentCam().config
if (currentWebcamConfig.service === 'mjpegstreamer' && currentWebcamConfig.url.indexOf("?") !== -1) {
let basicUrl = currentWebcamConfig.url
if('url' in this.currentCam) {
if ('service' in this.currentCam && this.currentCam.service === 'mjpegstreamer' && this.currentCam.url.indexOf("?") !== -1) {
let basicUrl = this.currentCam.url
const params = new URLSearchParams(basicUrl)
params.set('bypassCache', ""+this.refresh)
return decodeURIComponent(params.toString())
} else if (currentWebcamConfig.service === 'mjpegstreamer-adaptive') {
} else if ('service' in this.currentCam && this.currentCam.service === 'mjpegstreamer-adaptive') {
return this.imageData
} else {
return currentWebcamConfig.url
}
return this.currentCam.url
}
return ""
},
webcamStyle() {
let transforms = "";
if (this.currentCam().config.flipX) {
transforms += " scaleX(-1)";
}
if (this.currentCam().config.flipY) {
transforms += " scaleY(-1)";
}
if (transforms.trimLeft().length) {
return {
transform: transforms.trimLeft(),
};
}
return "";
let transforms = ""
if ('flipX' in this.currentCam && this.currentCam.flipX) transforms += " scaleX(-1)"
if ('flipX' in this.currentCam && this.currentCam.flipY) transforms += " scaleY(-1)"
if (transforms.trimLeft().length) return { transform: transforms.trimLeft() }
return ""
},
},
methods: {
currentCam(){
if(this.webcams.length === 0){
return ""
}
let currentcam
for(let camindex = 0; camindex < this.webcams.length; camindex ++){
if(this.webcams[camindex].name === this.webcamConfig.selectedCam){
currentcam = this.webcams[camindex]
break
}
}
if(typeof(currentcam)==="undefined"){
this.selectCam(this.webcams[0].name)
return this.webcams[0]
}
return currentcam
},
streamNotFound(){
this.validURL=false;
},
streamValid(){
if(this.webcams.length === 0){
return false
}
if(['mjpegstreamer', 'mjpegstreamer-adaptive'].includes(this.currentCam().config.service)){
if(!this.validURL){
return false
}
return true
}
return false
},
streamAdaptiveMode(){
if(!this.streamValid()){
return false
}
if(this.currentCam().config.service === 'mjpegstreamer-adaptive' && this.time){
return true
}
return false
},
handleRefreshChange() {
if (!document.hidden) {
this.refresh = Math.ceil(Math.random() * Math.pow(10, 12))
}
},
selectCam(name){
this.validURL = true
this.$store.dispatch('gui/setSettings', { webcam: { selectedCam: name } })
},
checkSelectedCam(name){
return this.webcamConfig.selectedCam === name
},
onLoad() {
const end_time = performance.now()
const current_time = end_time - this.start_time
this.time = (this.time * this.time_smoothing) + (current_time * (1.0 - this.time_smoothing))
this.start_time = end_time
const target_time = 1000/this.currentCam().config.targetFps
const target_time = 1000 / this.currentCam.targetFps
const current_request_time = performance.now() - this.request_start_time
this.request_time = (this.request_time * this.request_time_smoothing) + (current_request_time * (1.0 - this.request_time_smoothing))
@ -194,18 +152,17 @@
})
},
requestMjpeg() {
if(!this.isVisible) return
if(!this.streamValid()) return
if (!this.isVisible) return
if (!('url' in this.currentCam)) return
this.request_start_time = performance.now()
let basicUrl = this.currentCam().config.url
let basicUrl = this.currentCam.url
basicUrl = basicUrl.replace("action=stream", "action=snapshot")
if (basicUrl && basicUrl.indexOf("?") === -1) basicUrl += "?"
const params = new URLSearchParams(basicUrl)
params.set('bypassCache', ""+ this.refresh + (Math.random() * 1000))
this.currentFPS = Math.round(1000 / this.time)
this.$nextTick(() => {

View File

@ -336,6 +336,7 @@ export default {
GeneralPanel: {
General: "General",
PrinterName: "Printer Name",
Language: "Language",
DisplayCANCEL_PRINT: "Display CANCEL_PRINT",
DisplayZOffset: "Display Z-Offset-Panel",
ShowCANCEL_PRINT: "Shows the CANCEL_PRINT button permanently - no second layer confirmation needed.",
@ -344,9 +345,6 @@ export default {
FactoryInfo: "Do you really want to reset mainsail to factory settings?",
ResetMainsail: "reset mainsail"
},
LanguagePanel: {
Language: "Language"
},
LimitsPanel: {
MachineLimits: "Machine Limits",
Velocity: "Velocity",
@ -398,13 +396,36 @@ export default {
ERROR: "ERROR"
},
WebcamPanel: {
Webcam: "Webcam",
WebcamURL: "Webcam URL",
Rotate: "Rotate",
FlipWebcamHorizontally: "Flip webcam horizontally",
FlipWebcamVertically: "Flip webcam vertically",
Webcams: "Webcams",
ShowInNavigation: "Show in navigation",
Service: "Service"
AddWebcam: "add webcam",
CreateWebcam: "Create Webcam",
EditWebcam: "Edit Webcam",
SaveWebcam: "Save Webcam",
UpdateWebcam: "Update Webcam",
IconPrinter: "Printer",
IconNozzle: "Nozzle",
IconBed: "Bed",
IconCam: "Cam",
IconFilament: "Filament",
IconDoor: "Door",
IconMcu: "MCU",
IconHot: "Hot",
Mjpegstreamer: "MJPEG-Streamer",
MjpegstreamerAdaptive: "Adaptive MJPEG-Streamer (experimental)",
Required: "required",
NameAlreadyExists: "Name already exists",
UrlNotAvailable: "URL not available",
Name: "Name",
WebcamURL: "Webcam URL",
Service: "Service",
TargetFPS: "Target FPS",
FlipHorizontally: "Flip webcam horizontally",
FlipVertically: "Flip webcam vertically",
}
}
}

View File

@ -274,6 +274,7 @@ export default {
GeneralPanel: {
General: "一般",
PrinterName: "打印机名称",
Language: "语言",
DisplayCANCEL_PRINT: "显示 CANCEL_PRINT",
DisplayZOffset: "显示Z偏移面板",
ShowCANCEL_PRINT: "永久显示CANCEL_PRINT按钮-无需第二层确认。",
@ -282,9 +283,6 @@ export default {
FactoryInfo: "您是否真的要将主帆重置为出厂设置?",
ResetMainsail: "重置主帆"
},
LanguagePanel: {
Language: "语言"
},
LimitsPanel: {
MachineLimits: "机器限制",
Velocity: "速度",

View File

@ -7,14 +7,24 @@ export default {
},
getData({ commit, dispatch, rootState }, payload) {
let oldwebcamconfig = payload.value.webcam
// upgrade webcam config to multi webcam support
if ('webcam' in payload.value &&
(
'service' in payload.value.webcam ||
'targetFps' in payload.value.webcam ||
'url' in payload.value.webcam ||
'flipX' in payload.value.webcam ||
'flipY' in payload.value.webcam
)
) {
window.console.log("convert to multi webcam")
dispatch('upgradeWebcamConfig', payload.value.webcam)
delete payload.value.webcam
}
commit('setData', payload.value)
if (typeof (oldwebcamconfig) !== "undefined") {
dispatch('convertCamConfig', oldwebcamconfig)
}
if ('tempchart' in payload.value && 'datasetSettings' in payload.value.tempchart) {
commit('setTempchartDatasetSettings', payload.value.tempchart.datasetSettings)
}
@ -26,52 +36,6 @@ export default {
dispatch('printer/init', null, { root: true })
},
convertCamConfig({ dispatch }, payload) {
if (typeof (payload.configs) !== "undefined") {
return
}
let oldcamconfig = {
name: "Default",
icon: "mdi-webcam",
config: {
service: "mjpegstreamer",
targetFps: 25,
url: "/webcam/?action=stream",
flipX: false,
flipY: false,
},
}
let cleanupconfig = {
service: undefined,
targetFps: undefined,
url: undefined,
flipX: undefined,
flipY: undefined,
rotate: undefined,
rotateDegrees: undefined,
selectedCam: 'Default',
bool: payload.bool,
configs: [],
}
if (typeof (payload.url) !== "undefined") {
oldcamconfig.config.url = payload.url
}
if (typeof (payload.service) !== "undefined") {
oldcamconfig.config.service = payload.service
}
oldcamconfig.config.targetFps = payload.targetFps
oldcamconfig.config.flipX = payload.flipX
oldcamconfig.config.flipY = payload.flipY
cleanupconfig.configs.push(oldcamconfig)
dispatch('setSettings', { 'webcam': cleanupconfig })
},
setSettings({ commit }, payload) {
commit('setSettings', payload)
@ -161,6 +125,25 @@ export default {
})
},
upgradeWebcamConfig({ state, dispatch }, payload) {
const webcam = {...state.webcam}
webcam.bool = payload.bool
webcam.configs[0] = {
name: 'Default',
icon: 'mdi-webcam',
service: 'service' in payload ? payload.service : "mjpegstreamer-adaptive",
targetFps: 'targetFps' in payload ? payload.targetFps : 15,
url: 'url' in payload ? payload.url : "/webcam/?action=stream",
flipX: 'flipX' in payload ? payload.flipX : false,
flipY: 'flipY' in payload ? payload.flipY : false,
}
dispatch('updateSettings', {
keyName: 'webcam',
newVal: webcam
})
},
setTempchartDatasetSetting({ commit, dispatch, state }, payload) {
commit("setTempchartDatasetSetting", payload)
dispatch('updateSettings', {

View File

@ -36,21 +36,17 @@ export function getDefaultState() {
}
},
webcam: {
selectedCam: "Default",
selectedCam: "",
bool: false,
configs: [
{
name: 'Default',
icon: 'mdi-webcam',
config: {
service: "mjpegstreamer",
targetFps: 25,
url: "/webcam/?action=stream",
flipX: false,
flipY: false,
}
}
],
configs: [{
name: 'Default',
icon: 'mdi-webcam',
service: "mjpegstreamer-adaptive",
targetFps: 15,
url: "/webcam/?action=stream",
flipX: false,
flipY: false,
}],
},
tempchart: {
intervalChartUpdate: 1000,

View File

@ -67,18 +67,33 @@ export default {
},
addWebcam(state, payload) {
state.webcam.configs.push({
const newWebcam = {
name: payload.name,
icon: payload.icon,
config: payload.config
})
service: payload.service,
targetFps: payload.targetFps,
url: payload.url,
flipX: payload.flipX,
flipY: payload.flipY,
}
state.webcam.configs.push(newWebcam)
},
updateWebcam(state, payload) {
if (state.webcam.configs[payload.index]) {
Vue.set(state.webcam.configs[payload.index], 'name', payload.name)
Vue.set(state.webcam.configs[payload.index], 'icon', payload.icon)
Vue.set(state.webcam.configs[payload.index], 'config', payload.config)
const configs = state.webcam.configs
configs[payload.index] = {
name: payload.name,
icon: payload.icon,
service: payload.service,
targetFps: payload.targetFps,
url: payload.url,
flipX: payload.flipX,
flipY: payload.flipY,
}
Vue.set(state.webcam, 'configs', configs)
}
},