Go2rtc bump and set ffmpeg logs to debug (#130371)
This commit is contained in:
parent
7758d8ba48
commit
cb9cc0f801
6 changed files with 51 additions and 268 deletions
|
@ -55,7 +55,7 @@ RUN \
|
||||||
"armv7") go2rtc_suffix='arm' ;; \
|
"armv7") go2rtc_suffix='arm' ;; \
|
||||||
*) go2rtc_suffix=${BUILD_ARCH} ;; \
|
*) go2rtc_suffix=${BUILD_ARCH} ;; \
|
||||||
esac \
|
esac \
|
||||||
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.6/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.7/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
||||||
&& chmod +x /bin/go2rtc \
|
&& chmod +x /bin/go2rtc \
|
||||||
# Verify go2rtc can be executed
|
# Verify go2rtc can be executed
|
||||||
&& go2rtc --version
|
&& go2rtc --version
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
"""The go2rtc component."""
|
"""The go2rtc component."""
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
@ -41,13 +38,7 @@ from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.util.hass_dict import HassKey
|
from homeassistant.util.hass_dict import HassKey
|
||||||
from homeassistant.util.package import is_docker_env
|
from homeassistant.util.package import is_docker_env
|
||||||
|
|
||||||
from .const import (
|
from .const import CONF_DEBUG_UI, DEBUG_UI_URL_MESSAGE, DOMAIN, HA_MANAGED_URL
|
||||||
CONF_DEBUG_UI,
|
|
||||||
DEBUG_UI_URL_MESSAGE,
|
|
||||||
DOMAIN,
|
|
||||||
HA_MANAGED_RTSP_PORT,
|
|
||||||
HA_MANAGED_URL,
|
|
||||||
)
|
|
||||||
from .server import Server
|
from .server import Server
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -94,22 +85,13 @@ CONFIG_SCHEMA = vol.Schema(
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
_DATA_GO2RTC: HassKey[Go2RtcData] = HassKey(DOMAIN)
|
_DATA_GO2RTC: HassKey[str] = HassKey(DOMAIN)
|
||||||
_RETRYABLE_ERRORS = (ClientConnectionError, ServerConnectionError)
|
_RETRYABLE_ERRORS = (ClientConnectionError, ServerConnectionError)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Go2RtcData:
|
|
||||||
"""Data for go2rtc."""
|
|
||||||
|
|
||||||
url: str
|
|
||||||
managed: bool
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up WebRTC."""
|
"""Set up WebRTC."""
|
||||||
url: str | None = None
|
url: str | None = None
|
||||||
managed = False
|
|
||||||
if DOMAIN not in config and DEFAULT_CONFIG_DOMAIN not in config:
|
if DOMAIN not in config and DEFAULT_CONFIG_DOMAIN not in config:
|
||||||
await _remove_go2rtc_entries(hass)
|
await _remove_go2rtc_entries(hass)
|
||||||
return True
|
return True
|
||||||
|
@ -144,9 +126,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, on_stop)
|
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, on_stop)
|
||||||
|
|
||||||
url = HA_MANAGED_URL
|
url = HA_MANAGED_URL
|
||||||
managed = True
|
|
||||||
|
|
||||||
hass.data[_DATA_GO2RTC] = Go2RtcData(url, managed)
|
hass.data[_DATA_GO2RTC] = url
|
||||||
discovery_flow.async_create_flow(
|
discovery_flow.async_create_flow(
|
||||||
hass, DOMAIN, context={"source": SOURCE_SYSTEM}, data={}
|
hass, DOMAIN, context={"source": SOURCE_SYSTEM}, data={}
|
||||||
)
|
)
|
||||||
|
@ -161,32 +142,28 @@ async def _remove_go2rtc_entries(hass: HomeAssistant) -> None:
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up go2rtc from a config entry."""
|
"""Set up go2rtc from a config entry."""
|
||||||
data = hass.data[_DATA_GO2RTC]
|
url = hass.data[_DATA_GO2RTC]
|
||||||
|
|
||||||
# Validate the server URL
|
# Validate the server URL
|
||||||
try:
|
try:
|
||||||
client = Go2RtcRestClient(async_get_clientsession(hass), data.url)
|
client = Go2RtcRestClient(async_get_clientsession(hass), url)
|
||||||
await client.validate_server_version()
|
await client.validate_server_version()
|
||||||
except Go2RtcClientError as err:
|
except Go2RtcClientError as err:
|
||||||
if isinstance(err.__cause__, _RETRYABLE_ERRORS):
|
if isinstance(err.__cause__, _RETRYABLE_ERRORS):
|
||||||
raise ConfigEntryNotReady(
|
raise ConfigEntryNotReady(
|
||||||
f"Could not connect to go2rtc instance on {data.url}"
|
f"Could not connect to go2rtc instance on {url}"
|
||||||
) from err
|
) from err
|
||||||
_LOGGER.warning(
|
_LOGGER.warning("Could not connect to go2rtc instance on %s (%s)", url, err)
|
||||||
"Could not connect to go2rtc instance on %s (%s)", data.url, err
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
except Go2RtcVersionError as err:
|
except Go2RtcVersionError as err:
|
||||||
raise ConfigEntryNotReady(
|
raise ConfigEntryNotReady(
|
||||||
f"The go2rtc server version is not supported, {err}"
|
f"The go2rtc server version is not supported, {err}"
|
||||||
) from err
|
) from err
|
||||||
except Exception as err: # noqa: BLE001
|
except Exception as err: # noqa: BLE001
|
||||||
_LOGGER.warning(
|
_LOGGER.warning("Could not connect to go2rtc instance on %s (%s)", url, err)
|
||||||
"Could not connect to go2rtc instance on %s (%s)", data.url, err
|
|
||||||
)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
provider = WebRTCProvider(hass, data)
|
provider = WebRTCProvider(hass, url)
|
||||||
async_register_webrtc_provider(hass, provider)
|
async_register_webrtc_provider(hass, provider)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -204,12 +181,12 @@ async def _get_binary(hass: HomeAssistant) -> str | None:
|
||||||
class WebRTCProvider(CameraWebRTCProvider):
|
class WebRTCProvider(CameraWebRTCProvider):
|
||||||
"""WebRTC provider."""
|
"""WebRTC provider."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, data: Go2RtcData) -> None:
|
def __init__(self, hass: HomeAssistant, url: str) -> None:
|
||||||
"""Initialize the WebRTC provider."""
|
"""Initialize the WebRTC provider."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
self._data = data
|
self._url = url
|
||||||
self._session = async_get_clientsession(hass)
|
self._session = async_get_clientsession(hass)
|
||||||
self._rest_client = Go2RtcRestClient(self._session, data.url)
|
self._rest_client = Go2RtcRestClient(self._session, url)
|
||||||
self._sessions: dict[str, Go2RtcWsClient] = {}
|
self._sessions: dict[str, Go2RtcWsClient] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -231,7 +208,7 @@ class WebRTCProvider(CameraWebRTCProvider):
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle the WebRTC offer and return the answer via the provided callback."""
|
"""Handle the WebRTC offer and return the answer via the provided callback."""
|
||||||
self._sessions[session_id] = ws_client = Go2RtcWsClient(
|
self._sessions[session_id] = ws_client = Go2RtcWsClient(
|
||||||
self._session, self._data.url, source=camera.entity_id
|
self._session, self._url, source=camera.entity_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if not (stream_source := await camera.stream_source()):
|
if not (stream_source := await camera.stream_source()):
|
||||||
|
@ -242,34 +219,18 @@ class WebRTCProvider(CameraWebRTCProvider):
|
||||||
|
|
||||||
streams = await self._rest_client.streams.list()
|
streams = await self._rest_client.streams.list()
|
||||||
|
|
||||||
if self._data.managed:
|
if (stream := streams.get(camera.entity_id)) is None or not any(
|
||||||
# HA manages the go2rtc instance
|
stream_source == producer.url for producer in stream.producers
|
||||||
stream_original_name = f"{camera.entity_id}_original"
|
|
||||||
stream_redirect_sources = [
|
|
||||||
f"rtsp://127.0.0.1:{HA_MANAGED_RTSP_PORT}/{stream_original_name}",
|
|
||||||
f"ffmpeg:{stream_original_name}#audio=opus",
|
|
||||||
]
|
|
||||||
|
|
||||||
if (
|
|
||||||
(stream_org := streams.get(stream_original_name)) is None
|
|
||||||
or not any(
|
|
||||||
stream_source == producer.url for producer in stream_org.producers
|
|
||||||
)
|
|
||||||
or (stream_redirect := streams.get(camera.entity_id)) is None
|
|
||||||
or stream_redirect_sources != [p.url for p in stream_redirect.producers]
|
|
||||||
):
|
|
||||||
await self._rest_client.streams.add(stream_original_name, stream_source)
|
|
||||||
await self._rest_client.streams.add(
|
|
||||||
camera.entity_id, stream_redirect_sources
|
|
||||||
)
|
|
||||||
|
|
||||||
# go2rtc instance is managed outside HA
|
|
||||||
elif (stream_org := streams.get(camera.entity_id)) is None or not any(
|
|
||||||
stream_source == producer.url for producer in stream_org.producers
|
|
||||||
):
|
):
|
||||||
await self._rest_client.streams.add(
|
await self._rest_client.streams.add(
|
||||||
camera.entity_id,
|
camera.entity_id,
|
||||||
[stream_source, f"ffmpeg:{camera.entity_id}#audio=opus"],
|
[
|
||||||
|
stream_source,
|
||||||
|
# We are setting any ffmpeg rtsp related logs to debug
|
||||||
|
# Connection problems to the camera will be logged by the first stream
|
||||||
|
# Therefore setting it to debug will not hide any important logs
|
||||||
|
f"ffmpeg:{camera.entity_id}#audio=opus#query=log_level=debug",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
|
|
@ -6,4 +6,3 @@ CONF_DEBUG_UI = "debug_ui"
|
||||||
DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time."
|
DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time."
|
||||||
HA_MANAGED_API_PORT = 11984
|
HA_MANAGED_API_PORT = 11984
|
||||||
HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/"
|
HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/"
|
||||||
HA_MANAGED_RTSP_PORT = 18554
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import HA_MANAGED_API_PORT, HA_MANAGED_RTSP_PORT, HA_MANAGED_URL
|
from .const import HA_MANAGED_API_PORT, HA_MANAGED_URL
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_TERMINATE_TIMEOUT = 5
|
_TERMINATE_TIMEOUT = 5
|
||||||
|
@ -33,7 +33,7 @@ api:
|
||||||
listen: "{api_ip}:{api_port}"
|
listen: "{api_ip}:{api_port}"
|
||||||
|
|
||||||
rtsp:
|
rtsp:
|
||||||
listen: "127.0.0.1:{rtsp_port}"
|
listen: "127.0.0.1:18554"
|
||||||
|
|
||||||
webrtc:
|
webrtc:
|
||||||
listen: ":18555/tcp"
|
listen: ":18555/tcp"
|
||||||
|
@ -68,9 +68,7 @@ def _create_temp_file(api_ip: str) -> str:
|
||||||
with NamedTemporaryFile(prefix="go2rtc_", suffix=".yaml", delete=False) as file:
|
with NamedTemporaryFile(prefix="go2rtc_", suffix=".yaml", delete=False) as file:
|
||||||
file.write(
|
file.write(
|
||||||
_GO2RTC_CONFIG_FORMAT.format(
|
_GO2RTC_CONFIG_FORMAT.format(
|
||||||
api_ip=api_ip,
|
api_ip=api_ip, api_port=HA_MANAGED_API_PORT
|
||||||
api_port=HA_MANAGED_API_PORT,
|
|
||||||
rtsp_port=HA_MANAGED_RTSP_PORT,
|
|
||||||
).encode()
|
).encode()
|
||||||
)
|
)
|
||||||
return file.name
|
return file.name
|
||||||
|
|
|
@ -112,7 +112,7 @@ LABEL "com.github.actions.icon"="terminal"
|
||||||
LABEL "com.github.actions.color"="gray-dark"
|
LABEL "com.github.actions.color"="gray-dark"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_GO2RTC_VERSION = "1.9.6"
|
_GO2RTC_VERSION = "1.9.7"
|
||||||
|
|
||||||
|
|
||||||
def _get_package_versions(file: Path, packages: set[str]) -> dict[str, str]:
|
def _get_package_versions(file: Path, packages: set[str]) -> dict[str, str]:
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
from collections.abc import Callable, Generator
|
from collections.abc import Callable, Generator
|
||||||
import logging
|
import logging
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
from unittest.mock import AsyncMock, Mock, call, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
||||||
from go2rtc_client import Stream
|
from go2rtc_client import Stream
|
||||||
|
@ -238,7 +238,11 @@ async def _test_setup_and_signaling(
|
||||||
await test()
|
await test()
|
||||||
|
|
||||||
rest_client.streams.add.assert_called_once_with(
|
rest_client.streams.add.assert_called_once_with(
|
||||||
entity_id, ["rtsp://stream", f"ffmpeg:{camera.entity_id}#audio=opus"]
|
entity_id,
|
||||||
|
[
|
||||||
|
"rtsp://stream",
|
||||||
|
f"ffmpeg:{camera.entity_id}#audio=opus#query=log_level=debug",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stream exists but the source is different
|
# Stream exists but the source is different
|
||||||
|
@ -252,7 +256,11 @@ async def _test_setup_and_signaling(
|
||||||
await test()
|
await test()
|
||||||
|
|
||||||
rest_client.streams.add.assert_called_once_with(
|
rest_client.streams.add.assert_called_once_with(
|
||||||
entity_id, ["rtsp://stream", f"ffmpeg:{camera.entity_id}#audio=opus"]
|
entity_id,
|
||||||
|
[
|
||||||
|
"rtsp://stream",
|
||||||
|
f"ffmpeg:{camera.entity_id}#audio=opus#query=log_level=debug",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the stream is already added, the stream should not be added again.
|
# If the stream is already added, the stream should not be added again.
|
||||||
|
@ -296,7 +304,7 @@ async def _test_setup_and_signaling(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
|
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
|
||||||
async def test_setup_managed(
|
async def test_setup_go_binary(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
rest_client: AsyncMock,
|
rest_client: AsyncMock,
|
||||||
ws_client: Mock,
|
ws_client: Mock,
|
||||||
|
@ -308,131 +316,15 @@ async def test_setup_managed(
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
ui_enabled: bool,
|
ui_enabled: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the go2rtc setup with managed go2rtc instance."""
|
"""Test the go2rtc config entry with binary."""
|
||||||
assert (len(hass.config_entries.async_entries(DOMAIN)) == 1) == has_go2rtc_entry
|
assert (len(hass.config_entries.async_entries(DOMAIN)) == 1) == has_go2rtc_entry
|
||||||
camera = init_test_integration
|
|
||||||
|
|
||||||
entity_id = camera.entity_id
|
def after_setup() -> None:
|
||||||
stream_name_original = f"{camera.entity_id}_original"
|
server.assert_called_once_with(hass, "/usr/bin/go2rtc", enable_ui=ui_enabled)
|
||||||
assert camera.frontend_stream_type == StreamType.HLS
|
server_start.assert_called_once()
|
||||||
|
|
||||||
assert await async_setup_component(hass, DOMAIN, config)
|
await _test_setup_and_signaling(
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
hass, rest_client, ws_client, config, after_setup, init_test_integration
|
||||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
assert len(config_entries) == 1
|
|
||||||
assert config_entries[0].state == ConfigEntryState.LOADED
|
|
||||||
server.assert_called_once_with(hass, "/usr/bin/go2rtc", enable_ui=ui_enabled)
|
|
||||||
server_start.assert_called_once()
|
|
||||||
|
|
||||||
receive_message_callback = Mock(spec_set=WebRTCSendMessage)
|
|
||||||
|
|
||||||
async def test() -> None:
|
|
||||||
await camera.async_handle_async_webrtc_offer(
|
|
||||||
OFFER_SDP, "session_id", receive_message_callback
|
|
||||||
)
|
|
||||||
ws_client.send.assert_called_once_with(
|
|
||||||
WebRTCOffer(
|
|
||||||
OFFER_SDP,
|
|
||||||
camera.async_get_webrtc_client_configuration().configuration.ice_servers,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
ws_client.subscribe.assert_called_once()
|
|
||||||
|
|
||||||
# Simulate the answer from the go2rtc server
|
|
||||||
callback = ws_client.subscribe.call_args[0][0]
|
|
||||||
callback(WebRTCAnswer(ANSWER_SDP))
|
|
||||||
receive_message_callback.assert_called_once_with(HAWebRTCAnswer(ANSWER_SDP))
|
|
||||||
|
|
||||||
await test()
|
|
||||||
|
|
||||||
stream_added_calls = [
|
|
||||||
call(stream_name_original, "rtsp://stream"),
|
|
||||||
call(
|
|
||||||
entity_id,
|
|
||||||
[
|
|
||||||
f"rtsp://127.0.0.1:18554/{stream_name_original}",
|
|
||||||
f"ffmpeg:{stream_name_original}#audio=opus",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
assert rest_client.streams.add.call_args_list == stream_added_calls
|
|
||||||
|
|
||||||
# Stream original missing
|
|
||||||
rest_client.streams.add.reset_mock()
|
|
||||||
rest_client.streams.list.return_value = {
|
|
||||||
entity_id: Stream(
|
|
||||||
[
|
|
||||||
Producer(f"rtsp://127.0.0.1:18554/{stream_name_original}"),
|
|
||||||
Producer(f"ffmpeg:{stream_name_original}#audio=opus"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
receive_message_callback.reset_mock()
|
|
||||||
ws_client.reset_mock()
|
|
||||||
await test()
|
|
||||||
|
|
||||||
assert rest_client.streams.add.call_args_list == stream_added_calls
|
|
||||||
|
|
||||||
# Stream original source different
|
|
||||||
rest_client.streams.add.reset_mock()
|
|
||||||
rest_client.streams.list.return_value = {
|
|
||||||
stream_name_original: Stream([Producer("rtsp://different")]),
|
|
||||||
entity_id: Stream(
|
|
||||||
[
|
|
||||||
Producer(f"rtsp://127.0.0.1:18554/{stream_name_original}"),
|
|
||||||
Producer(f"ffmpeg:{stream_name_original}#audio=opus"),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
receive_message_callback.reset_mock()
|
|
||||||
ws_client.reset_mock()
|
|
||||||
await test()
|
|
||||||
|
|
||||||
assert rest_client.streams.add.call_args_list == stream_added_calls
|
|
||||||
|
|
||||||
# Stream source different
|
|
||||||
rest_client.streams.add.reset_mock()
|
|
||||||
rest_client.streams.list.return_value = {
|
|
||||||
stream_name_original: Stream([Producer("rtsp://stream")]),
|
|
||||||
entity_id: Stream([Producer("rtsp://different")]),
|
|
||||||
}
|
|
||||||
|
|
||||||
receive_message_callback.reset_mock()
|
|
||||||
ws_client.reset_mock()
|
|
||||||
await test()
|
|
||||||
|
|
||||||
assert rest_client.streams.add.call_args_list == stream_added_calls
|
|
||||||
|
|
||||||
# If the stream is already added, the stream should not be added again.
|
|
||||||
rest_client.streams.add.reset_mock()
|
|
||||||
rest_client.streams.list.return_value = {
|
|
||||||
stream_name_original: Stream([Producer("rtsp://stream")]),
|
|
||||||
entity_id: Stream(
|
|
||||||
[
|
|
||||||
Producer(f"rtsp://127.0.0.1:18554/{stream_name_original}"),
|
|
||||||
Producer(f"ffmpeg:{stream_name_original}#audio=opus"),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
receive_message_callback.reset_mock()
|
|
||||||
ws_client.reset_mock()
|
|
||||||
await test()
|
|
||||||
|
|
||||||
rest_client.streams.add.assert_not_called()
|
|
||||||
assert isinstance(camera._webrtc_provider, WebRTCProvider)
|
|
||||||
|
|
||||||
# Set stream source to None and provider should be skipped
|
|
||||||
rest_client.streams.list.return_value = {}
|
|
||||||
receive_message_callback.reset_mock()
|
|
||||||
camera.set_stream_source(None)
|
|
||||||
await camera.async_handle_async_webrtc_offer(
|
|
||||||
OFFER_SDP, "session_id", receive_message_callback
|
|
||||||
)
|
|
||||||
receive_message_callback.assert_called_once_with(
|
|
||||||
WebRTCError("go2rtc_webrtc_offer_failed", "Camera has no stream source")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await hass.async_stop()
|
await hass.async_stop()
|
||||||
|
@ -448,7 +340,7 @@ async def test_setup_managed(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
|
@pytest.mark.parametrize("has_go2rtc_entry", [True, False])
|
||||||
async def test_setup_self_hosted(
|
async def test_setup_go(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
rest_client: AsyncMock,
|
rest_client: AsyncMock,
|
||||||
ws_client: Mock,
|
ws_client: Mock,
|
||||||
|
@ -458,83 +350,16 @@ async def test_setup_self_hosted(
|
||||||
mock_is_docker_env: Mock,
|
mock_is_docker_env: Mock,
|
||||||
has_go2rtc_entry: bool,
|
has_go2rtc_entry: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the go2rtc with selfhosted go2rtc instance."""
|
"""Test the go2rtc config entry without binary."""
|
||||||
assert (len(hass.config_entries.async_entries(DOMAIN)) == 1) == has_go2rtc_entry
|
assert (len(hass.config_entries.async_entries(DOMAIN)) == 1) == has_go2rtc_entry
|
||||||
|
|
||||||
config = {DOMAIN: {CONF_URL: "http://localhost:1984/"}}
|
config = {DOMAIN: {CONF_URL: "http://localhost:1984/"}}
|
||||||
camera = init_test_integration
|
|
||||||
|
|
||||||
entity_id = camera.entity_id
|
def after_setup() -> None:
|
||||||
assert camera.frontend_stream_type == StreamType.HLS
|
server.assert_not_called()
|
||||||
|
|
||||||
assert await async_setup_component(hass, DOMAIN, config)
|
await _test_setup_and_signaling(
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
hass, rest_client, ws_client, config, after_setup, init_test_integration
|
||||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
|
||||||
assert len(config_entries) == 1
|
|
||||||
assert config_entries[0].state == ConfigEntryState.LOADED
|
|
||||||
server.assert_not_called()
|
|
||||||
|
|
||||||
receive_message_callback = Mock(spec_set=WebRTCSendMessage)
|
|
||||||
|
|
||||||
async def test() -> None:
|
|
||||||
await camera.async_handle_async_webrtc_offer(
|
|
||||||
OFFER_SDP, "session_id", receive_message_callback
|
|
||||||
)
|
|
||||||
ws_client.send.assert_called_once_with(
|
|
||||||
WebRTCOffer(
|
|
||||||
OFFER_SDP,
|
|
||||||
camera.async_get_webrtc_client_configuration().configuration.ice_servers,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
ws_client.subscribe.assert_called_once()
|
|
||||||
|
|
||||||
# Simulate the answer from the go2rtc server
|
|
||||||
callback = ws_client.subscribe.call_args[0][0]
|
|
||||||
callback(WebRTCAnswer(ANSWER_SDP))
|
|
||||||
receive_message_callback.assert_called_once_with(HAWebRTCAnswer(ANSWER_SDP))
|
|
||||||
|
|
||||||
await test()
|
|
||||||
|
|
||||||
rest_client.streams.add.assert_called_once_with(
|
|
||||||
entity_id, ["rtsp://stream", f"ffmpeg:{camera.entity_id}#audio=opus"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Stream exists but the source is different
|
|
||||||
rest_client.streams.add.reset_mock()
|
|
||||||
rest_client.streams.list.return_value = {
|
|
||||||
entity_id: Stream([Producer("rtsp://different")])
|
|
||||||
}
|
|
||||||
|
|
||||||
receive_message_callback.reset_mock()
|
|
||||||
ws_client.reset_mock()
|
|
||||||
await test()
|
|
||||||
|
|
||||||
rest_client.streams.add.assert_called_once_with(
|
|
||||||
entity_id, ["rtsp://stream", f"ffmpeg:{camera.entity_id}#audio=opus"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# If the stream is already added, the stream should not be added again.
|
|
||||||
rest_client.streams.add.reset_mock()
|
|
||||||
rest_client.streams.list.return_value = {
|
|
||||||
entity_id: Stream([Producer("rtsp://stream")])
|
|
||||||
}
|
|
||||||
|
|
||||||
receive_message_callback.reset_mock()
|
|
||||||
ws_client.reset_mock()
|
|
||||||
await test()
|
|
||||||
|
|
||||||
rest_client.streams.add.assert_not_called()
|
|
||||||
assert isinstance(camera._webrtc_provider, WebRTCProvider)
|
|
||||||
|
|
||||||
# Set stream source to None and provider should be skipped
|
|
||||||
rest_client.streams.list.return_value = {}
|
|
||||||
receive_message_callback.reset_mock()
|
|
||||||
camera.set_stream_source(None)
|
|
||||||
await camera.async_handle_async_webrtc_offer(
|
|
||||||
OFFER_SDP, "session_id", receive_message_callback
|
|
||||||
)
|
|
||||||
receive_message_callback.assert_called_once_with(
|
|
||||||
WebRTCError("go2rtc_webrtc_offer_failed", "Camera has no stream source")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_get_binary.assert_not_called()
|
mock_get_binary.assert_not_called()
|
||||||
|
|
Loading…
Add table
Reference in a new issue