Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 3 additions & 22 deletions homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import attr
from propcache.api import cached_property, under_cached_property
import voluptuous as vol
from webrtc_models import RTCIceCandidateInit, RTCIceServer
from webrtc_models import RTCIceCandidateInit

from homeassistant.components import websocket_api
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
Expand All @@ -37,6 +37,7 @@
Stream,
create_stream,
)
from homeassistant.components.web_rtc import async_get_ice_servers
from homeassistant.components.websocket_api import ActiveConnection
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
Expand Down Expand Up @@ -84,7 +85,6 @@
get_dynamic_camera_stream_settings,
)
from .webrtc import (
DATA_ICE_SERVERS,
CameraWebRTCProvider,
WebRTCAnswer, # noqa: F401
WebRTCCandidate, # noqa: F401
Expand All @@ -93,7 +93,6 @@
WebRTCMessage, # noqa: F401
WebRTCSendMessage,
async_get_supported_provider,
async_register_ice_servers,
async_register_webrtc_provider, # noqa: F401
async_register_ws,
)
Expand Down Expand Up @@ -400,20 +399,6 @@ def unsub_track_time_interval(_event: Event) -> None:
SERVICE_RECORD, CAMERA_SERVICE_RECORD, async_handle_record_service
)

@callback
def get_ice_servers() -> list[RTCIceServer]:
if hass.config.webrtc.ice_servers:
return hass.config.webrtc.ice_servers
return [
RTCIceServer(
urls=[
"stun:stun.home-assistant.io:3478",
"stun:stun.home-assistant.io:80",
]
),
]

async_register_ice_servers(hass, get_ice_servers)
return True


Expand Down Expand Up @@ -731,11 +716,7 @@ def async_get_webrtc_client_configuration(self) -> WebRTCClientConfiguration:
"""Return the WebRTC client configuration and extend it with the registered ice servers."""
config = self._async_get_webrtc_client_configuration()

ice_servers = [
server
for servers in self.hass.data.get(DATA_ICE_SERVERS, [])
for server in servers()
]
ice_servers = async_get_ice_servers(self.hass)
config.configuration.ice_servers.extend(ice_servers)

return config
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/camera/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Camera",
"after_dependencies": ["media_player"],
"codeowners": ["@home-assistant/core"],
"dependencies": ["http"],
"dependencies": ["http", "web_rtc"],
"documentation": "https://www.home-assistant.io/integrations/camera",
"integration_type": "entity",
"quality_scale": "internal",
Expand Down
30 changes: 2 additions & 28 deletions homeassistant/components/camera/webrtc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@

from abc import ABC, abstractmethod
import asyncio
from collections.abc import Awaitable, Callable, Iterable
from collections.abc import Awaitable, Callable
from dataclasses import asdict, dataclass, field
from functools import cache, partial, wraps
import logging
from typing import TYPE_CHECKING, Any

from mashumaro import MissingField
import voluptuous as vol
from webrtc_models import (
RTCConfiguration,
RTCIceCandidate,
RTCIceCandidateInit,
RTCIceServer,
)
from webrtc_models import RTCConfiguration, RTCIceCandidate, RTCIceCandidateInit

from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant, callback
Expand All @@ -38,9 +33,6 @@
DATA_WEBRTC_PROVIDERS: HassKey[set[CameraWebRTCProvider]] = HassKey(
"camera_webrtc_providers"
)
DATA_ICE_SERVERS: HassKey[list[Callable[[], Iterable[RTCIceServer]]]] = HassKey(
"camera_webrtc_ice_servers"
)


