mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 16:57:10 +00:00
Remove deprecated legacy WebRTC provider (#144547)
This commit is contained in:
parent
a93bf3c150
commit
1f84c5e1f1
@ -85,7 +85,6 @@ from .img_util import scale_jpeg_camera_image
|
|||||||
from .prefs import CameraPreferences, DynamicStreamSettings # noqa: F401
|
from .prefs import CameraPreferences, DynamicStreamSettings # noqa: F401
|
||||||
from .webrtc import (
|
from .webrtc import (
|
||||||
DATA_ICE_SERVERS,
|
DATA_ICE_SERVERS,
|
||||||
CameraWebRTCLegacyProvider,
|
|
||||||
CameraWebRTCProvider,
|
CameraWebRTCProvider,
|
||||||
WebRTCAnswer,
|
WebRTCAnswer,
|
||||||
WebRTCCandidate, # noqa: F401
|
WebRTCCandidate, # noqa: F401
|
||||||
@ -93,10 +92,8 @@ from .webrtc import (
|
|||||||
WebRTCError,
|
WebRTCError,
|
||||||
WebRTCMessage, # noqa: F401
|
WebRTCMessage, # noqa: F401
|
||||||
WebRTCSendMessage,
|
WebRTCSendMessage,
|
||||||
async_get_supported_legacy_provider,
|
|
||||||
async_get_supported_provider,
|
async_get_supported_provider,
|
||||||
async_register_ice_servers,
|
async_register_ice_servers,
|
||||||
async_register_rtsp_to_web_rtc_provider, # noqa: F401
|
|
||||||
async_register_webrtc_provider, # noqa: F401
|
async_register_webrtc_provider, # noqa: F401
|
||||||
async_register_ws,
|
async_register_ws,
|
||||||
)
|
)
|
||||||
@ -476,7 +473,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
self.async_update_token()
|
self.async_update_token()
|
||||||
self._create_stream_lock: asyncio.Lock | None = None
|
self._create_stream_lock: asyncio.Lock | None = None
|
||||||
self._webrtc_provider: CameraWebRTCProvider | None = None
|
self._webrtc_provider: CameraWebRTCProvider | None = None
|
||||||
self._legacy_webrtc_provider: CameraWebRTCLegacyProvider | None = None
|
|
||||||
self._supports_native_sync_webrtc = (
|
self._supports_native_sync_webrtc = (
|
||||||
type(self).async_handle_web_rtc_offer != Camera.async_handle_web_rtc_offer
|
type(self).async_handle_web_rtc_offer != Camera.async_handle_web_rtc_offer
|
||||||
)
|
)
|
||||||
@ -646,14 +642,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._legacy_webrtc_provider and (
|
raise HomeAssistantError("Camera does not support WebRTC")
|
||||||
answer := await self._legacy_webrtc_provider.async_handle_web_rtc_offer(
|
|
||||||
self, offer_sdp
|
|
||||||
)
|
|
||||||
):
|
|
||||||
send_message(WebRTCAnswer(answer))
|
|
||||||
else:
|
|
||||||
raise HomeAssistantError("Camera does not support WebRTC")
|
|
||||||
|
|
||||||
def camera_image(
|
def camera_image(
|
||||||
self, width: int | None = None, height: int | None = None
|
self, width: int | None = None, height: int | None = None
|
||||||
@ -772,9 +761,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
providers or inputs to the state attributes change.
|
providers or inputs to the state attributes change.
|
||||||
"""
|
"""
|
||||||
old_provider = self._webrtc_provider
|
old_provider = self._webrtc_provider
|
||||||
old_legacy_provider = self._legacy_webrtc_provider
|
|
||||||
new_provider = None
|
new_provider = None
|
||||||
new_legacy_provider = None
|
|
||||||
|
|
||||||
# Skip all providers if the camera has a native WebRTC implementation
|
# Skip all providers if the camera has a native WebRTC implementation
|
||||||
if not (
|
if not (
|
||||||
@ -785,15 +772,8 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
async_get_supported_provider
|
async_get_supported_provider
|
||||||
)
|
)
|
||||||
|
|
||||||
if new_provider is None:
|
if old_provider != new_provider:
|
||||||
# Only add the legacy provider if the new provider is not available
|
|
||||||
new_legacy_provider = await self._async_get_supported_webrtc_provider(
|
|
||||||
async_get_supported_legacy_provider
|
|
||||||
)
|
|
||||||
|
|
||||||
if old_provider != new_provider or old_legacy_provider != new_legacy_provider:
|
|
||||||
self._webrtc_provider = new_provider
|
self._webrtc_provider = new_provider
|
||||||
self._legacy_webrtc_provider = new_legacy_provider
|
|
||||||
self._invalidate_camera_capabilities_cache()
|
self._invalidate_camera_capabilities_cache()
|
||||||
if write_state:
|
if write_state:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
@ -828,10 +808,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
]
|
]
|
||||||
config.configuration.ice_servers.extend(ice_servers)
|
config.configuration.ice_servers.extend(ice_servers)
|
||||||
|
|
||||||
config.get_candidates_upfront = (
|
config.get_candidates_upfront = self._supports_native_sync_webrtc
|
||||||
self._supports_native_sync_webrtc
|
|
||||||
or self._legacy_webrtc_provider is not None
|
|
||||||
)
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@ -867,7 +844,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
else:
|
else:
|
||||||
frontend_stream_types.add(StreamType.HLS)
|
frontend_stream_types.add(StreamType.HLS)
|
||||||
|
|
||||||
if self._webrtc_provider or self._legacy_webrtc_provider:
|
if self._webrtc_provider:
|
||||||
frontend_stream_types.add(StreamType.WEB_RTC)
|
frontend_stream_types.add(StreamType.WEB_RTC)
|
||||||
|
|
||||||
return CameraCapabilities(frontend_stream_types)
|
return CameraCapabilities(frontend_stream_types)
|
||||||
|
@ -46,10 +46,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"legacy_webrtc_provider": {
|
|
||||||
"title": "Detected use of legacy WebRTC provider registered by {legacy_integration}",
|
|
||||||
"description": "The {legacy_integration} integration has registered a legacy WebRTC provider. Home Assistant prefers using the built-in modern WebRTC provider registered by the {builtin_integration} integration.\n\nBenefits of the built-in integration are:\n\n- The camera stream is started faster.\n- More camera devices are supported.\n\nTo fix this issue, you can either keep using the built-in modern WebRTC provider and remove the {legacy_integration} integration or remove the {builtin_integration} integration to use the legacy provider, and then restart Home Assistant."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
@ -8,7 +8,7 @@ from collections.abc import Awaitable, Callable, Iterable
|
|||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from functools import cache, partial, wraps
|
from functools import cache, partial, wraps
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any, Protocol
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from mashumaro import MissingField
|
from mashumaro import MissingField
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -22,8 +22,7 @@ from webrtc_models import (
|
|||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import config_validation as cv, issue_registry as ir
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.deprecation import deprecated_function
|
|
||||||
from homeassistant.util.hass_dict import HassKey
|
from homeassistant.util.hass_dict import HassKey
|
||||||
from homeassistant.util.ulid import ulid
|
from homeassistant.util.ulid import ulid
|
||||||
|
|
||||||
@ -39,9 +38,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
DATA_WEBRTC_PROVIDERS: HassKey[set[CameraWebRTCProvider]] = HassKey(
|
DATA_WEBRTC_PROVIDERS: HassKey[set[CameraWebRTCProvider]] = HassKey(
|
||||||
"camera_webrtc_providers"
|
"camera_webrtc_providers"
|
||||||
)
|
)
|
||||||
DATA_WEBRTC_LEGACY_PROVIDERS: HassKey[dict[str, CameraWebRTCLegacyProvider]] = HassKey(
|
|
||||||
"camera_webrtc_legacy_providers"
|
|
||||||
)
|
|
||||||
DATA_ICE_SERVERS: HassKey[list[Callable[[], Iterable[RTCIceServer]]]] = HassKey(
|
DATA_ICE_SERVERS: HassKey[list[Callable[[], Iterable[RTCIceServer]]]] = HassKey(
|
||||||
"camera_webrtc_ice_servers"
|
"camera_webrtc_ice_servers"
|
||||||
)
|
)
|
||||||
@ -163,18 +159,6 @@ class CameraWebRTCProvider(ABC):
|
|||||||
return ## This is an optional method so we need a default here.
|
return ## This is an optional method so we need a default here.
|
||||||
|
|
||||||
|
|
||||||
class CameraWebRTCLegacyProvider(Protocol):
|
|
||||||
"""WebRTC provider."""
|
|
||||||
|
|
||||||
async def async_is_supported(self, stream_source: str) -> bool:
|
|
||||||
"""Determine if the provider supports the stream source."""
|
|
||||||
|
|
||||||
async def async_handle_web_rtc_offer(
|
|
||||||
self, camera: Camera, offer_sdp: str
|
|
||||||
) -> str | None:
|
|
||||||
"""Handle the WebRTC offer and return an answer."""
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_register_webrtc_provider(
|
def async_register_webrtc_provider(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -204,8 +188,6 @@ def async_register_webrtc_provider(
|
|||||||
|
|
||||||
async def _async_refresh_providers(hass: HomeAssistant) -> None:
|
async def _async_refresh_providers(hass: HomeAssistant) -> None:
|
||||||
"""Check all cameras for any state changes for registered providers."""
|
"""Check all cameras for any state changes for registered providers."""
|
||||||
_async_check_conflicting_legacy_provider(hass)
|
|
||||||
|
|
||||||
component = hass.data[DATA_COMPONENT]
|
component = hass.data[DATA_COMPONENT]
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
*(camera.async_refresh_providers() for camera in component.entities)
|
*(camera.async_refresh_providers() for camera in component.entities)
|
||||||
@ -380,21 +362,6 @@ async def async_get_supported_provider(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def async_get_supported_legacy_provider(
|
|
||||||
hass: HomeAssistant, camera: Camera
|
|
||||||
) -> CameraWebRTCLegacyProvider | None:
|
|
||||||
"""Return the first supported provider for the camera."""
|
|
||||||
providers = hass.data.get(DATA_WEBRTC_LEGACY_PROVIDERS)
|
|
||||||
if not providers or not (stream_source := await camera.stream_source()):
|
|
||||||
return None
|
|
||||||
|
|
||||||
for provider in providers.values():
|
|
||||||
if await provider.async_is_supported(stream_source):
|
|
||||||
return provider
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_register_ice_servers(
|
def async_register_ice_servers(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -411,94 +378,3 @@ def async_register_ice_servers(
|
|||||||
|
|
||||||
servers.append(get_ice_server_fn)
|
servers.append(get_ice_server_fn)
|
||||||
return remove
|
return remove
|
||||||
|
|
||||||
|
|
||||||
# The following code is legacy code that was introduced with rtsp_to_webrtc and will be deprecated/removed in the future.
|
|
||||||
# Left it so custom integrations can still use it.
|
|
||||||
|
|
||||||
_RTSP_PREFIXES = {"rtsp://", "rtsps://", "rtmp://"}
|
|
||||||
|
|
||||||
# An RtspToWebRtcProvider accepts these inputs:
|
|
||||||
# stream_source: The RTSP url
|
|
||||||
# offer_sdp: The WebRTC SDP offer
|
|
||||||
# stream_id: A unique id for the stream, used to update an existing source
|
|
||||||
# The output is the SDP answer, or None if the source or offer is not eligible.
|
|
||||||
# The Callable may throw HomeAssistantError on failure.
|
|
||||||
type RtspToWebRtcProviderType = Callable[[str, str, str], Awaitable[str | None]]
|
|
||||||
|
|
||||||
|
|
||||||
class _CameraRtspToWebRTCProvider(CameraWebRTCLegacyProvider):
|
|
||||||
def __init__(self, fn: RtspToWebRtcProviderType) -> None:
|
|
||||||
"""Initialize the RTSP to WebRTC provider."""
|
|
||||||
self._fn = fn
|
|
||||||
|
|
||||||
async def async_is_supported(self, stream_source: str) -> bool:
|
|
||||||
"""Return if this provider is supports the Camera as source."""
|
|
||||||
return any(stream_source.startswith(prefix) for prefix in _RTSP_PREFIXES)
|
|
||||||
|
|
||||||
async def async_handle_web_rtc_offer(
|
|
||||||
self, camera: Camera, offer_sdp: str
|
|
||||||
) -> str | None:
|
|
||||||
"""Handle the WebRTC offer and return an answer."""
|
|
||||||
if not (stream_source := await camera.stream_source()):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return await self._fn(stream_source, offer_sdp, camera.entity_id)
|
|
||||||
|
|
||||||
|
|
||||||
@deprecated_function("async_register_webrtc_provider", breaks_in_ha_version="2025.6")
|
|
||||||
def async_register_rtsp_to_web_rtc_provider(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
domain: str,
|
|
||||||
provider: RtspToWebRtcProviderType,
|
|
||||||
) -> Callable[[], None]:
|
|
||||||
"""Register an RTSP to WebRTC provider.
|
|
||||||
|
|
||||||
The first provider to satisfy the offer will be used.
|
|
||||||
"""
|
|
||||||
if DOMAIN not in hass.data:
|
|
||||||
raise ValueError("Unexpected state, camera not loaded")
|
|
||||||
|
|
||||||
legacy_providers = hass.data.setdefault(DATA_WEBRTC_LEGACY_PROVIDERS, {})
|
|
||||||
|
|
||||||
if domain in legacy_providers:
|
|
||||||
raise ValueError("Provider already registered")
|
|
||||||
|
|
||||||
provider_instance = _CameraRtspToWebRTCProvider(provider)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def remove_provider() -> None:
|
|
||||||
legacy_providers.pop(domain)
|
|
||||||
hass.async_create_task(_async_refresh_providers(hass))
|
|
||||||
|
|
||||||
legacy_providers[domain] = provider_instance
|
|
||||||
hass.async_create_task(_async_refresh_providers(hass))
|
|
||||||
|
|
||||||
return remove_provider
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def _async_check_conflicting_legacy_provider(hass: HomeAssistant) -> None:
|
|
||||||
"""Check if a legacy provider is registered together with the builtin provider."""
|
|
||||||
builtin_provider_domain = "go2rtc"
|
|
||||||
if (
|
|
||||||
(legacy_providers := hass.data.get(DATA_WEBRTC_LEGACY_PROVIDERS))
|
|
||||||
and (providers := hass.data.get(DATA_WEBRTC_PROVIDERS))
|
|
||||||
and any(provider.domain == builtin_provider_domain for provider in providers)
|
|
||||||
):
|
|
||||||
for domain in legacy_providers:
|
|
||||||
ir.async_create_issue(
|
|
||||||
hass,
|
|
||||||
DOMAIN,
|
|
||||||
f"legacy_webrtc_provider_{domain}",
|
|
||||||
is_fixable=False,
|
|
||||||
is_persistent=False,
|
|
||||||
issue_domain=domain,
|
|
||||||
learn_more_url="https://www.home-assistant.io/integrations/go2rtc/",
|
|
||||||
severity=ir.IssueSeverity.WARNING,
|
|
||||||
translation_key="legacy_webrtc_provider",
|
|
||||||
translation_placeholders={
|
|
||||||
"legacy_integration": domain,
|
|
||||||
"builtin_integration": builtin_provider_domain,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Test camera WebRTC."""
|
"""Test camera WebRTC."""
|
||||||
|
|
||||||
from collections.abc import AsyncGenerator, Generator
|
from collections.abc import AsyncGenerator
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
@ -20,9 +20,7 @@ from homeassistant.components.camera import (
|
|||||||
WebRTCError,
|
WebRTCError,
|
||||||
WebRTCMessage,
|
WebRTCMessage,
|
||||||
WebRTCSendMessage,
|
WebRTCSendMessage,
|
||||||
async_get_supported_legacy_provider,
|
|
||||||
async_register_ice_servers,
|
async_register_ice_servers,
|
||||||
async_register_rtsp_to_web_rtc_provider,
|
|
||||||
async_register_webrtc_provider,
|
async_register_webrtc_provider,
|
||||||
get_camera_from_entity_id,
|
get_camera_from_entity_id,
|
||||||
)
|
)
|
||||||
@ -31,7 +29,6 @@ from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.core_config import async_process_ha_core_config
|
from homeassistant.core_config import async_process_ha_core_config
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import issue_registry as ir
|
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .common import STREAM_SOURCE, WEBRTC_ANSWER, SomeTestProvider
|
from .common import STREAM_SOURCE, WEBRTC_ANSWER, SomeTestProvider
|
||||||
@ -427,21 +424,6 @@ async def provide_webrtc_answer(stream_source: str, offer: str, stream_id: str)
|
|||||||
return WEBRTC_ANSWER
|
return WEBRTC_ANSWER
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_rtsp_to_webrtc")
|
|
||||||
def mock_rtsp_to_webrtc_fixture(
|
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
||||||
) -> Generator[Mock]:
|
|
||||||
"""Fixture that registers a mock rtsp to webrtc provider."""
|
|
||||||
mock_provider = Mock(side_effect=provide_webrtc_answer)
|
|
||||||
unsub = async_register_rtsp_to_web_rtc_provider(hass, "mock_domain", mock_provider)
|
|
||||||
assert (
|
|
||||||
"async_register_rtsp_to_web_rtc_provider is a deprecated function which will"
|
|
||||||
" be removed in HA Core 2025.6. Use async_register_webrtc_provider instead"
|
|
||||||
) in caplog.text
|
|
||||||
yield mock_provider
|
|
||||||
unsub()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_test_webrtc_cameras")
|
@pytest.mark.usefixtures("mock_test_webrtc_cameras")
|
||||||
async def test_websocket_webrtc_offer(
|
async def test_websocket_webrtc_offer(
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||||
@ -804,45 +786,6 @@ async def test_websocket_webrtc_offer_invalid_stream_type(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera", "mock_stream_source")
|
|
||||||
async def test_rtsp_to_webrtc_offer(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
hass_ws_client: WebSocketGenerator,
|
|
||||||
mock_rtsp_to_webrtc: Mock,
|
|
||||||
) -> None:
|
|
||||||
"""Test creating a webrtc offer from an rstp provider."""
|
|
||||||
client = await hass_ws_client(hass)
|
|
||||||
await client.send_json_auto_id(
|
|
||||||
{
|
|
||||||
"type": "camera/webrtc/offer",
|
|
||||||
"entity_id": "camera.demo_camera",
|
|
||||||
"offer": WEBRTC_OFFER,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
|
|
||||||
assert response["type"] == TYPE_RESULT
|
|
||||||
assert response["success"]
|
|
||||||
subscription_id = response["id"]
|
|
||||||
|
|
||||||
# Session id
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["id"] == subscription_id
|
|
||||||
assert response["type"] == "event"
|
|
||||||
assert response["event"]["type"] == "session"
|
|
||||||
|
|
||||||
# Answer
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["id"] == subscription_id
|
|
||||||
assert response["type"] == "event"
|
|
||||||
assert response["event"] == {
|
|
||||||
"type": "answer",
|
|
||||||
"answer": WEBRTC_ANSWER,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert mock_rtsp_to_webrtc.called
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_hls_stream_source")
|
@pytest.fixture(name="mock_hls_stream_source")
|
||||||
async def mock_hls_stream_source_fixture() -> AsyncGenerator[AsyncMock]:
|
async def mock_hls_stream_source_fixture() -> AsyncGenerator[AsyncMock]:
|
||||||
"""Fixture to create an HLS stream source."""
|
"""Fixture to create an HLS stream source."""
|
||||||
@ -853,117 +796,6 @@ async def mock_hls_stream_source_fixture() -> AsyncGenerator[AsyncMock]:
|
|||||||
yield mock_hls_stream_source
|
yield mock_hls_stream_source
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera", "mock_stream_source")
|
|
||||||
async def test_rtsp_to_webrtc_provider_unregistered(
|
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
||||||
) -> None:
|
|
||||||
"""Test creating a webrtc offer from an rstp provider."""
|
|
||||||
mock_provider = Mock(side_effect=provide_webrtc_answer)
|
|
||||||
unsub = async_register_rtsp_to_web_rtc_provider(hass, "mock_domain", mock_provider)
|
|
||||||
|
|
||||||
client = await hass_ws_client(hass)
|
|
||||||
|
|
||||||
# Registered provider can handle the WebRTC offer
|
|
||||||
await client.send_json_auto_id(
|
|
||||||
{
|
|
||||||
"type": "camera/webrtc/offer",
|
|
||||||
"entity_id": "camera.demo_camera",
|
|
||||||
"offer": WEBRTC_OFFER,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["type"] == TYPE_RESULT
|
|
||||||
assert response["success"]
|
|
||||||
subscription_id = response["id"]
|
|
||||||
|
|
||||||
# Session id
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["id"] == subscription_id
|
|
||||||
assert response["type"] == "event"
|
|
||||||
assert response["event"]["type"] == "session"
|
|
||||||
|
|
||||||
# Answer
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["id"] == subscription_id
|
|
||||||
assert response["type"] == "event"
|
|
||||||
assert response["event"] == {
|
|
||||||
"type": "answer",
|
|
||||||
"answer": WEBRTC_ANSWER,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert mock_provider.called
|
|
||||||
mock_provider.reset_mock()
|
|
||||||
|
|
||||||
# Unregister provider, then verify the WebRTC offer cannot be handled
|
|
||||||
unsub()
|
|
||||||
await client.send_json_auto_id(
|
|
||||||
{
|
|
||||||
"type": "camera/webrtc/offer",
|
|
||||||
"entity_id": "camera.demo_camera",
|
|
||||||
"offer": WEBRTC_OFFER,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response.get("type") == TYPE_RESULT
|
|
||||||
assert not response["success"]
|
|
||||||
assert response["error"] == {
|
|
||||||
"code": "webrtc_offer_failed",
|
|
||||||
"message": "Camera does not support WebRTC, frontend_stream_types={<StreamType.HLS: 'hls'>}",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert not mock_provider.called
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera", "mock_stream_source")
|
|
||||||
async def test_rtsp_to_webrtc_offer_not_accepted(
|
|
||||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
|
||||||
) -> None:
|
|
||||||
"""Test a provider that can't satisfy the rtsp to webrtc offer."""
|
|
||||||
|
|
||||||
async def provide_none(
|
|
||||||
stream_source: str, offer: str, stream_id: str
|
|
||||||
) -> str | None:
|
|
||||||
"""Simulate a provider that can't accept the offer."""
|
|
||||||
return None
|
|
||||||
|
|
||||||
mock_provider = Mock(side_effect=provide_none)
|
|
||||||
unsub = async_register_rtsp_to_web_rtc_provider(hass, "mock_domain", mock_provider)
|
|
||||||
client = await hass_ws_client(hass)
|
|
||||||
|
|
||||||
# Registered provider can handle the WebRTC offer
|
|
||||||
await client.send_json_auto_id(
|
|
||||||
{
|
|
||||||
"type": "camera/webrtc/offer",
|
|
||||||
"entity_id": "camera.demo_camera",
|
|
||||||
"offer": WEBRTC_OFFER,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["type"] == TYPE_RESULT
|
|
||||||
assert response["success"]
|
|
||||||
subscription_id = response["id"]
|
|
||||||
|
|
||||||
# Session id
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["id"] == subscription_id
|
|
||||||
assert response["type"] == "event"
|
|
||||||
assert response["event"]["type"] == "session"
|
|
||||||
|
|
||||||
# Answer
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["id"] == subscription_id
|
|
||||||
assert response["type"] == "event"
|
|
||||||
assert response["event"] == {
|
|
||||||
"type": "error",
|
|
||||||
"code": "webrtc_offer_failed",
|
|
||||||
"message": "Camera does not support WebRTC",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert mock_provider.called
|
|
||||||
|
|
||||||
unsub()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("frontend_candidate", "expected_candidate"),
|
("frontend_candidate", "expected_candidate"),
|
||||||
[
|
[
|
||||||
@ -1224,79 +1056,3 @@ async def test_webrtc_provider_optional_interface(hass: HomeAssistant) -> None:
|
|||||||
"session_id", RTCIceCandidateInit("candidate")
|
"session_id", RTCIceCandidateInit("candidate")
|
||||||
)
|
)
|
||||||
provider.async_close_session("session_id")
|
provider.async_close_session("session_id")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera")
|
|
||||||
async def test_repair_issue_legacy_provider(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
) -> None:
|
|
||||||
"""Test repair issue created for legacy provider."""
|
|
||||||
# Ensure no issue if no provider is registered
|
|
||||||
assert not issue_registry.async_get_issue(
|
|
||||||
"camera", "legacy_webrtc_provider_mock_domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Register a legacy provider
|
|
||||||
legacy_provider = Mock(side_effect=provide_webrtc_answer)
|
|
||||||
unsub_legacy_provider = async_register_rtsp_to_web_rtc_provider(
|
|
||||||
hass, "mock_domain", legacy_provider
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
# Ensure no issue if only legacy provider is registered
|
|
||||||
assert not issue_registry.async_get_issue(
|
|
||||||
"camera", "legacy_webrtc_provider_mock_domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
provider = Go2RTCProvider()
|
|
||||||
unsub_go2rtc_provider = async_register_webrtc_provider(hass, provider)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
# Ensure issue when legacy and builtin provider are registered
|
|
||||||
issue = issue_registry.async_get_issue(
|
|
||||||
"camera", "legacy_webrtc_provider_mock_domain"
|
|
||||||
)
|
|
||||||
assert issue
|
|
||||||
assert issue.is_fixable is False
|
|
||||||
assert issue.is_persistent is False
|
|
||||||
assert issue.issue_domain == "mock_domain"
|
|
||||||
assert issue.learn_more_url == "https://www.home-assistant.io/integrations/go2rtc/"
|
|
||||||
assert issue.severity == ir.IssueSeverity.WARNING
|
|
||||||
assert issue.issue_id == "legacy_webrtc_provider_mock_domain"
|
|
||||||
assert issue.translation_key == "legacy_webrtc_provider"
|
|
||||||
assert issue.translation_placeholders == {
|
|
||||||
"legacy_integration": "mock_domain",
|
|
||||||
"builtin_integration": "go2rtc",
|
|
||||||
}
|
|
||||||
|
|
||||||
unsub_legacy_provider()
|
|
||||||
unsub_go2rtc_provider()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera", "register_test_provider", "mock_rtsp_to_webrtc")
|
|
||||||
async def test_no_repair_issue_without_new_provider(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
issue_registry: ir.IssueRegistry,
|
|
||||||
) -> None:
|
|
||||||
"""Test repair issue not created if no go2rtc provider exists."""
|
|
||||||
assert not issue_registry.async_get_issue(
|
|
||||||
"camera", "legacy_webrtc_provider_mock_domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_camera", "mock_rtsp_to_webrtc")
|
|
||||||
async def test_registering_same_legacy_provider(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
) -> None:
|
|
||||||
"""Test registering the same legacy provider twice."""
|
|
||||||
legacy_provider = Mock(side_effect=provide_webrtc_answer)
|
|
||||||
with pytest.raises(ValueError, match="Provider already registered"):
|
|
||||||
async_register_rtsp_to_web_rtc_provider(hass, "mock_domain", legacy_provider)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_hls_stream_source", "mock_camera", "mock_rtsp_to_webrtc")
|
|
||||||
async def test_get_not_supported_legacy_provider(hass: HomeAssistant) -> None:
|
|
||||||
"""Test getting a not supported legacy provider."""
|
|
||||||
camera = get_camera_from_entity_id(hass, "camera.demo_camera")
|
|
||||||
assert await async_get_supported_legacy_provider(hass, camera) is None
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user