From c77e5dee84e4c09bbb41e8dcb8fd63b96b7178c7 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 3 Oct 2022 18:10:57 -0700 Subject: [PATCH] Allow remote WebRTC playback using STUN server from RTSPtoWeb integration (#13942) Co-authored-by: Bram Kragten --- src/components/ha-web-rtc-player.ts | 37 +++++++++++++++++++++++++++-- src/data/rtsp_to_webrtc.ts | 10 ++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/data/rtsp_to_webrtc.ts diff --git a/src/components/ha-web-rtc-player.ts b/src/components/ha-web-rtc-player.ts index 3987ff918c..0e7d31bc3a 100644 --- a/src/components/ha-web-rtc-player.ts +++ b/src/components/ha-web-rtc-player.ts @@ -7,7 +7,9 @@ import { TemplateResult, } from "lit"; import { customElement, property, state, query } from "lit/decorators"; +import { isComponentLoaded } from "../common/config/is_component_loaded"; import { handleWebRtcOffer, WebRtcAnswer } from "../data/camera"; +import { fetchWebRtcSettings } from "../data/rtsp_to_webrtc"; import type { HomeAssistant } from "../types"; import "./ha-alert"; @@ -86,7 +88,8 @@ class HaWebRtcPlayer extends LitElement { private async _startWebRtc(): Promise { this._error = undefined; - const peerConnection = new RTCPeerConnection(); + const configuration = await this._fetchPeerConfiguration(); + const peerConnection = new RTCPeerConnection(configuration); // Some cameras (such as nest) require a data channel to establish a stream // however, not used by any integrations. peerConnection.createDataChannel("dataSendChannel"); @@ -102,12 +105,25 @@ class HaWebRtcPlayer extends LitElement { ); await peerConnection.setLocalDescription(offer); + let candidates = ""; // Build an Offer SDP string with ice candidates + const iceResolver = new Promise((resolve) => { + peerConnection.addEventListener("icecandidate", async (event) => { + if (!event.candidate) { + resolve(); // Gathering complete + return; + } + candidates += `a=${event.candidate.candidate}\r\n`; + }); + }); + await iceResolver; + const offer_sdp = offer.sdp! + candidates; + let webRtcAnswer: WebRtcAnswer; try { webRtcAnswer = await handleWebRtcOffer( this.hass, this.entityid, - offer.sdp! + offer_sdp ); } catch (err: any) { this._error = "Failed to start WebRTC stream: " + err.message; @@ -138,6 +154,23 @@ class HaWebRtcPlayer extends LitElement { this._peerConnection = peerConnection; } + private async _fetchPeerConfiguration(): Promise { + if (!isComponentLoaded(this.hass!, "rtsp_to_webrtc")) { + return {}; + } + const settings = await fetchWebRtcSettings(this.hass!); + if (!settings || !settings.stun_server) { + return {}; + } + return { + iceServers: [ + { + urls: [`stun:${settings.stun_server!}`], + }, + ], + }; + } + private _cleanUp() { if (this._remoteStream) { this._remoteStream.getTracks().forEach((track) => { diff --git a/src/data/rtsp_to_webrtc.ts b/src/data/rtsp_to_webrtc.ts new file mode 100644 index 0000000000..777d7147e4 --- /dev/null +++ b/src/data/rtsp_to_webrtc.ts @@ -0,0 +1,10 @@ +import { HomeAssistant } from "../types"; + +export interface WebRtcSettings { + stun_server?: string; +} + +export const fetchWebRtcSettings = async (hass: HomeAssistant) => + hass.callWS({ + type: "rtsp_to_webrtc/get_settings", + });