Use RTCIceCandidate instead of str for candidate (#129793)

This commit is contained in:
Robert Resch 2024-11-04 10:38:27 +01:00 committed by GitHub
parent 0c40fcdaeb
commit d75dda0c05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 47 additions and 20 deletions

View File

@ -20,7 +20,7 @@ from aiohttp import hdrs, web
import attr import attr
from propcache import cached_property, under_cached_property from propcache import cached_property, under_cached_property
import voluptuous as vol import voluptuous as vol
from webrtc_models import RTCIceServer from webrtc_models import RTCIceCandidate, RTCIceServer
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
@ -840,7 +840,9 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
return config 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.""" """Handle a WebRTC candidate."""
if self._webrtc_provider: if self._webrtc_provider:
await self._webrtc_provider.async_on_webrtc_candidate(session_id, candidate) await self._webrtc_provider.async_on_webrtc_candidate(session_id, candidate)

View File

@ -11,7 +11,7 @@ import logging
from typing import TYPE_CHECKING, Any, Protocol from typing import TYPE_CHECKING, Any, Protocol
import voluptuous as vol 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.components import websocket_api
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
@ -78,7 +78,14 @@ class WebRTCAnswer(WebRTCMessage):
class WebRTCCandidate(WebRTCMessage): class WebRTCCandidate(WebRTCMessage):
"""WebRTC candidate.""" """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) @dataclass(frozen=True)
@ -138,7 +145,9 @@ class CameraWebRTCProvider(ABC):
"""Handle the WebRTC offer and return the answer via the provided callback.""" """Handle the WebRTC offer and return the answer via the provided callback."""
@abstractmethod @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.""" """Handle the WebRTC candidate."""
@callback @callback
@ -319,7 +328,9 @@ async def ws_candidate(
) )
return 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"])) connection.send_message(websocket_api.result_message(msg["id"]))

View File

@ -15,6 +15,7 @@ from go2rtc_client.ws import (
WsError, WsError,
) )
import voluptuous as vol import voluptuous as vol
from webrtc_models import RTCIceCandidate
from homeassistant.components.camera import ( from homeassistant.components.camera import (
Camera, Camera,
@ -219,7 +220,7 @@ class WebRTCProvider(CameraWebRTCProvider):
value: WebRTCMessage value: WebRTCMessage
match message: match message:
case WebRTCCandidate(): case WebRTCCandidate():
value = HAWebRTCCandidate(message.candidate) value = HAWebRTCCandidate(RTCIceCandidate(message.candidate))
case WebRTCAnswer(): case WebRTCAnswer():
value = HAWebRTCAnswer(message.sdp) value = HAWebRTCAnswer(message.sdp)
case WsError(): case WsError():
@ -231,11 +232,13 @@ class WebRTCProvider(CameraWebRTCProvider):
config = camera.async_get_webrtc_client_configuration() config = camera.async_get_webrtc_client_configuration()
await ws_client.send(WebRTCOffer(offer_sdp, config.configuration.ice_servers)) 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.""" """Handle the WebRTC candidate."""
if ws_client := self._sessions.get(session_id): if ws_client := self._sessions.get(session_id):
await ws_client.send(WebRTCCandidate(candidate)) await ws_client.send(WebRTCCandidate(candidate.candidate))
else: else:
_LOGGER.debug("Unknown session %s. Ignoring candidate", session_id) _LOGGER.debug("Unknown session %s. Ignoring candidate", session_id)

View File

