Skip to content

Commit

Permalink
fix(MediaMTX): fix some connection issues (#1979)
Browse files Browse the repository at this point in the history
  • Loading branch information
meteyou authored Aug 24, 2024
1 parent e0f5f78 commit 85c5774
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 87 deletions.
2 changes: 1 addition & 1 deletion src/components/panels/WebcamPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<v-card-text v-if="webcams.length" class="px-0 py-0 content d-inline-block">
<v-row>
<v-col class="pb-0" style="position: relative">
<webcam-wrapper :webcam="currentCam" />
<webcam-wrapper :webcam="currentCam" :page="currentPage" />
</v-col>
</v-row>
</v-card-text>
Expand Down
2 changes: 1 addition & 1 deletion src/components/settings/Webcams/WebcamForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
</v-row>
</v-col>
<v-col class="col-12 col-sm-6 text-center" align-self="center">
<webcam-wrapper :webcam="webcam" />
<webcam-wrapper :webcam="webcam" page="settings" />
</v-col>
</v-row>
</v-card-text>
Expand Down
9 changes: 7 additions & 2 deletions src/components/webcams/WebcamWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
<v-container v-if="webcams" fluid class="pb-4">
<v-row dense>
<v-col v-for="gridWebcam in webcams" :key="gridWebcam.name" cols="6">
<webcam-wrapper-item :webcam="gridWebcam" :printer-url="printerUrl" :show-fps="showFps" />
<webcam-wrapper-item
:webcam="gridWebcam"
:printer-url="printerUrl"
:show-fps="showFps"
:page="page" />
</v-col>
</v-row>
</v-container>
</template>
<template v-else>
<webcam-wrapper-item :webcam="webcam" :printer-url="printerUrl" :show-fps="showFps" />
<webcam-wrapper-item :webcam="webcam" :printer-url="printerUrl" :show-fps="showFps" :page="page" />
</template>
</div>
</template>
Expand All @@ -31,6 +35,7 @@ export default class WebcamWrapper extends Mixins(BaseMixin) {
@Prop({ type: Object, required: true }) webcam!: GuiWebcamStateWebcam
@Prop({ type: Boolean, default: true }) showFps!: Boolean
@Prop({ type: String, default: null }) printerUrl!: string | null
@Prop({ type: String, default: null }) page!: string | null
get webcams(): GuiWebcamStateWebcam[] {
return this.$store.getters['gui/webcams/getWebcams']
Expand Down
3 changes: 2 additions & 1 deletion src/components/webcams/WebcamWrapperItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<janus-streamer-async :cam-settings="webcam" :printer-url="printerUrl" />
</template>
<template v-else-if="service === 'webrtc-mediamtx'">
<webrtc-media-m-t-x-async :cam-settings="webcam" :printer-url="printerUrl" />
<webrtc-media-m-t-x-async :cam-settings="webcam" :printer-url="printerUrl" :page="page" />
</template>
<template v-else-if="service === 'webrtc-go2rtc'">
<webrtc-go2rtc-async :cam-settings="webcam" :printer-url="printerUrl" />
Expand Down Expand Up @@ -61,6 +61,7 @@ export default class WebcamWrapperItem extends Mixins(BaseMixin) {
@Prop({ type: Object, required: true }) webcam!: GuiWebcamStateWebcam
@Prop({ type: Boolean, default: true }) showFps!: Boolean
@Prop({ default: null }) printerUrl!: string | null
@Prop({ type: String, default: null }) page!: string | null
get service() {
return this.webcam?.service ?? 'unknown'
Expand Down
173 changes: 92 additions & 81 deletions src/components/webcams/streamers/WebrtcMediaMTX.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface OfferData {
export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
@Prop({ required: true }) readonly camSettings!: GuiWebcamStateWebcam
@Prop({ default: null }) readonly printerUrl!: string | null
@Prop({ type: String, default: null }) readonly page!: string | null
@Ref() declare video: HTMLVideoElement
pc: RTCPeerConnection | null = null
Expand All @@ -48,10 +49,6 @@ export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
}
RESTART_PAUSE = 2000
mounted() {
this.start()
}
// stop the video and close the streams if the component is going to be destroyed so we don't leave hanging streams
beforeDestroy() {
this.terminate()
Expand All @@ -73,16 +70,9 @@ export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
get url() {
let baseUrl = this.camSettings.stream_url
if (!baseUrl.endsWith('/')) baseUrl += '/'
baseUrl += 'whep'
try {
baseUrl = new URL('whep', baseUrl).toString()
return this.convertUrl(baseUrl, this.printerUrl)
} catch (e) {
this.log('invalid baseURL', baseUrl)
return null
}
return this.convertUrl(baseUrl, this.printerUrl)
}
// stop and restart the video if the url changes
Expand All @@ -93,11 +83,13 @@ export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
}
get expanded(): boolean {
if (this.page !== 'dashboard') return true
return this.$store.getters['gui/getPanelExpand']('webcam-panel', this.viewport) ?? false
}
// start or stop the video when the expand state changes
@Watch('expanded')
// start or stop the video when the expanded state changes
@Watch('expanded', { immediate: true })
expandChanged(newExpanded: boolean): void {
if (!newExpanded) {
this.terminate()
Expand Down Expand Up @@ -198,7 +190,13 @@ export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
return frag
}
start() {
async start() {
// clear any potentially open restart timeout
if (this.restartTimeout !== null) {
clearTimeout(this.restartTimeout)
this.restartTimeout = null
}
// stop if url is not valid
if (this.url === null) {
this.log('invalid url')
Expand All @@ -209,17 +207,22 @@ export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
this.log('requesting ICE servers from ' + this.url)
fetch(this.url, {
method: 'OPTIONS',
})
.then((res) => this.onIceServers(res))
.catch((err) => {
this.log('error: ' + err)
try {
const res = await fetch(this.url, { method: 'OPTIONS' })
if (res.status !== 204) {
this.log('error: Received bad status code:', res.status)
this.scheduleRestart()
})
return
}
await this.onIceServers(res)
} catch (err) {
this.log('error: Cannot connect to backend')
this.scheduleRestart()
}
}
onIceServers(res: Response) {
async onIceServers(res: Response) {
const iceServers = this.linkToIceServers(res.headers.get('Link'))
this.log('ice servers:', iceServers)
Expand All @@ -242,51 +245,57 @@ export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
this.video.srcObject = evt.streams[0]
}
this.pc.createOffer().then((offer) => this.onLocalOffer(offer))
const offer = await this.pc.createOffer()
await this.onLocalOffer(offer)
}
// eslint-disable-next-line no-undef
onLocalOffer(offer: RTCSessionDescriptionInit) {
this.offerData = this.parseOffer(offer.sdp ?? '')
this.pc?.setLocalDescription(offer)
fetch(this.url ?? '', {
method: 'POST',
headers: {
'Content-Type': 'application/sdp',
},
body: offer.sdp,
})
.then((res) => {
if (res.status !== 201) throw new Error('bad status code')
this.eTag = res.headers.get('ETag')
const location = res.headers.get('Location') ?? ''
this.sessionUuid = location?.substring(location.lastIndexOf('/') + 1) ?? null
// fallback for MediaMTX v1.0.x with broken ETag header
if (res.headers.has('E-Tag')) this.eTag = res.headers.get('E-Tag')
return res.text()
})
.then((sdp) => {
this.onRemoteAnswer(
new RTCSessionDescription({
type: 'answer',
sdp,
})
)
async onLocalOffer(offer: RTCSessionDescriptionInit) {
try {
const res = await fetch(this.url ?? '', {
method: 'POST',
headers: { 'Content-Type': 'application/sdp' },
body: offer.sdp,
})
.catch((err) => {
this.log(err)
if (res.status !== 201) {
this.log('error: Received bad status code:', res.status)
this.scheduleRestart()
})
return
}
this.offerData = this.parseOffer(offer.sdp ?? '')
this.pc?.setLocalDescription(offer)
this.eTag = res.headers.get('ETag')
const location = res.headers.get('Location') ?? ''
this.sessionUuid = location?.substring(location.lastIndexOf('/') + 1) ?? null
// fallback for MediaMTX v1.0.x with broken ETag header
if (res.headers.has('E-Tag')) this.eTag = res.headers.get('E-Tag')
const sdp = await res.text()
this.onRemoteAnswer(
new RTCSessionDescription({
type: 'answer',
sdp,
})
)
} catch (err: any) {
this.log(err?.message ?? err ?? 'unknown error')
this.scheduleRestart()
}
}
onRemoteAnswer(answer: RTCSessionDescription) {
if (this.restartTimeout !== null) return
// this.pc.setRemoteDescription(new RTCSessionDescription(answer));
this.pc?.setRemoteDescription(answer)
try {
this.pc?.setRemoteDescription(answer)
} catch (err: any) {
this.log(err)
this.scheduleRestart()
}
if (this.queuedCandidates.length !== 0) {
this.sendLocalCandidates(this.queuedCandidates)
Expand Down Expand Up @@ -319,7 +328,7 @@ export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
}
}
sendLocalCandidates(candidates: RTCIceCandidate[]) {
async sendLocalCandidates(candidates: RTCIceCandidate[]) {
if (this.sessionUuid === null) {
this.log('Session-UUID is null')
this.scheduleRestart()
Expand All @@ -328,29 +337,31 @@ export default class WebrtcMediaMTX extends Mixins(BaseMixin, WebcamMixin) {
}
const url = (this.url ?? '') + '/' + this.sessionUuid
fetch(url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/trickle-ice-sdpfrag',
'If-Match': this.eTag,
// eslint-disable-next-line no-undef
} as HeadersInit,
body: this.generateSdpFragment(this.offerData, candidates),
})
.then((res) => {
switch (res.status) {
case 204:
break
case 404:
throw new Error('stream not found')
default:
throw new Error(`bad status code ${res.status}`)
}
try {
const res = await fetch(url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/trickle-ice-sdpfrag',
'If-Match': this.eTag,
// eslint-disable-next-line no-undef
} as HeadersInit,
body: this.generateSdpFragment(this.offerData, candidates),
})
.catch((err) => {
this.log(err)
if (res.status === 204) return
if (res.status === 404) {
this.log('stream not found')
this.scheduleRestart()
})
return
}
this.log(`bad status code ${res.status}`)
this.scheduleRestart()
} catch (err: any) {
this.log(err)
this.scheduleRestart()
}
}
terminate() {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Webcam.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div>
<v-row>
<v-col col-12>
<webcam-panel current-page="page"></webcam-panel>
<webcam-panel current-page="page" />
</v-col>
</v-row>
</div>
Expand Down

0 comments on commit 85c5774

Please sign in to comment.