-
-
Notifications
You must be signed in to change notification settings - Fork 434
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move each camera to its own component
Signed-off-by: Pedro Lamas <[email protected]>
- Loading branch information
1 parent
4a4e8f6
commit e6fdaf5
Showing
9 changed files
with
509 additions
and
365 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
52 changes: 52 additions & 0 deletions
52
src/components/widgets/camera/services/HlsstreamCamera.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<template> | ||
<video | ||
ref="streamingElement" | ||
autoplay | ||
muted | ||
:style="cameraStyle" | ||
/> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { Component, Ref, Mixins } from 'vue-property-decorator' | ||
import CameraMixin from '@/mixins/camera' | ||
import Hls from 'hls.js' | ||
@Component({}) | ||
export default class HlsstreamCamera extends Mixins(CameraMixin) { | ||
@Ref('streamingElement') | ||
readonly cameraVideo!: HTMLVideoElement | ||
hls: Hls | null = null | ||
startPlayback () { | ||
const url = this.cameraUrl | ||
if (Hls.isSupported()) { | ||
this.hls?.destroy() | ||
this.hls = new Hls({ | ||
enableWorker: true, | ||
lowLatencyMode: true, | ||
maxLiveSyncPlaybackRate: 2, | ||
liveSyncDuration: 0.5, | ||
liveMaxLatencyDuration: 2, | ||
backBufferLength: 5 | ||
}) | ||
this.hls.loadSource(url) | ||
this.hls.attachMedia(this.cameraVideo) | ||
this.hls.on(Hls.Events.MEDIA_ATTACHED, () => { | ||
this.cameraVideo.play() | ||
}) | ||
} else if (this.cameraVideo.canPlayType('application/vnd.apple.mpegurl')) { | ||
this.cameraVideo.src = url | ||
} | ||
} | ||
stopPlayback () { | ||
this.hls?.destroy() | ||
this.hls = null | ||
this.cameraVideo.src = '' | ||
} | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<template> | ||
<iframe | ||
ref="streamingElement" | ||
:src="cameraIFrameSource" | ||
style="border: none; width: 100%" | ||
:style="{ | ||
'aspect-ratio': (camera.aspectRatio || '16:9').replace(':', '/'), | ||
...cameraStyle | ||
}" | ||
/> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { Component, Ref, Mixins } from 'vue-property-decorator' | ||
import CameraMixin from '@/mixins/camera' | ||
@Component({}) | ||
export default class IframeCamera extends Mixins(CameraMixin) { | ||
@Ref('streamingElement') | ||
readonly cameraIframe!: HTMLIFrameElement | ||
cameraIFrameSource = '' | ||
startPlayback () { | ||
this.cameraIFrameSource = this.cameraUrl | ||
this.$emit('raw-camera-url', this.cameraUrl) | ||
} | ||
stopPlayback () { | ||
this.cameraIFrameSource = '' | ||
this.cameraIframe.src = '' | ||
} | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<template> | ||
<video | ||
ref="streamingElement" | ||
:src="cameraVideoSource" | ||
autoplay | ||
muted | ||
:style="cameraStyle" | ||
/> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { Component, Mixins, Ref } from 'vue-property-decorator' | ||
import CameraMixin from '@/mixins/camera' | ||
@Component({}) | ||
export default class IpstreamCamera extends Mixins(CameraMixin) { | ||
@Ref('streamingElement') | ||
readonly cameraVideo!: HTMLVideoElement | ||
cameraVideoSource = '' | ||
startPlayback () { | ||
this.cameraVideoSource = this.cameraUrl | ||
this.$emit('raw-camera-url', this.cameraUrl) | ||
} | ||
stopPlayback () { | ||
this.cameraVideoSource = '' | ||
this.cameraVideo.src = '' | ||
} | ||
} | ||
</script> |
92 changes: 92 additions & 0 deletions
92
src/components/widgets/camera/services/MjpegstreamerAdaptiveCamera.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<template> | ||
<img | ||
ref="streamingElement" | ||
:src="cameraImageSource" | ||
:style="cameraStyle" | ||
@load="handleImageLoad" | ||
> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { Component, Mixins, Ref } from 'vue-property-decorator' | ||
import CameraMixin from '@/mixins/camera' | ||
@Component({}) | ||
export default class MjpegstreamerAdaptiveCamera extends Mixins(CameraMixin) { | ||
@Ref('streamingElement') | ||
readonly cameraImage!: HTMLImageElement | ||
cameraImageSource = '' | ||
requestStartTime = performance.now() | ||
startTime = performance.now() | ||
time = 0 | ||
requestTime = 0 | ||
timeSmoothing = 0.6 | ||
requestTimeSmoothing = 0.1 | ||
handleImageLoad () { | ||
const fpsTarget = (!document.hasFocus() && this.camera.targetFpsIdle) || this.camera.targetFps || 10 | ||
const endTime = performance.now() | ||
const currentTime = endTime - this.startTime | ||
this.time = (this.time * this.timeSmoothing) + (currentTime * (1.0 - this.timeSmoothing)) | ||
this.startTime = endTime | ||
const targetTime = 1000 / fpsTarget | ||
const currentRequestTime = performance.now() - this.requestStartTime | ||
this.requestTime = (this.requestTime * this.requestTimeSmoothing) + (currentRequestTime * (1.0 - this.requestTimeSmoothing)) | ||
const timeout = Math.max(0, targetTime - this.requestTime) | ||
this.$nextTick(() => { | ||
setTimeout(this.handleRefresh, timeout) | ||
}) | ||
} | ||
handleRefresh () { | ||
if (!document.hidden) { | ||
const framesPerSecond = Math.round(1000 / this.time).toLocaleString(undefined, { minimumIntegerDigits: 2 }) | ||
this.$emit('frames-per-second', framesPerSecond) | ||
this.$nextTick(() => { | ||
const url = new URL(this.cameraImageSource) | ||
url.searchParams.set('cacheBust', Date.now().toString()) | ||
this.requestStartTime = performance.now() | ||
this.cameraImageSource = url.toString() | ||
}) | ||
} else { | ||
this.stopPlayback() | ||
} | ||
} | ||
startPlayback () { | ||
const url = new URL(this.cameraUrl) | ||
this.requestStartTime = performance.now() | ||
if (!url.searchParams.get('action')?.startsWith('snapshot')) { | ||
url.searchParams.set('action', 'snapshot') | ||
} | ||
url.searchParams.set('cacheBust', Date.now().toString()) | ||
this.cameraImageSource = url.toString() | ||
url.searchParams.set('action', 'stream') | ||
this.$emit('raw-camera-url', url.toString()) | ||
} | ||
stopPlayback () { | ||
this.cameraImageSource = '' | ||
this.cameraImage.src = '' | ||
} | ||
} | ||
</script> |
39 changes: 39 additions & 0 deletions
39
src/components/widgets/camera/services/MjpegstreamerCamera.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<template> | ||
<img | ||
ref="streamingElement" | ||
:src="cameraImageSource" | ||
:style="cameraStyle" | ||
> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { Component, Mixins, Ref } from 'vue-property-decorator' | ||
import CameraMixin from '@/mixins/camera' | ||
@Component({}) | ||
export default class MjpegstreamerCamera extends Mixins(CameraMixin) { | ||
@Ref('streamingElement') | ||
readonly cameraImage!: HTMLImageElement | ||
cameraImageSource = '' | ||
startPlayback () { | ||
const url = new URL(this.cameraUrl) | ||
if (!url.searchParams.get('action')?.startsWith('stream')) { | ||
url.searchParams.set('action', 'stream') | ||
} | ||
url.searchParams.set('cacheBust', Date.now().toString()) | ||
this.cameraImageSource = url.toString() | ||
this.$emit('raw-camera-url', this.cameraImageSource) | ||
} | ||
stopPlayback () { | ||
this.cameraImageSource = '' | ||
this.cameraImage.src = '' | ||
} | ||
} | ||
</script> |
107 changes: 107 additions & 0 deletions
107
src/components/widgets/camera/services/WebrtcCamerastreamerCamera.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
<template> | ||
<video | ||
ref="streamingElement" | ||
autoplay | ||
muted | ||
:style="cameraStyle" | ||
/> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { Component, Ref, Mixins } from 'vue-property-decorator' | ||
import consola from 'consola' | ||
import CameraMixin from '@/mixins/camera' | ||
@Component({}) | ||
export default class WebrtcCamerastreamerCamera extends Mixins(CameraMixin) { | ||
@Ref('streamingElement') | ||
readonly cameraVideo!: HTMLVideoElement | ||
pc: RTCPeerConnection | null = null | ||
remoteId: string | null = null | ||
startPlayback () { | ||
const url = this.cameraUrl | ||
this.pc?.close() | ||
fetch(url, { | ||
body: JSON.stringify({ | ||
type: 'request' | ||
}), | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
method: 'POST' | ||
}) | ||
.then(response => response.json()) | ||
.then((answer: RTCSessionDescriptionInit) => { | ||
this.remoteId = 'id' in answer && typeof (answer.id) === 'string' ? answer.id : null | ||
const config = { | ||
sdpSemantics: 'unified-plan' | ||
} as RTCConfiguration | ||
if ('iceServers' in answer && Array.isArray(answer.iceServers)) { | ||
config.iceServers = answer.iceServers | ||
} | ||
this.pc = new RTCPeerConnection(config) | ||
this.pc.addTransceiver('video', { | ||
direction: 'recvonly' | ||
}) | ||
this.pc.ontrack = (evt: RTCTrackEvent) => { | ||
if (evt.track.kind === 'video') { | ||
this.cameraVideo.srcObject = evt.streams[0] | ||
} | ||
} | ||
this.pc.onicecandidate = (e: RTCPeerConnectionIceEvent) => { | ||
if (e.candidate) { | ||
return fetch(url, { | ||
body: JSON.stringify({ | ||
type: 'remote_candidate', | ||
id: this.remoteId, | ||
candidates: [e.candidate] | ||
}), | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
method: 'POST' | ||
}) | ||
.catch(e => consola.error('[WebrtcCamerastreamerCamera] onicecandidate', e)) | ||
} | ||
} | ||
return this.pc?.setRemoteDescription(answer) | ||
}) | ||
.then(() => this.pc?.createAnswer()) | ||
.then(answer => this.pc?.setLocalDescription(answer)) | ||
.then(() => { | ||
const offer = this.pc?.localDescription | ||
return fetch(url, { | ||
body: JSON.stringify({ | ||
type: offer?.type, | ||
id: this.remoteId, | ||
sdp: offer?.sdp | ||
}), | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
method: 'POST' | ||
}) | ||
}) | ||
.then(response => response.json()) | ||
.catch(e => consola.error('[WebrtcCamerastreamerCamera] setUrl', e)) | ||
} | ||
stopPlayback () { | ||
this.pc?.close() | ||
this.pc = null | ||
this.cameraVideo.src = '' | ||
} | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.