Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: stop webcam when webcam panel is collapse #839

Merged
merged 5 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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