From d75dda0c055b66bde600e9fa428d76c072bdc51f Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Mon, 4 Nov 2024 10:38:27 +0100 Subject: [PATCH] Use RTCIceCandidate instead of str for candidate (#129793) --- homeassistant/components/camera/__init__.py | 6 ++++-- homeassistant/components/camera/webrtc.py | 19 +++++++++++++---- homeassistant/components/go2rtc/__init__.py | 9 +++++--- tests/components/camera/test_init.py | 3 ++- tests/components/camera/test_webrtc.py | 23 ++++++++++++++------- tests/components/go2rtc/test_init.py | 7 ++++--- 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 58826eb07ce..1feb7dffd3b 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -20,7 +20,7 @@ from aiohttp import hdrs, web import attr from propcache import cached_property, under_cached_property import voluptuous as vol -from webrtc_models import RTCIceServer +from webrtc_models import RTCIceCandidate, RTCIceServer from homeassistant.components import websocket_api from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView @@ -840,7 +840,9 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): return config - async def async_on_webrtc_candidate(self, session_id: str, candidate: str) -> None: + async def async_on_webrtc_candidate( + self, session_id: str, candidate: RTCIceCandidate + ) -> None: """Handle a WebRTC candidate.""" if self._webrtc_provider: await self._webrtc_provider.async_on_webrtc_candidate(session_id, candidate) diff --git a/homeassistant/components/camera/webrtc.py b/homeassistant/components/camera/webrtc.py index aca2b8291f1..0612c96e40c 100644 --- a/homeassistant/components/camera/webrtc.py +++ b/homeassistant/components/camera/webrtc.py @@ -11,7 +11,7 @@ import logging from typing import TYPE_CHECKING, Any, Protocol import voluptuous as vol -from webrtc_models import RTCConfiguration, RTCIceServer +from webrtc_models import RTCConfiguration, RTCIceCandidate, RTCIceServer from homeassistant.components import websocket_api from homeassistant.core import HomeAssistant, callback @@ -78,7 +78,14 @@ class WebRTCAnswer(WebRTCMessage): class WebRTCCandidate(WebRTCMessage): """WebRTC candidate.""" - candidate: str + candidate: RTCIceCandidate + + def as_dict(self) -> dict[str, Any]: + """Return a dict representation of the message.""" + return { + "type": self._get_type(), + "candidate": self.candidate.candidate, + } @dataclass(frozen=True) @@ -138,7 +145,9 @@ class CameraWebRTCProvider(ABC): """Handle the WebRTC offer and return the answer via the provided callback.""" @abstractmethod - async def async_on_webrtc_candidate(self, session_id: str, candidate: str) -> None: + async def async_on_webrtc_candidate( + self, session_id: str, candidate: RTCIceCandidate + ) -> None: """Handle the WebRTC candidate.""" @callback @@ -319,7 +328,9 @@ async def ws_candidate( ) return - await camera.async_on_webrtc_candidate(msg["session_id"], msg["candidate"]) + await camera.async_on_webrtc_candidate( + msg["session_id"], RTCIceCandidate(msg["candidate"]) + ) connection.send_message(websocket_api.result_message(msg["id"])) diff --git a/homeassistant/components/go2rtc/__init__.py b/homeassistant/components/go2rtc/__init__.py index 0bf01490a47..eeaa35fbbb4 100644 --- a/homeassistant/components/go2rtc/__init__.py +++ b/homeassistant/components/go2rtc/__init__.py @@ -15,6 +15,7 @@ from go2rtc_client.ws import ( WsError, ) import voluptuous as vol +from webrtc_models import RTCIceCandidate from homeassistant.components.camera import ( Camera, @@ -219,7 +220,7 @@ class WebRTCProvider(CameraWebRTCProvider): value: WebRTCMessage match message: case WebRTCCandidate(): - value = HAWebRTCCandidate(message.candidate) + value = HAWebRTCCandidate(RTCIceCandidate(message.candidate)) case WebRTCAnswer(): value = HAWebRTCAnswer(message.sdp) case WsError(): @@ -231,11 +232,13 @@ class WebRTCProvider(CameraWebRTCProvider): config = camera.async_get_webrtc_client_configuration() await ws_client.send(WebRTCOffer(offer_sdp, config.configuration.ice_servers)) - async def async_on_webrtc_candidate(self, session_id: str, candidate: str) -> None: + async def async_on_webrtc_candidate( + self, session_id: str, candidate: RTCIceCandidate + ) -> None: """Handle the WebRTC candidate.""" if ws_client := self._sessions.get(session_id): - await ws_client.send(WebRTCCandidate(candidate)) + await ws_client.send(WebRTCCandidate(candidate.candidate)) else: _LOGGER.debug("Unknown session %s. Ignoring candidate", session_id) diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index e0d4e38fb57..e7279f60848 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -7,6 +7,7 @@ from unittest.mock import ANY, AsyncMock, Mock, PropertyMock, mock_open, patch import pytest from syrupy.assertion import SnapshotAssertion +from webrtc_models import RTCIceCandidate from homeassistant.components import camera from homeassistant.components.camera import ( @@ -960,7 +961,7 @@ async def _test_capabilities( send_message(WebRTCAnswer("answer")) async def async_on_webrtc_candidate( - self, session_id: str, candidate: str + self, session_id: str, candidate: RTCIceCandidate ) -> None: """Handle the WebRTC candidate.""" diff --git a/tests/components/camera/test_webrtc.py b/tests/components/camera/test_webrtc.py index ec096b5f37a..27c50848ebf 100644 --- a/tests/components/camera/test_webrtc.py +++ b/tests/components/camera/test_webrtc.py @@ -6,6 +6,7 @@ from typing import Any from unittest.mock import AsyncMock, Mock, patch import pytest +from webrtc_models import RTCIceCandidate, RTCIceServer from homeassistant.components.camera import ( DATA_ICE_SERVERS, @@ -13,7 +14,6 @@ from homeassistant.components.camera import ( Camera, CameraEntityFeature, CameraWebRTCProvider, - RTCIceServer, StreamType, WebRTCAnswer, WebRTCCandidate, @@ -81,7 +81,9 @@ class SomeTestProvider(CameraWebRTCProvider): """ send_message(WebRTCAnswer(answer="answer")) - async def async_on_webrtc_candidate(self, session_id: str, candidate: str) -> None: + async def async_on_webrtc_candidate( + self, session_id: str, candidate: RTCIceCandidate + ) -> None: """Handle the WebRTC candidate.""" @callback @@ -503,7 +505,10 @@ async def test_websocket_webrtc_offer( @pytest.mark.parametrize( ("message", "expected_frontend_message"), [ - (WebRTCCandidate("candidate"), {"type": "candidate", "candidate": "candidate"}), + ( + WebRTCCandidate(RTCIceCandidate("candidate")), + {"type": "candidate", "candidate": "candidate"}, + ), ( WebRTCError("webrtc_offer_failed", "error"), {"type": "error", "code": "webrtc_offer_failed", "message": "error"}, @@ -989,7 +994,9 @@ async def test_ws_webrtc_candidate( response = await client.receive_json() assert response["type"] == TYPE_RESULT assert response["success"] - mock_on_webrtc_candidate.assert_called_once_with(session_id, candidate) + mock_on_webrtc_candidate.assert_called_once_with( + session_id, RTCIceCandidate(candidate) + ) @pytest.mark.usefixtures("mock_camera_webrtc") @@ -1039,7 +1046,9 @@ async def test_ws_webrtc_candidate_webrtc_provider( response = await client.receive_json() assert response["type"] == TYPE_RESULT assert response["success"] - mock_on_webrtc_candidate.assert_called_once_with(session_id, candidate) + mock_on_webrtc_candidate.assert_called_once_with( + session_id, RTCIceCandidate(candidate) + ) @pytest.mark.usefixtures("mock_camera_webrtc") @@ -1140,7 +1149,7 @@ async def test_webrtc_provider_optional_interface(hass: HomeAssistant) -> None: send_message(WebRTCAnswer(answer="answer")) async def async_on_webrtc_candidate( - self, session_id: str, candidate: str + self, session_id: str, candidate: RTCIceCandidate ) -> None: """Handle the WebRTC candidate.""" @@ -1150,7 +1159,7 @@ async def test_webrtc_provider_optional_interface(hass: HomeAssistant) -> None: await provider.async_handle_async_webrtc_offer( Mock(), "offer_sdp", "session_id", Mock() ) - await provider.async_on_webrtc_candidate("session_id", "candidate") + await provider.async_on_webrtc_candidate("session_id", RTCIceCandidate("candidate")) provider.async_close_session("session_id") diff --git a/tests/components/go2rtc/test_init.py b/tests/components/go2rtc/test_init.py index c4a23731a93..1e73525fbe3 100644 --- a/tests/components/go2rtc/test_init.py +++ b/tests/components/go2rtc/test_init.py @@ -17,6 +17,7 @@ from go2rtc_client.ws import ( WsError, ) import pytest +from webrtc_models import RTCIceCandidate from homeassistant.components.camera import ( DOMAIN as CAMERA_DOMAIN, @@ -379,7 +380,7 @@ async def message_callbacks( [ ( WebRTCCandidate("candidate"), - HAWebRTCCandidate("candidate"), + HAWebRTCCandidate(RTCIceCandidate("candidate")), ), ( WebRTCAnswer(ANSWER_SDP), @@ -415,7 +416,7 @@ async def test_on_candidate( session_id = "session_id" # Session doesn't exist - await camera.async_on_webrtc_candidate(session_id, "candidate") + await camera.async_on_webrtc_candidate(session_id, RTCIceCandidate("candidate")) assert ( "homeassistant.components.go2rtc", logging.DEBUG, @@ -435,7 +436,7 @@ async def test_on_candidate( ) ws_client.reset_mock() - await camera.async_on_webrtc_candidate(session_id, "candidate") + await camera.async_on_webrtc_candidate(session_id, RTCIceCandidate("candidate")) ws_client.send.assert_called_once_with(WebRTCCandidate("candidate")) assert caplog.record_tuples == []