fix: stop webcam when webcam panel is collapse (#839)
This commit is contained in:
parent
bbf2832238
commit
6ffb771eeb
@ -1,6 +1,7 @@
|
|||||||
<style>
|
<style>
|
||||||
.webcamImage {
|
.webcamImage {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.webcamFpsOutput {
|
.webcamFpsOutput {
|
||||||
@ -16,7 +17,12 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div style="position: relative">
|
<div style="position: relative">
|
||||||
<img ref="image" class="webcamImage" :style="webcamStyle" />
|
<img
|
||||||
|
ref="image"
|
||||||
|
v-observe-visibility="visibilityChanged"
|
||||||
|
class="webcamImage"
|
||||||
|
:style="webcamStyle"
|
||||||
|
@load="onload" />
|
||||||
<span v-if="showFps" class="webcamFpsOutput">{{ $t('Panels.WebcamPanel.FPS') }}: {{ fpsOutput }}</span>
|
<span v-if="showFps" class="webcamFpsOutput">{{ $t('Panels.WebcamPanel.FPS') }}: {{ fpsOutput }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -32,6 +38,8 @@ const TYPE_JPEG = 'image/jpeg'
|
|||||||
@Component
|
@Component
|
||||||
export default class Mjpegstreamer extends Mixins(BaseMixin) {
|
export default class Mjpegstreamer extends Mixins(BaseMixin) {
|
||||||
private currentFPS = 0
|
private currentFPS = 0
|
||||||
|
private streamState = false
|
||||||
|
private aspectRatio: null | number = null
|
||||||
private timerFPS: number | null = null
|
private timerFPS: number | null = null
|
||||||
private timerRestart: number | null = null
|
private timerRestart: number | null = null
|
||||||
private stream: ReadableStream | null = null
|
private stream: ReadableStream | null = null
|
||||||
@ -58,12 +66,19 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get webcamStyle() {
|
get webcamStyle() {
|
||||||
|
const output = {
|
||||||
|
transform: 'none',
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
}
|
||||||
|
|
||||||
let transforms = ''
|
let transforms = ''
|
||||||
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-1)'
|
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-1)'
|
||||||
if ('flipX' in this.camSettings && this.camSettings.flipY) transforms += ' scaleY(-1)'
|
if ('flipX' in this.camSettings && this.camSettings.flipY) transforms += ' scaleY(-1)'
|
||||||
if (transforms.trimLeft().length) return { transform: transforms.trimLeft() }
|
if (transforms.trimStart().length) output.transform = transforms.trimStart()
|
||||||
|
|
||||||
return ''
|
if (this.aspectRatio) output.aspectRatio = this.aspectRatio
|
||||||
|
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
get fpsOutput() {
|
get fpsOutput() {
|
||||||
@ -71,6 +86,8 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startStream() {
|
startStream() {
|
||||||
|
this.streamState = true
|
||||||
|
|
||||||
const SOI = new Uint8Array(2)
|
const SOI = new Uint8Array(2)
|
||||||
SOI[0] = 0xff
|
SOI[0] = 0xff
|
||||||
SOI[1] = 0xd8
|
SOI[1] = 0xd8
|
||||||
@ -91,7 +108,7 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
|
|||||||
const { signal } = this.controller
|
const { signal } = this.controller
|
||||||
|
|
||||||
//readable stream credit to from https://github.com/aruntj/mjpeg-readable-stream
|
//readable stream credit to from https://github.com/aruntj/mjpeg-readable-stream
|
||||||
fetch(this.url, { signal })
|
fetch(this.url, { signal, mode: 'cors' })
|
||||||
.then((response) => response.body)
|
.then((response) => response.body)
|
||||||
.then((rb) => {
|
.then((rb) => {
|
||||||
const reader = rb?.getReader()
|
const reader = rb?.getReader()
|
||||||
@ -116,10 +133,8 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
|
|||||||
|
|
||||||
this.stream = new ReadableStream({
|
this.stream = new ReadableStream({
|
||||||
start(controller) {
|
start(controller) {
|
||||||
return pump()
|
|
||||||
|
|
||||||
// The following function handles each data chunk
|
// The following function handles each data chunk
|
||||||
function pump(): any {
|
const pump = (): any => {
|
||||||
// "done" is a Boolean and value a "Uint8Array"
|
// "done" is a Boolean and value a "Uint8Array"
|
||||||
return reader?.read().then(({ done, value }) => {
|
return reader?.read().then(({ done, value }) => {
|
||||||
// If there is no more data to read
|
// If there is no more data to read
|
||||||
@ -165,22 +180,25 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
|
|||||||
return pump()
|
return pump()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return pump()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('visibilitychange', this.visibilityChanged)
|
document.addEventListener('visibilitychange', this.documentVisibilityChanged)
|
||||||
this.startStream()
|
this.startStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
document.removeEventListener('visibilitychange', this.visibilityChanged)
|
document.removeEventListener('visibilitychange', this.documentVisibilityChanged)
|
||||||
this.stopStream()
|
this.stopStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
stopStream() {
|
stopStream() {
|
||||||
|
this.streamState = false
|
||||||
URL.revokeObjectURL(this.url)
|
URL.revokeObjectURL(this.url)
|
||||||
if (this.timerFPS) clearTimeout(this.timerFPS)
|
if (this.timerFPS) clearTimeout(this.timerFPS)
|
||||||
if (this.timerRestart) clearTimeout(this.timerRestart)
|
if (this.timerRestart) clearTimeout(this.timerRestart)
|
||||||
@ -195,18 +213,32 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
|
|||||||
|
|
||||||
@Watch('url')
|
@Watch('url')
|
||||||
urlChanged() {
|
urlChanged() {
|
||||||
|
this.aspectRatio = null
|
||||||
this.restartStream()
|
this.restartStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
visibilityChanged() {
|
// this function check if you changed the browser tab
|
||||||
|
documentVisibilityChanged() {
|
||||||
const visibility = document.visibilityState
|
const visibility = document.visibilityState
|
||||||
|
this.visibilityChanged(visibility === 'visible')
|
||||||
|
}
|
||||||
|
|
||||||
if (visibility === 'visible') {
|
// this function is to stop the stream, on scroll or on collapse the webcam panel
|
||||||
|
visibilityChanged(newVal: boolean) {
|
||||||
|
if (newVal && this.streamState) return
|
||||||
|
|
||||||
|
if (newVal) {
|
||||||
this.startStream()
|
this.startStream()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stopStream()
|
this.stopStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onload() {
|
||||||
|
if (this.aspectRatio === null && this.$refs.image) {
|
||||||
|
this.aspectRatio = this.$refs.image.naturalWidth / this.$refs.image.naturalHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<canvas
|
<canvas
|
||||||
ref="mjpegstreamerAdaptive"
|
ref="mjpegstreamerAdaptive"
|
||||||
|
v-observe-visibility="visibilityChanged"
|
||||||
width="600"
|
width="600"
|
||||||
height="400"
|
height="400"
|
||||||
:style="webcamStyle"
|
:style="webcamStyle"
|
||||||
@ -46,6 +47,7 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
|
|||||||
private time_smoothing = 0.6
|
private time_smoothing = 0.6
|
||||||
private request_time_smoothing = 0.1
|
private request_time_smoothing = 0.1
|
||||||
private currentFPS = 0
|
private currentFPS = 0
|
||||||
|
private aspectRatio: null | number = null
|
||||||
|
|
||||||
declare $refs: {
|
declare $refs: {
|
||||||
mjpegstreamerAdaptive: any
|
mjpegstreamerAdaptive: any
|
||||||
@ -56,12 +58,19 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
|
|||||||
@Prop({ default: true }) declare showFps: boolean
|
@Prop({ default: true }) declare showFps: boolean
|
||||||
|
|
||||||
get webcamStyle() {
|
get webcamStyle() {
|
||||||
|
const output = {
|
||||||
|
transform: 'none',
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
}
|
||||||
|
|
||||||
let transforms = ''
|
let transforms = ''
|
||||||
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-1)'
|
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-1)'
|
||||||
if ('flipX' in this.camSettings && this.camSettings.flipY) transforms += ' scaleY(-1)'
|
if ('flipX' in this.camSettings && this.camSettings.flipY) transforms += ' scaleY(-1)'
|
||||||
if (transforms.trimLeft().length) return { transform: transforms.trimLeft() }
|
if (transforms.trimStart().length) output.transform = transforms.trimStart()
|
||||||
|
|
||||||
return ''
|
if (this.aspectRatio) output.aspectRatio = this.aspectRatio
|
||||||
|
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
get fpsOutput() {
|
get fpsOutput() {
|
||||||
@ -112,6 +121,9 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
|
|||||||
|
|
||||||
canvas.width = canvas.clientWidth
|
canvas.width = canvas.clientWidth
|
||||||
canvas.height = canvas.clientWidth * (frame.height / frame.width)
|
canvas.height = canvas.clientWidth * (frame.height / frame.width)
|
||||||
|
if (this.aspectRatio === null) {
|
||||||
|
this.aspectRatio = frame.width / frame.height
|
||||||
|
}
|
||||||
|
|
||||||
ctx?.drawImage(frame, 0, 0, frame.width, frame.height, 0, 0, canvas.width, canvas.height)
|
ctx?.drawImage(frame, 0, 0, frame.width, frame.height, 0, 0, canvas.width, canvas.height)
|
||||||
this.isLoaded = true
|
this.isLoaded = true
|
||||||
@ -132,18 +144,21 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('visibilitychange', this.visibilityChanged)
|
document.addEventListener('visibilitychange', this.documentVisibilityChanged)
|
||||||
this.refreshFrame()
|
this.refreshFrame()
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
document.removeEventListener('visibilitychange', this.visibilityChanged)
|
document.removeEventListener('visibilitychange', this.documentVisibilityChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
visibilityChanged() {
|
documentVisibilityChanged() {
|
||||||
const visibility = document.visibilityState
|
const visibility = document.visibilityState
|
||||||
|
this.visibilityChanged(visibility === 'visible')
|
||||||
|
}
|
||||||
|
|
||||||
if (visibility === 'visible') {
|
visibilityChanged(newVal: boolean) {
|
||||||
|
if (newVal) {
|
||||||
this.isVisible = true
|
this.isVisible = true
|
||||||
this.refreshFrame()
|
this.refreshFrame()
|
||||||
return
|
return
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.webcamImage {
|
.webcamImage {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: lightgray;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img ref="webcamUv4lMjpegImage" :src="url" :alt="camSettings.name" :style="webcamStyle" class="webcamImage" />
|
<img
|
||||||
|
ref="webcamUv4lMjpegImage"
|
||||||
|
v-observe-visibility="visibilityChanged"
|
||||||
|
:src="url"
|
||||||
|
:alt="camSettings.name"
|
||||||
|
:style="webcamStyle"
|
||||||
|
class="webcamImage"
|
||||||
|
@load="onload" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -15,6 +23,7 @@ import { GuiWebcamStateWebcam } from '@/store/gui/webcams/types'
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class Uv4lMjpeg extends Mixins(BaseMixin) {
|
export default class Uv4lMjpeg extends Mixins(BaseMixin) {
|
||||||
|
private aspectRatio: null | number = null
|
||||||
@Prop({ required: true }) declare readonly camSettings: GuiWebcamStateWebcam
|
@Prop({ required: true }) declare readonly camSettings: GuiWebcamStateWebcam
|
||||||
@Prop({ default: null }) declare readonly printerUrl: string | null
|
@Prop({ default: null }) declare readonly printerUrl: string | null
|
||||||
|
|
||||||
@ -30,20 +39,27 @@ export default class Uv4lMjpeg extends Mixins(BaseMixin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get webcamStyle() {
|
get webcamStyle() {
|
||||||
|
const output = {
|
||||||
|
transform: 'none',
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
}
|
||||||
|
|
||||||
let transforms = ''
|
let transforms = ''
|
||||||
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-1)'
|
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-1)'
|
||||||
if ('flipX' in this.camSettings && this.camSettings.flipY) transforms += ' scaleY(-1)'
|
if ('flipX' in this.camSettings && this.camSettings.flipY) transforms += ' scaleY(-1)'
|
||||||
if (transforms.trimLeft().length) return { transform: transforms.trimLeft() }
|
if (transforms.trimStart().length) output.transform = transforms.trimStart()
|
||||||
|
|
||||||
return ''
|
if (this.aspectRatio) output.aspectRatio = this.aspectRatio
|
||||||
|
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('visibilitychange', this.visibilityChanged)
|
document.addEventListener('visibilitychange', this.documentVisibilityChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
document.removeEventListener('visibilitychange', this.visibilityChanged)
|
document.removeEventListener('visibilitychange', this.documentVisibilityChanged)
|
||||||
this.stopStream()
|
this.stopStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,15 +74,25 @@ export default class Uv4lMjpeg extends Mixins(BaseMixin) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visibilityChanged() {
|
documentVisibilityChanged() {
|
||||||
const visibility = document.visibilityState
|
const visibility = document.visibilityState
|
||||||
|
this.visibilityChanged(visibility === 'visible')
|
||||||
|
}
|
||||||
|
|
||||||
if (visibility === 'visible') {
|
visibilityChanged(newVal: boolean) {
|
||||||
|
if (newVal) {
|
||||||
this.startStream()
|
this.startStream()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stopStream()
|
this.stopStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onload() {
|
||||||
|
if (this.aspectRatio === null && this.$refs.webcamUv4lMjpegImage) {
|
||||||
|
this.aspectRatio =
|
||||||
|
this.$refs.webcamUv4lMjpegImage.naturalWidth / this.$refs.webcamUv4lMjpegImage.naturalHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -19,8 +19,8 @@ const updateSW = registerSW({
|
|||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
// vue-observe-visibility
|
// vue-observe-visibility
|
||||||
import VueObserveVisibility from 'vue-observe-visibility'
|
import { ObserveVisibility } from 'vue-observe-visibility'
|
||||||
Vue.use(VueObserveVisibility)
|
Vue.directive('observe-visibility', ObserveVisibility)
|
||||||
|
|
||||||
//vue-meta
|
//vue-meta
|
||||||
import VueMeta from 'vue-meta'
|
import VueMeta from 'vue-meta'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user