Skip to content

Commit

Permalink
fix: stop webcam when webcam panel is collapse (#839)
Browse files Browse the repository at this point in the history
  • Loading branch information
meteyou authored May 24, 2022
1 parent bbf2832 commit 6ffb771
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 26 deletions.
54 changes: 43 additions & 11 deletions src/components/webcams/Mjpegstreamer.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<style>
.webcamImage {
width: 100%;
background: lightgray;
}
.webcamFpsOutput {
Expand All @@ -16,7 +17,12 @@

<template>
<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>
</div>
</template>
Expand All @@ -32,6 +38,8 @@ const TYPE_JPEG = 'image/jpeg'
@Component
export default class Mjpegstreamer extends Mixins(BaseMixin) {
private currentFPS = 0
private streamState = false
private aspectRatio: null | number = null
private timerFPS: number | null = null
private timerRestart: number | null = null
private stream: ReadableStream | null = null
Expand All @@ -58,19 +66,28 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
}
get webcamStyle() {
const output = {
transform: 'none',
aspectRatio: 16 / 9,
}
let transforms = ''
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-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() {
return this.currentFPS < 10 ? '0' + this.currentFPS.toString() : this.currentFPS
}
startStream() {
this.streamState = true
const SOI = new Uint8Array(2)
SOI[0] = 0xff
SOI[1] = 0xd8
Expand All @@ -91,7 +108,7 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
const { signal } = this.controller
//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((rb) => {
const reader = rb?.getReader()
Expand All @@ -116,10 +133,8 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
this.stream = new ReadableStream({
start(controller) {
return pump()
// The following function handles each data chunk
function pump(): any {
const pump = (): any => {
// "done" is a Boolean and value a "Uint8Array"
return reader?.read().then(({ done, value }) => {
// If there is no more data to read
Expand Down Expand Up @@ -165,22 +180,25 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
return pump()
})
}
return pump()
},
})
})
}
mounted() {
document.addEventListener('visibilitychange', this.visibilityChanged)
document.addEventListener('visibilitychange', this.documentVisibilityChanged)
this.startStream()
}
beforeDestroy() {
document.removeEventListener('visibilitychange', this.visibilityChanged)
document.removeEventListener('visibilitychange', this.documentVisibilityChanged)
this.stopStream()
}
stopStream() {
this.streamState = false
URL.revokeObjectURL(this.url)
if (this.timerFPS) clearTimeout(this.timerFPS)
if (this.timerRestart) clearTimeout(this.timerRestart)
Expand All @@ -195,18 +213,32 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
@Watch('url')
urlChanged() {
this.aspectRatio = null
this.restartStream()
}
visibilityChanged() {
// this function check if you changed the browser tab
documentVisibilityChanged() {
const visibility = document.visibilityState
this.visibilityChanged(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 (visibility === 'visible') {
if (newVal) {
this.startStream()
return
}
this.stopStream()
}
onload() {
if (this.aspectRatio === null && this.$refs.image) {
this.aspectRatio = this.$refs.image.naturalWidth / this.$refs.image.naturalHeight
}
}
}
</script>
27 changes: 21 additions & 6 deletions src/components/webcams/MjpegstreamerAdaptive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
</div>
<canvas
ref="mjpegstreamerAdaptive"
v-observe-visibility="visibilityChanged"
width="600"
height="400"
:style="webcamStyle"
Expand Down Expand Up @@ -46,6 +47,7 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
private time_smoothing = 0.6
private request_time_smoothing = 0.1
private currentFPS = 0
private aspectRatio: null | number = null
declare $refs: {
mjpegstreamerAdaptive: any
Expand All @@ -56,12 +58,19 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
@Prop({ default: true }) declare showFps: boolean
get webcamStyle() {
const output = {
transform: 'none',
aspectRatio: 16 / 9,
}
let transforms = ''
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-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() {
Expand Down Expand Up @@ -112,6 +121,9 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
canvas.width = canvas.clientWidth
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)
this.isLoaded = true
Expand All @@ -132,18 +144,21 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
}
mounted() {
document.addEventListener('visibilitychange', this.visibilityChanged)
document.addEventListener('visibilitychange', this.documentVisibilityChanged)
this.refreshFrame()
}
beforeDestroy() {
document.removeEventListener('visibilitychange', this.visibilityChanged)
document.removeEventListener('visibilitychange', this.documentVisibilityChanged)
}
visibilityChanged() {
documentVisibilityChanged() {
const visibility = document.visibilityState
this.visibilityChanged(visibility === 'visible')
}
if (visibility === 'visible') {
visibilityChanged(newVal: boolean) {
if (newVal) {
this.isVisible = true
this.refreshFrame()
return
Expand Down
40 changes: 33 additions & 7 deletions src/components/webcams/Uv4lMjpeg.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
<style scoped>
.webcamImage {
width: 100%;
background: lightgray;
}
</style>

<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>

<script lang="ts">
Expand All @@ -15,6 +23,7 @@ import { GuiWebcamStateWebcam } from '@/store/gui/webcams/types'
@Component
export default class Uv4lMjpeg extends Mixins(BaseMixin) {
private aspectRatio: null | number = null
@Prop({ required: true }) declare readonly camSettings: GuiWebcamStateWebcam
@Prop({ default: null }) declare readonly printerUrl: string | null
Expand All @@ -30,20 +39,27 @@ export default class Uv4lMjpeg extends Mixins(BaseMixin) {
}
get webcamStyle() {
const output = {
transform: 'none',
aspectRatio: 16 / 9,
}
let transforms = ''
if ('flipX' in this.camSettings && this.camSettings.flipX) transforms += ' scaleX(-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() {
document.addEventListener('visibilitychange', this.visibilityChanged)
document.addEventListener('visibilitychange', this.documentVisibilityChanged)
}
beforeDestroy() {
document.removeEventListener('visibilitychange', this.visibilityChanged)
document.removeEventListener('visibilitychange', this.documentVisibilityChanged)
this.stopStream()
}
Expand All @@ -58,15 +74,25 @@ export default class Uv4lMjpeg extends Mixins(BaseMixin) {
}
}
visibilityChanged() {
documentVisibilityChanged() {
const visibility = document.visibilityState
this.visibilityChanged(visibility === 'visible')
}
if (visibility === 'visible') {
visibilityChanged(newVal: boolean) {
if (newVal) {
this.startStream()
return
}
this.stopStream()
}
onload() {
if (this.aspectRatio === null && this.$refs.webcamUv4lMjpegImage) {
this.aspectRatio =
this.$refs.webcamUv4lMjpegImage.naturalWidth / this.$refs.webcamUv4lMjpegImage.naturalHeight
}
}
}
</script>
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const updateSW = registerSW({
Vue.config.productionTip = false

// vue-observe-visibility
import VueObserveVisibility from 'vue-observe-visibility'
Vue.use(VueObserveVisibility)
import { ObserveVisibility } from 'vue-observe-visibility'
Vue.directive('observe-visibility', ObserveVisibility)

//vue-meta
import VueMeta from 'vue-meta'
Expand Down

0 comments on commit 6ffb771

Please sign in to comment.