1 """The go2rtc component."""
6 from aiohttp.client_exceptions
import ClientConnectionError, ServerConnectionError
7 from awesomeversion
import AwesomeVersion
8 from go2rtc_client
import Go2RtcRestClient
9 from go2rtc_client.exceptions
import Go2RtcClientError, Go2RtcVersionError
10 from go2rtc_client.ws
import (
18 import voluptuous
as vol
19 from webrtc_models
import RTCIceCandidateInit
24 WebRTCAnswer
as HAWebRTCAnswer,
25 WebRTCCandidate
as HAWebRTCCandidate,
29 async_register_webrtc_provider,
37 config_validation
as cv,
53 from .server
import Server
55 _LOGGER = logging.getLogger(__name__)
57 _SUPPORTED_STREAMS = frozenset(
87 CONFIG_SCHEMA = vol.Schema(
91 vol.Exclusive(CONF_URL, DOMAIN, DEBUG_UI_URL_MESSAGE): cv.url,
92 vol.Exclusive(CONF_DEBUG_UI, DOMAIN, DEBUG_UI_URL_MESSAGE): cv.boolean,
96 extra=vol.ALLOW_EXTRA,
99 _DATA_GO2RTC: HassKey[str] =
HassKey(DOMAIN)
100 _RETRYABLE_ERRORS = (ClientConnectionError, ServerConnectionError)
103 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
105 url: str |
None =
None
106 if DOMAIN
not in config
and DEFAULT_CONFIG_DOMAIN
not in config:
110 if not (configured_by_user := DOMAIN
in config)
or not (
111 url := config[DOMAIN].
get(CONF_URL)
114 if not configured_by_user:
118 _LOGGER.warning(
"Go2rtc URL required in non-docker installs")
121 _LOGGER.error(
"Could not find go2rtc docker binary")
126 hass, binary, enable_ui=config.get(DOMAIN, {}).
get(CONF_DEBUG_UI,
False)
131 _LOGGER.warning(
"Could not start go2rtc server", exc_info=
True)
134 async
def on_stop(event: Event) ->
None:
137 hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, on_stop)
141 hass.data[_DATA_GO2RTC] = url
142 discovery_flow.async_create_flow(
143 hass, DOMAIN, context={
"source": SOURCE_SYSTEM}, data={}
149 """Remove go2rtc config entries, if any."""
150 for entry
in hass.config_entries.async_entries(DOMAIN):
151 await hass.config_entries.async_remove(entry.entry_id)
155 """Set up go2rtc from a config entry."""
156 url = hass.data[_DATA_GO2RTC]
161 version = await client.validate_server_version()
162 if version < AwesomeVersion(RECOMMENDED_VERSION):
163 ir.async_create_issue(
166 "recommended_version",
169 severity=ir.IssueSeverity.WARNING,
170 translation_key=
"recommended_version",
171 translation_placeholders={
172 "recommended_version": RECOMMENDED_VERSION,
173 "current_version":
str(version),
176 except Go2RtcClientError
as err:
177 if isinstance(err.__cause__, _RETRYABLE_ERRORS):
179 f
"Could not connect to go2rtc instance on {url}"
181 _LOGGER.warning(
"Could not connect to go2rtc instance on %s (%s)", url, err)
183 except Go2RtcVersionError
as err:
185 f
"The go2rtc server version is not supported, {err}"
187 except Exception
as err:
188 _LOGGER.warning(
"Could not connect to go2rtc instance on %s (%s)", url, err)
197 """Unload a go2rtc config entry."""
202 """Return the binary path if found."""
203 return await hass.async_add_executor_job(shutil.which,
"go2rtc")
207 """WebRTC provider."""
209 def __init__(self, hass: HomeAssistant, url: str) ->
None:
210 """Initialize the WebRTC provider."""
215 self._sessions: dict[str, Go2RtcWsClient] = {}
219 """Return the integration domain of the provider."""
224 """Return if this provider is supports the Camera as source."""
225 return stream_source.partition(
":")[0]
in _SUPPORTED_STREAMS
232 send_message: WebRTCSendMessage,
234 """Handle the WebRTC offer and return the answer via the provided callback."""
235 self._sessions[session_id] = ws_client = Go2RtcWsClient(
236 self.
_session_session, self.
_url_url, source=camera.entity_id
239 if not (stream_source := await camera.stream_source()):
241 WebRTCError(
"go2rtc_webrtc_offer_failed",
"Camera has no stream source")
245 streams = await self.
_rest_client_rest_client.streams.list()
247 if (stream := streams.get(camera.entity_id))
is None or not any(
248 stream_source == producer.url
for producer
in stream.producers
257 f
"ffmpeg:{camera.entity_id}#audio=opus#query=log_level=debug",
262 def on_messages(message: ReceiveMessages) ->
None:
263 """Handle messages."""
267 value = HAWebRTCCandidate(RTCIceCandidateInit(message.candidate))
269 value = HAWebRTCAnswer(message.sdp)
271 value = WebRTCError(
"go2rtc_webrtc_offer_failed", message.error)
275 ws_client.subscribe(on_messages)
276 config = camera.async_get_webrtc_client_configuration()
277 await ws_client.send(WebRTCOffer(offer_sdp, config.configuration.ice_servers))
280 self, session_id: str, candidate: RTCIceCandidateInit
282 """Handle the WebRTC candidate."""
284 if ws_client := self._sessions.
get(session_id):
287 _LOGGER.debug(
"Unknown session %s. Ignoring candidate", session_id)
291 """Close the session."""
292 ws_client = self._sessions.pop(session_id)
293 self.
_hass_hass.async_create_task(ws_client.close())
None __init__(self, HomeAssistant hass, str url)
None async_on_webrtc_candidate(self, str session_id, RTCIceCandidateInit candidate)
None async_close_session(self, str session_id)
bool async_is_supported(self, str stream_source)
None async_handle_async_webrtc_offer(self, Camera camera, str offer_sdp, str session_id, WebRTCSendMessage send_message)
Callable[[], None] async_register_webrtc_provider(HomeAssistant hass, CameraWebRTCProvider provider)
web.Response get(self, web.Request request, str config_key)
str|None _get_binary(HomeAssistant hass)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
None _remove_go2rtc_entries(HomeAssistant hass)
bool async_setup(HomeAssistant hass, ConfigType config)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)