Skip to content

Commit

Permalink
feat: adds WebRTC (camera-streamer) support (#1107)
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
  • Loading branch information
pedrolamas committed Jun 6, 2023
1 parent 220057f commit 1e42ca8
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/components/settings/cameras/CameraConfigDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
:title="(camera.id != '') ? $t('app.general.label.edit_camera') : $t('app.general.label.add_camera')"
:cancel-button-text="camera.source === 'config' ? $t('app.general.btn.close') : $t('app.general.btn.cancel')"
:save-button-text="(camera.id !== '') ? $t('app.general.btn.save') : $t('app.general.btn.add')"
max-width="500"
max-width="600"
:disabled="camera.source === 'config'"
@save="handleSave"
>
Expand Down Expand Up @@ -83,6 +83,7 @@
{ text: $t('app.setting.camera_type_options.mjpegadaptive'), value: 'mjpegstreamer-adaptive' },
{ text: $t('app.setting.camera_type_options.mjpegstream'), value: 'mjpegstreamer' },
{ text: $t('app.setting.camera_type_options.hlsstream'), value: 'hlsstream' },
{ text: $t('app.setting.camera_type_options.webrtc_camera_streamer'), value: 'webrtc-camerastreamer' },
{ text: $t('app.setting.camera_type_options.video'), value: 'ipstream' },
{ text: $t('app.setting.camera_type_options.iframe'), value: 'iframe' }
]"
Expand Down
79 changes: 78 additions & 1 deletion src/components/widgets/camera/CameraItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
>

<video
v-else-if="camera.service === 'ipstream' || camera.service === 'hlsstream'"
v-else-if="camera.service === 'ipstream' || camera.service === 'hlsstream' || camera.service === 'webrtc-camerastreamer'"
ref="camera_image"
:src="cameraUrl"
autoplay
Expand Down Expand Up @@ -66,6 +66,7 @@ import { CameraConfig } from '@/store/cameras/types'
import { noop } from 'vue-class-component/lib/util'
import { CameraFullscreenAction } from '@/store/config/types'
import type HlsType from 'hls.js'
import consola from 'consola'
let Hls: typeof HlsType
/**
Expand All @@ -91,6 +92,8 @@ export default class CameraItem extends Vue {
request_time_smoothing = 0.1
currentFPS = '0'
hls: HlsType | null = null
pc: RTCPeerConnection | null = null
remoteId: string | null = null
// URL used by camera
cameraUrl = ''
Expand Down Expand Up @@ -157,6 +160,7 @@ export default class CameraItem extends Vue {
*/
beforeDestroy () {
this.hls?.destroy()
this.pc?.close()
this.cancelCameraTransform()
this.cameraUrl = this.cameraImage.src = ''
this.cameraFullScreenUrl = ''
Expand Down Expand Up @@ -286,9 +290,82 @@ export default class CameraItem extends Vue {
}
break
}
case 'webrtc-camerastreamer': {
const cameraVideo = this.cameraImage as HTMLVideoElement
this.pc?.close()
const config = {
sdpSemantics: 'unified-plan'
} as RTCConfiguration
this.pc = new RTCPeerConnection(config)
this.pc.addTransceiver('video', {
direction: 'recvonly'
})
this.pc.ontrack = (evt: RTCTrackEvent) => {
if (evt.track.kind === 'video' && cameraVideo) {
cameraVideo.srcObject = evt.streams[0]
}
}
fetch(baseUrl, {
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
return this.pc?.setRemoteDescription(answer)
})
.then(() => this.pc?.createAnswer())
.then(answer => this.pc?.setLocalDescription(answer))
.then(() => new Promise(resolve => {
const checkState = () => {
if (this.pc?.iceGatheringState === 'complete') {
this.pc?.removeEventListener('icegatheringstatechange', checkState)
resolve(true)
}
}
if (this.pc?.iceGatheringState === 'complete') {
resolve(true)
} else {
this.pc?.addEventListener('icegatheringstatechange', checkState)
}
}))
.then(() => {
const offer = this.pc?.localDescription
return fetch(baseUrl, {
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('[CameraItem] setUrl', e))
}
}
} else {
this.hls?.destroy()
this.pc?.close()
this.cameraUrl = ''
}
}
Expand Down
1 change: 1 addition & 0 deletions src/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ app:
mjpegadaptive: MJPEG Adaptive
mjpegstream: MJPEG Stream
hlsstream: HLS Stream
webrtc_camera_streamer: WebRTC (camera-streamer)
video: IP Camera
iframe: HTTP page
camera_rotate_options:
Expand Down
2 changes: 1 addition & 1 deletion src/store/cameras/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface CameraConfig extends CameraConfigWithoutId {
id: string;
}

export type CameraService = 'mjpegstreamer' | 'mjpegstreamer-adaptive' | 'ipstream' | 'iframe' | 'hlsstream'
export type CameraService = 'mjpegstreamer' | 'mjpegstreamer-adaptive' | 'ipstream' | 'iframe' | 'hlsstream' | 'webrtc-camerastreamer'

export interface LegacyCamerasState {
activeCamera: string;
Expand Down

0 comments on commit 1e42ca8

Please sign in to comment.