Skip to content

Commit

Permalink
Merge pull request #784 from felipecrs/webrtc-native
Browse files Browse the repository at this point in the history
Add back option to use Frigate-native WebRTC support
  • Loading branch information
dermotduffy authored Nov 27, 2024
2 parents e4fa487 + 0158825 commit 9c4f785
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 15 deletions.
2 changes: 0 additions & 2 deletions custom_components/frigate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
ATTR_WS_EVENT_PROXY,
ATTRIBUTE_LABELS,
CONF_CAMERA_STATIC_IMAGE_HEIGHT,
CONF_ENABLE_WEBRTC,
CONF_RTMP_URL_TEMPLATE,
DOMAIN,
FRIGATE_RELEASES_URL,
Expand Down Expand Up @@ -268,7 +267,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Remove old options.
OLD_OPTIONS = [
CONF_CAMERA_STATIC_IMAGE_HEIGHT,
CONF_ENABLE_WEBRTC,
CONF_RTMP_URL_TEMPLATE,
]
if any(option in entry.options for option in OLD_OPTIONS):
Expand Down
78 changes: 71 additions & 7 deletions custom_components/frigate/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
from yarl import URL

from custom_components.frigate.api import FrigateApiClient
from homeassistant.components.camera import Camera, CameraEntityFeature
from homeassistant.components.camera import (
Camera,
CameraEntityFeature,
StreamType,
WebRTCAnswer,
WebRTCSendMessage,
)
from homeassistant.components.mqtt import async_publish
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_URL
Expand Down Expand Up @@ -44,6 +50,7 @@
ATTR_PTZ_ACTION,
ATTR_PTZ_ARGUMENT,
ATTR_START_TIME,
CONF_ENABLE_WEBRTC,
CONF_RTSP_URL_TEMPLATE,
DEVICE_CLASS_CAMERA,
DOMAIN,
Expand All @@ -67,9 +74,13 @@ async def async_setup_entry(
client_id = get_frigate_instance_id_for_config_entry(hass, entry)
coordinator = hass.data[DOMAIN][entry.entry_id][ATTR_COORDINATOR]

frigate_webrtc = entry.options.get(CONF_ENABLE_WEBRTC, False)
camera_type = FrigateCameraWebRTC if frigate_webrtc else FrigateCamera
birdseye_type = BirdseyeCameraWebRTC if frigate_webrtc else BirdseyeCamera

async_add_entities(
[
FrigateCamera(
camera_type(
entry,
cam_name,
frigate_client,
Expand All @@ -81,7 +92,7 @@ async def async_setup_entry(
for cam_name, camera_config in frigate_config["cameras"].items()
]
+ (
[BirdseyeCamera(entry, frigate_client)]
[birdseye_type(entry, frigate_client)]
if frigate_config.get("birdseye", {}).get("restream", False)
else []
)
Expand Down Expand Up @@ -336,7 +347,7 @@ async def ptz(self, action: str, argument: str) -> None:


class BirdseyeCamera(FrigateEntity, Camera):
"""Representation of the Frigate birdseye camera."""
"""A Frigate birdseye camera."""

# sets the entity name to same as device name ex: camera.front_doorbell
_attr_name = None
Expand All @@ -348,6 +359,7 @@ def __init__(
) -> None:
"""Initialize the birdseye camera."""
self._client = frigate_client
self._cam_name = "birdseye"
FrigateEntity.__init__(self, config_entry)
Camera.__init__(self)
self._url = config_entry.data[CONF_URL]
Expand All @@ -368,10 +380,10 @@ def __init__(
# template instead. This means templates cannot access HomeAssistant
# state, but rather only the camera config.
self._stream_source = Template(streaming_template).render(
{"name": "birdseye"}
{"name": self._cam_name}
)
else:
self._stream_source = f"rtsp://{URL(self._url).host}:8554/birdseye"
self._stream_source = f"rtsp://{URL(self._url).host}:8554/{self._cam_name}"

@property
def unique_id(self) -> str:
Expand Down Expand Up @@ -409,7 +421,7 @@ async def async_camera_image(

image_url = str(
URL(self._url)
/ "api/birdseye/latest.jpg"
/ f"api/{self._cam_name}/latest.jpg"
% ({"h": height} if height is not None and height > 0 else {})
)

Expand All @@ -420,3 +432,55 @@ async def async_camera_image(
async def stream_source(self) -> str | None:
"""Return the source of the stream."""
return self._stream_source


class FrigateCameraWebRTC(FrigateCamera):
"""A Frigate camera with WebRTC support."""

# TODO: this property can be removed after this fix is released:
# https://github.com/home-assistant/core/pull/130932/files#diff-75655c0eec1c3e736cad1bdb5627100a4595ece9accc391b5c85343bb998594fR598-R603
@property
def frontend_stream_type(self) -> StreamType | None:
"""Return the type of stream supported by this camera."""
return StreamType.WEB_RTC

async def async_handle_async_webrtc_offer(
self, offer_sdp: str, session_id: str, send_message: WebRTCSendMessage
) -> None:
"""Handle the WebRTC offer and return an answer."""
websession = async_get_clientsession(self.hass)
url = f"{self._url}/api/go2rtc/webrtc?src={self._cam_name}"
payload = {"type": "offer", "sdp": offer_sdp}
async with websession.post(url, json=payload) as resp:
answer = await resp.json()
send_message(WebRTCAnswer(answer["sdp"]))

async def async_on_webrtc_candidate(self, session_id: str, candidate: Any) -> None:
"""Ignore WebRTC candidates for Frigate cameras."""
return


class BirdseyeCameraWebRTC(BirdseyeCamera):
"""A Frigate birdseye camera with WebRTC support."""

# TODO: this property can be removed after this fix is released:
# https://github.com/home-assistant/core/pull/130932/files#diff-75655c0eec1c3e736cad1bdb5627100a4595ece9accc391b5c85343bb998594fR598-R603
@property
def frontend_stream_type(self) -> StreamType | None:
"""Return the type of stream supported by this camera."""
return StreamType.WEB_RTC

async def async_handle_async_webrtc_offer(
self, offer_sdp: str, session_id: str, send_message: WebRTCSendMessage
) -> None:
"""Handle the WebRTC offer and return an answer."""
websession = async_get_clientsession(self.hass)
url = f"{self._url}/api/go2rtc/webrtc?src={self._cam_name}"
payload = {"type": "offer", "sdp": offer_sdp}
async with websession.post(url, json=payload) as resp:
answer = await resp.json()
send_message(WebRTCAnswer(answer["sdp"]))

async def async_on_webrtc_candidate(self, session_id: str, candidate: Any) -> None:
"""Ignore WebRTC candidates for Frigate cameras."""
return
9 changes: 9 additions & 0 deletions custom_components/frigate/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from .api import FrigateApiClient, FrigateApiClientError
from .const import (
CONF_ENABLE_WEBRTC,
CONF_MEDIA_BROWSER_ENABLE,
CONF_NOTIFICATION_PROXY_ENABLE,
CONF_NOTIFICATION_PROXY_EXPIRE_AFTER_SECONDS,
Expand Down Expand Up @@ -122,6 +123,14 @@ async def async_step_init(
return self.async_abort(reason="only_advanced_options")

schema: dict[Any, Any] = {
# Whether to enable Frigate-native WebRTC for camera streaming
vol.Optional(
CONF_ENABLE_WEBRTC,
default=self._config_entry.options.get(
CONF_ENABLE_WEBRTC,
False,
),
): bool,
# The input URL is not validated as being a URL to allow for the
# possibility the template input won't be a valid URL until after
# it's rendered.
Expand Down
3 changes: 2 additions & 1 deletion custom_components/frigate/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"step": {
"init": {
"data": {
"enable_webrtc": "Use Frigate-native WebRTC support",
"rtsp_url_template": "RTSP URL template (see documentation)",
"media_browser_enable": "Enable the media browser",
"notification_proxy_enable": "Enable the unauthenticated notification event proxy",
Expand All @@ -31,4 +32,4 @@
"only_advanced_options": "Advanced mode is disabled and there are only advanced options"
}
}
}
}
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ aiohttp
aiohttp_cors
attr
janus
homeassistant==2024.10.4
homeassistant==2024.11.3
paho-mqtt
python-dateutil
yarl
Expand Down
2 changes: 1 addition & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ flake8
mypy
pre-commit
pytest
pytest-homeassistant-custom-component==0.13.175
pytest-homeassistant-custom-component==0.13.184
pylint-pytest
pylint
pytest-aiohttp
Expand Down
Loading

0 comments on commit 9c4f785

Please sign in to comment.