@ -7,6 +7,7 @@ from unittest.mock import ANY, AsyncMock, Mock, PropertyMock, mock_open, patch
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from webrtc_models import RTCIceCandidate
from homeassistant.components import camera from homeassistant.components import camera
from homeassistant.components.camera import ( from homeassistant.components.camera import (
@ -960,7 +961,7 @@ async def _test_capabilities(
send_message(WebRTCAnswer("answer")) send_message(WebRTCAnswer("answer"))
async def async_on_webrtc_candidate( async def async_on_webrtc_candidate(
self, session_id: str, candidate: str self, session_id: str, candidate: RTCIceCandidate
) -> None: ) -> None:
"""Handle the WebRTC candidate.""" """Handle the WebRTC candidate."""

View File

@ -6,6 +6,7 @@ from typing import Any
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, patch
import pytest import pytest
from webrtc_models import RTCIceCandidate, RTCIceServer
from homeassistant.components.camera import ( from homeassistant.components.camera import (
DATA_ICE_SERVERS, DATA_ICE_SERVERS,
@ -13,7 +14,6 @@ from homeassistant.components.camera import (
Camera, Camera,
CameraEntityFeature, CameraEntityFeature,
CameraWebRTCProvider, CameraWebRTCProvider,
RTCIceServer,
StreamType, StreamType,
WebRTCAnswer, WebRTCAnswer,
WebRTCCandidate, WebRTCCandidate,
@ -81,7 +81,9 @@ class SomeTestProvider(CameraWebRTCProvider):
""" """
send_message(WebRTCAnswer(answer="answer")) 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.""" """Handle the WebRTC candidate."""
@callback @callback
@ -503,7 +505,10 @@ async def test_websocket_webrtc_offer(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("message", "expected_frontend_message"), ("message", "expected_frontend_message"),
[ [
(WebRTCCandidate("candidate"), {"type": "candidate", "candidate": "candidate"}), (
WebRTCCandidate(RTCIceCandidate("candidate")),
{"type": "candidate", "candidate": "candidate"},
),
( (
WebRTCError("webrtc_offer_failed", "error"), WebRTCError("webrtc_offer_failed", "error"),
{"type": "error", "code": "webrtc_offer_failed", "message": "error"}, {"type": "error", "code": "webrtc_offer_failed", "message": "error"},
@ -989,7 +994,9 @@ async def test_ws_webrtc_candidate(
response = await client.receive_json() response = await client.receive_json()
assert response["type"] == TYPE_RESULT assert response["type"] == TYPE_RESULT
assert response["success"] 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") @pytest.mark.usefixtures("mock_camera_webrtc")
@ -1039,7 +1046,9 @@ async def test_ws_webrtc_candidate_webrtc_provider(
response = await client.receive_json() response = await client.receive_json()
assert response["type"] == TYPE_RESULT assert response["type"] == TYPE_RESULT
assert response["success"] 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") @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")) send_message(WebRTCAnswer(answer="answer"))
async def async_on_webrtc_candidate( async def async_on_webrtc_candidate(
self, session_id: str, candidate: str self, session_id: str, candidate: RTCIceCandidate
) -> None: ) -> None:
"""Handle the WebRTC candidate.""" """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( await provider.async_handle_async_webrtc_offer(
Mock(), "offer_sdp", "session_id", Mock() 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") provider.async_close_session("session_id")

View File

@ -17,6 +17,7 @@ from go2rtc_client.ws import (
WsError, WsError,
) )
import pytest import pytest
from webrtc_models import RTCIceCandidate
from homeassistant.components.camera import ( from homeassistant.components.camera import (
DOMAIN as CAMERA_DOMAIN, DOMAIN as CAMERA_DOMAIN,
@ -379,7 +380,7 @@ async def message_callbacks(
[ [
( (
WebRTCCandidate("candidate"), WebRTCCandidate("candidate"),
HAWebRTCCandidate("candidate"), HAWebRTCCandidate(RTCIceCandidate("candidate")),
), ),
( (
WebRTCAnswer(ANSWER_SDP), WebRTCAnswer(ANSWER_SDP),
@ -415,7 +416,7 @@ async def test_on_candidate(
session_id = "session_id" session_id = "session_id"
# Session doesn't exist # Session doesn't exist
await camera.async_on_webrtc_candidate(session_id, "candidate") await camera.async_on_webrtc_candidate(session_id, RTCIceCandidate("candidate"))
assert ( assert (
"homeassistant.components.go2rtc", "homeassistant.components.go2rtc",
logging.DEBUG, logging.DEBUG,
@ -435,7 +436,7 @@ async def test_on_candidate(
) )
ws_client.reset_mock() 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")) ws_client.send.assert_called_once_with(WebRTCCandidate("candidate"))
assert caplog.record_tuples == [] assert caplog.record_tuples == []