mirror of
https://github.com/home-assistant/core.git
synced 2025-11-25 02:28:03 +00:00
Add go2rtc and extend camera integration for better WebRTC support (#124410)
This commit is contained in:
236
tests/components/camera/test_webrtc.py
Normal file
236
tests/components/camera/test_webrtc.py
Normal file
@@ -0,0 +1,236 @@
|
||||
"""Test camera WebRTC."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.components.camera.const import StreamType
|
||||
from homeassistant.components.camera.helper import get_camera_from_entity_id
|
||||
from homeassistant.components.camera.webrtc import (
|
||||
DATA_ICE_SERVERS,
|
||||
CameraWebRTCProvider,
|
||||
RTCIceServer,
|
||||
async_register_webrtc_provider,
|
||||
register_ice_server,
|
||||
)
|
||||
from homeassistant.components.websocket_api import TYPE_RESULT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_camera", "mock_stream", "mock_stream_source")
|
||||
async def test_async_register_webrtc_provider(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test registering a WebRTC provider."""
|
||||
await async_setup_component(hass, "camera", {})
|
||||
|
||||
camera = get_camera_from_entity_id(hass, "camera.demo_camera")
|
||||
assert camera.frontend_stream_type is StreamType.HLS
|
||||
|
||||
stream_supported = True
|
||||
|
||||
class TestProvider(CameraWebRTCProvider):
|
||||
"""Test provider."""
|
||||
|
||||
async def async_is_supported(self, stream_source: str) -> bool:
|
||||
"""Determine if the provider supports the stream source."""
|
||||
nonlocal stream_supported
|
||||
return stream_supported
|
||||
|
||||
async def async_handle_web_rtc_offer(
|
||||
self, camera: Camera, offer_sdp: str
|
||||
) -> str | None:
|
||||
"""Handle the WebRTC offer and return an answer."""
|
||||
return "answer"
|
||||
|
||||
unregister = async_register_webrtc_provider(hass, TestProvider())
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert camera.frontend_stream_type is StreamType.WEB_RTC
|
||||
|
||||
# Mark stream as unsupported
|
||||
stream_supported = False
|
||||
# Manually refresh the provider
|
||||
await camera.async_refresh_providers()
|
||||
|
||||
assert camera.frontend_stream_type is StreamType.HLS
|
||||
|
||||
# Mark stream as unsupported
|
||||
stream_supported = True
|
||||
# Manually refresh the provider
|
||||
await camera.async_refresh_providers()
|
||||
assert camera.frontend_stream_type is StreamType.WEB_RTC
|
||||
|
||||
unregister()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert camera.frontend_stream_type is StreamType.HLS
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_camera", "mock_stream", "mock_stream_source")
|
||||
async def test_async_register_webrtc_provider_twice(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test registering a WebRTC provider twice should raise."""
|
||||
await async_setup_component(hass, "camera", {})
|
||||
|
||||
class TestProvider(CameraWebRTCProvider):
|
||||
"""Test provider."""
|
||||
|
||||
async def async_is_supported(self, stream_source: str) -> bool:
|
||||
"""Determine if the provider supports the stream source."""
|
||||
return True
|
||||
|
||||
async def async_handle_web_rtc_offer(
|
||||
self, camera: Camera, offer_sdp: str
|
||||
) -> str | None:
|
||||
"""Handle the WebRTC offer and return an answer."""
|
||||
return "answer"
|
||||
|
||||
provider = TestProvider()
|
||||
async_register_webrtc_provider(hass, provider)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with pytest.raises(ValueError, match="Provider already registered"):
|
||||
async_register_webrtc_provider(hass, provider)
|
||||
|
||||
|
||||
async def test_async_register_webrtc_provider_camera_not_loaded(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test registering a WebRTC provider when camera is not loaded."""
|
||||
|
||||
class TestProvider(CameraWebRTCProvider):
|
||||
"""Test provider."""
|
||||
|
||||
async def async_is_supported(self, stream_source: str) -> bool:
|
||||
"""Determine if the provider supports the stream source."""
|
||||
return True
|
||||
|
||||
async def async_handle_web_rtc_offer(
|
||||
self, camera: Camera, offer_sdp: str
|
||||
) -> str | None:
|
||||
"""Handle the WebRTC offer and return an answer."""
|
||||
return "answer"
|
||||
|
||||
with pytest.raises(ValueError, match="Unexpected state, camera not loaded"):
|
||||
async_register_webrtc_provider(hass, TestProvider())
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_camera", "mock_stream", "mock_stream_source")
|
||||
async def test_async_register_ice_server(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test registering an ICE server."""
|
||||
await async_setup_component(hass, "camera", {})
|
||||
|
||||
# Clear any existing ICE servers
|
||||
hass.data[DATA_ICE_SERVERS].clear()
|
||||
|
||||
called = 0
|
||||
|
||||
async def get_ice_server() -> RTCIceServer:
|
||||
nonlocal called
|
||||
called += 1
|
||||
return RTCIceServer(urls="stun:example.com")
|
||||
|
||||
unregister = register_ice_server(hass, get_ice_server)
|
||||
assert not called
|
||||
|
||||
camera = get_camera_from_entity_id(hass, "camera.demo_camera")
|
||||
config = await camera.async_get_webrtc_client_configuration()
|
||||
|
||||
assert config.configuration.ice_servers == [RTCIceServer(urls="stun:example.com")]
|
||||
assert called == 1
|
||||
|
||||
# register another ICE server
|
||||
called_2 = 0
|
||||
|
||||
async def get_ice_server_2() -> RTCIceServer:
|
||||
nonlocal called_2
|
||||
called_2 += 1
|
||||
return RTCIceServer(
|
||||
urls=["stun:example2.com", "turn:example2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
)
|
||||
|
||||
unregister_2 = register_ice_server(hass, get_ice_server_2)
|
||||
|
||||
config = await camera.async_get_webrtc_client_configuration()
|
||||
assert config.configuration.ice_servers == [
|
||||
RTCIceServer(urls="stun:example.com"),
|
||||
RTCIceServer(
|
||||
urls=["stun:example2.com", "turn:example2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
),
|
||||
]
|
||||
assert called == 2
|
||||
assert called_2 == 1
|
||||
|
||||
# unregister the first ICE server
|
||||
|
||||
unregister()
|
||||
|
||||
config = await camera.async_get_webrtc_client_configuration()
|
||||
assert config.configuration.ice_servers == [
|
||||
RTCIceServer(
|
||||
urls=["stun:example2.com", "turn:example2.com"],
|
||||
username="user",
|
||||
credential="pass",
|
||||
),
|
||||
]
|
||||
assert called == 2
|
||||
assert called_2 == 2
|
||||
|
||||
# unregister the second ICE server
|
||||
unregister_2()
|
||||
|
||||
config = await camera.async_get_webrtc_client_configuration()
|
||||
assert config.configuration.ice_servers == []
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_camera_web_rtc")
|
||||
async def test_ws_get_client_config(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test get WebRTC client config."""
|
||||
await async_setup_component(hass, "camera", {})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json_auto_id(
|
||||
{"type": "camera/webrtc/get_client_config", "entity_id": "camera.demo_camera"}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
|
||||
# Assert WebSocket response
|
||||
assert msg["type"] == TYPE_RESULT
|
||||
assert msg["success"]
|
||||
assert msg["result"] == {
|
||||
"configuration": {"iceServers": [{"urls": "stun:stun.l.google.com:19302"}]}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_camera_hls")
|
||||
async def test_ws_get_client_config_no_rtc_camera(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test get WebRTC client config."""
|
||||
await async_setup_component(hass, "camera", {})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json_auto_id(
|
||||
{"type": "camera/webrtc/get_client_config", "entity_id": "camera.demo_camera"}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
|
||||
# Assert WebSocket response
|
||||
assert msg["type"] == TYPE_RESULT
|
||||
assert not msg["success"]
|
||||
assert msg["error"] == {
|
||||
"code": "web_rtc_offer_failed",
|
||||
"message": "Camera does not support WebRTC, frontend_stream_type=hls",
|
||||
}
|
||||
Reference in New Issue
Block a user