_WEBRTC = "WebRTC"
Expand Down Expand Up @@ -367,21 +359,3 @@ async def async_get_supported_provider(
return provider

return None


@callback
def async_register_ice_servers(
hass: HomeAssistant,
get_ice_server_fn: Callable[[], Iterable[RTCIceServer]],
) -> Callable[[], None]:
"""Register a ICE server.

The registering integration is responsible to implement caching if needed.
"""
servers = hass.data.setdefault(DATA_ICE_SERVERS, [])

def remove() -> None:
servers.remove(get_ice_server_fn)

servers.append(get_ice_server_fn)
return remove
2 changes: 1 addition & 1 deletion homeassistant/components/cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
errors as alexa_errors,
smart_home as alexa_smart_home,
)
from homeassistant.components.camera import async_register_ice_servers
from homeassistant.components.google_assistant import smart_home as ga
from homeassistant.components.web_rtc import async_register_ice_servers
from homeassistant.const import __version__ as HA_VERSION
from homeassistant.core import Context, HassJob, HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/cloud/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"google_assistant"
],
"codeowners": ["@home-assistant/cloud"],
"dependencies": ["auth", "http", "repairs", "webhook"],
"dependencies": ["auth", "http", "repairs", "webhook", "web_rtc"],
"documentation": "https://www.home-assistant.io/integrations/cloud",
"integration_type": "system",
"iot_class": "cloud_push",
Expand Down
138 changes: 138 additions & 0 deletions homeassistant/components/web_rtc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""The WebRTC integration."""

from __future__ import annotations

from collections.abc import Callable, Iterable
from typing import Any

import voluptuous as vol
from webrtc_models import RTCIceServer

from homeassistant.components import websocket_api
from homeassistant.const import CONF_URL, CONF_USERNAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.core_config import (
CONF_CREDENTIAL,
CONF_ICE_SERVERS,
validate_stun_or_turn_url,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey

__all__ = [
"async_get_ice_servers",
"async_register_ice_servers",
]

DOMAIN = "web_rtc"

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_ICE_SERVERS): vol.All(
cv.ensure_list,
[
vol.Schema(
{
vol.Required(CONF_URL): vol.All(
cv.ensure_list, [validate_stun_or_turn_url]
),
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_CREDENTIAL): cv.string,
}
)
],
)
}
)
},
extra=vol.ALLOW_EXTRA,
)

DATA_ICE_SERVERS_USER: HassKey[Iterable[RTCIceServer]] = HassKey(
"web_rtc_ice_servers_user"
)
DATA_ICE_SERVERS: HassKey[list[Callable[[], Iterable[RTCIceServer]]]] = HassKey(
"web_rtc_ice_servers"
)
Comment thread
balloob marked this conversation as resolved.


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the WebRTC integration."""
servers = [
RTCIceServer(
server[CONF_URL],
server.get(CONF_USERNAME),
server.get(CONF_CREDENTIAL),
)
for server in config.get(DOMAIN, {}).get(CONF_ICE_SERVERS, [])
]
if servers:
hass.data[DATA_ICE_SERVERS_USER] = servers

hass.data[DATA_ICE_SERVERS] = []
websocket_api.async_register_command(hass, ws_ice_servers)
return True


@callback
def async_register_ice_servers(
hass: HomeAssistant,
get_ice_server_fn: Callable[[], Iterable[RTCIceServer]],
) -> Callable[[], None]:
"""Register an ICE server.

The registering integration is responsible to implement caching if needed.
"""
servers = hass.data[DATA_ICE_SERVERS]

def remove() -> None:
servers.remove(get_ice_server_fn)

servers.append(get_ice_server_fn)
return remove


@callback
def async_get_ice_servers(hass: HomeAssistant) -> list[RTCIceServer]:
"""Return all registered ICE servers."""
servers: list[RTCIceServer] = []

if hass.config.webrtc.ice_servers:
servers.extend(hass.config.webrtc.ice_servers)
Comment thread
edenhaus marked this conversation as resolved.

if DATA_ICE_SERVERS_USER in hass.data:
servers.extend(hass.data[DATA_ICE_SERVERS_USER])

if not servers:
servers = [
RTCIceServer(
urls=[
"stun:stun.home-assistant.io:3478",
"stun:stun.home-assistant.io:80",
]
),
]
Comment thread
edenhaus marked this conversation as resolved.

for gen_servers in hass.data[DATA_ICE_SERVERS]:
servers.extend(gen_servers())

return servers


@websocket_api.websocket_command(
{
"type": "web_rtc/ice_servers",
}
)
@callback
def ws_ice_servers(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Handle get WebRTC ICE servers websocket command."""
ice_servers = [server.to_dict() for server in async_get_ice_servers(hass)]
connection.send_result(msg["id"], ice_servers)
Comment thread
arturpragacz marked this conversation as resolved.
8 changes: 8 additions & 0 deletions homeassistant/components/web_rtc/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "web_rtc",
"name": "WebRTC",
"codeowners": ["@home-assistant/core"],
"documentation": "https://www.home-assistant.io/integrations/web_rtc",
"integration_type": "system",
"quality_scale": "internal"
}
4 changes: 2 additions & 2 deletions homeassistant/core_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def _validate_currency(data: Any) -> Any:
raise


def _validate_stun_or_turn_url(value: Any) -> str:
def validate_stun_or_turn_url(value: Any) -> str:
"""Validate an URL."""
url_in = str(value)
url = urlparse(url_in)
Expand Down Expand Up @@ -331,7 +331,7 @@ def _validate_stun_or_turn_url(value: Any) -> str:
vol.Schema(
{
vol.Required(CONF_URL): vol.All(
cv.ensure_list, [_validate_stun_or_turn_url]
cv.ensure_list, [validate_stun_or_turn_url]
),
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_CREDENTIAL): cv.string,
Expand Down
1 change: 1 addition & 0 deletions script/hassfest/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class NonScaledQualityScaleTiers(StrEnum):
"tag",
"timer",
"trace",
"web_rtc",
"webhook",
"websocket_api",
"zone",
Expand Down
1 change: 1 addition & 0 deletions script/hassfest/quality_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -2196,6 +2196,7 @@ class Rule:
"timer",
"trace",
"usage_prediction",
"web_rtc",
"webhook",
"websocket_api",
"zone",
Expand Down
Loading
Loading