Add a stream_id parameter to the WebRTC provider (#63625)

* Add a stream_id parameter to the WebRTC provider

Add an additional stream id parameter (effectively, the camera entity id)
to allow updating an existing stream source when the strema changes. This
is useful for stream sources that have expiring URLs, like Nest.

* Redefine the provider using a type

* Use old typing methods for type definintion to pass python3.8 pylint
pull/63182/head
Allen Porter 2022-01-10 17:56:18 -08:00 committed by GitHub
parent 8f6e24aa1e
commit 596edc8919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 19 additions and 12 deletions

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import asyncio
import base64
import collections
from collections.abc import Awaitable, Callable, Iterable, Mapping
from collections.abc import Iterable, Mapping
from contextlib import suppress
from dataclasses import dataclass
from datetime import datetime, timedelta
@ -14,7 +14,7 @@ import inspect
import logging
import os
from random import SystemRandom
from typing import Final, cast, final
from typing import Awaitable, Callable, Final, Optional, cast, final
from aiohttp import web
import async_timeout
@ -287,17 +287,23 @@ def _get_camera_from_entity_id(hass: HomeAssistant, entity_id: str) -> Camera:
return cast(Camera, camera)
# 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.
RtspToWebRtcProviderType = Callable[[str, str, str], Awaitable[Optional[str]]]
def async_register_rtsp_to_web_rtc_provider(
hass: HomeAssistant,
domain: str,
provider: Callable[[str, str], Awaitable[str | None]],
provider: RtspToWebRtcProviderType,
) -> Callable[[], None]:
"""Register an RTSP to WebRTC provider.
Integrations may register a Callable that accepts a `stream_source` and
SDP `offer` as an input, and the output is the SDP `answer`. An implementation
may return None if the source or offer is not eligible or throw HomeAssistantError
on failure. The first provider to satisfy the offer will be used.
The first provider to satisfy the offer will be used.
"""
if DOMAIN not in hass.data:
raise ValueError("Unexpected state, camera not loaded")
@ -327,9 +333,9 @@ async def _async_refresh_providers(hass: HomeAssistant) -> None:
def _async_get_rtsp_to_web_rtc_providers(
hass: HomeAssistant,
) -> Iterable[Callable[[str, str], Awaitable[str | None]]]:
) -> Iterable[RtspToWebRtcProviderType]:
"""Return registered RTSP to WebRTC providers."""
providers: dict[str, Callable[[str, str], Awaitable[str | None]]] = hass.data.get(
providers: dict[str, RtspToWebRtcProviderType] = hass.data.get(
DATA_RTSP_TO_WEB_RTC, {}
)
return providers.values()
@ -548,7 +554,7 @@ class Camera(Entity):
if not stream_source:
return None
for provider in _async_get_rtsp_to_web_rtc_providers(self.hass):
answer_sdp = await provider(stream_source, offer_sdp)
answer_sdp = await provider(stream_source, offer_sdp, self.entity_id)
if answer_sdp:
return answer_sdp
raise HomeAssistantError("WebRTC offer was not accepted by any providers")

View File

@ -57,6 +57,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_offer_for_stream_source(
stream_source: str,
offer_sdp: str,
stream_id: str,
) -> str:
"""Handle the signal path for a WebRTC stream.
@ -66,7 +67,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""
try:
async with async_timeout.timeout(TIMEOUT):
return await client.offer(offer_sdp, stream_source)
return await client.offer_stream_id(offer_sdp, stream_source, stream_id)
except TimeoutError as err:
raise HomeAssistantError("Timeout talking to RTSPtoWebRTC server") from err
except ClientError as err:

View File

@ -116,7 +116,7 @@ async def mock_hls_stream_source_fixture():
yield mock_hls_stream_source
async def provide_web_rtc_answer(stream_source: str, offer: str) -> str:
async def provide_web_rtc_answer(stream_source: str, offer: str, stream_id: str) -> str:
"""Simulate an rtsp to webrtc provider."""
assert stream_source == STREAM_SOURCE
assert offer == WEBRTC_OFFER