2019-03-12 02:57:10 +00:00
|
|
|
"""Provides core stream functionality."""
|
2021-02-16 14:59:43 +00:00
|
|
|
import abc
|
2019-03-12 02:57:10 +00:00
|
|
|
import io
|
2021-02-16 14:59:43 +00:00
|
|
|
from typing import Callable
|
2019-03-12 02:57:10 +00:00
|
|
|
|
|
|
|
from aiohttp import web
|
2019-10-14 21:20:18 +00:00
|
|
|
import attr
|
2019-03-12 02:57:10 +00:00
|
|
|
|
|
|
|
from homeassistant.components.http import HomeAssistantView
|
2021-02-08 15:19:41 +00:00
|
|
|
from homeassistant.core import HomeAssistant, callback
|
2019-03-12 02:57:10 +00:00
|
|
|
from homeassistant.helpers.event import async_call_later
|
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
from .const import ATTR_STREAMS, DOMAIN
|
2019-03-12 02:57:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
@attr.s
|
|
|
|
class StreamBuffer:
|
|
|
|
"""Represent a segment."""
|
|
|
|
|
2020-07-14 17:30:30 +00:00
|
|
|
segment: io.BytesIO = attr.ib()
|
2019-07-31 19:25:30 +00:00
|
|
|
output = attr.ib() # type=av.OutputContainer
|
|
|
|
vstream = attr.ib() # type=av.VideoStream
|
2020-07-14 17:30:30 +00:00
|
|
|
astream = attr.ib(default=None) # type=Optional[av.AudioStream]
|
2019-03-12 02:57:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
@attr.s
|
|
|
|
class Segment:
|
|
|
|
"""Represent a segment."""
|
|
|
|
|
2020-07-14 17:30:30 +00:00
|
|
|
sequence: int = attr.ib()
|
|
|
|
segment: io.BytesIO = attr.ib()
|
|
|
|
duration: float = attr.ib()
|
2019-03-12 02:57:10 +00:00
|
|
|
|
|
|
|
|
2021-02-08 15:19:41 +00:00
|
|
|
class IdleTimer:
|
|
|
|
"""Invoke a callback after an inactivity timeout.
|
|
|
|
|
|
|
|
The IdleTimer invokes the callback after some timeout has passed. The awake() method
|
|
|
|
resets the internal alarm, extending the inactivity time.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self, hass: HomeAssistant, timeout: int, idle_callback: Callable[[], None]
|
|
|
|
):
|
|
|
|
"""Initialize IdleTimer."""
|
|
|
|
self._hass = hass
|
|
|
|
self._timeout = timeout
|
|
|
|
self._callback = idle_callback
|
|
|
|
self._unsub = None
|
|
|
|
self.idle = False
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
"""Start the idle timer if not already started."""
|
|
|
|
self.idle = False
|
|
|
|
if self._unsub is None:
|
|
|
|
self._unsub = async_call_later(self._hass, self._timeout, self.fire)
|
|
|
|
|
|
|
|
def awake(self):
|
|
|
|
"""Keep the idle time alive by resetting the timeout."""
|
|
|
|
self.idle = False
|
|
|
|
# Reset idle timeout
|
|
|
|
self.clear()
|
|
|
|
self._unsub = async_call_later(self._hass, self._timeout, self.fire)
|
|
|
|
|
|
|
|
def clear(self):
|
2021-02-16 20:10:26 +00:00
|
|
|
"""Clear and disable the timer if it has not already fired."""
|
2021-02-08 15:19:41 +00:00
|
|
|
if self._unsub is not None:
|
|
|
|
self._unsub()
|
|
|
|
|
|
|
|
def fire(self, _now=None):
|
|
|
|
"""Invoke the idle timeout callback, called when the alarm fires."""
|
|
|
|
self.idle = True
|
|
|
|
self._unsub = None
|
|
|
|
self._callback()
|
|
|
|
|
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
class StreamOutput(abc.ABC):
|
2019-03-12 02:57:10 +00:00
|
|
|
"""Represents a stream output."""
|
|
|
|
|
2021-02-16 14:59:43 +00:00
|
|
|
def __init__(self, hass: HomeAssistant):
|
2019-03-12 02:57:10 +00:00
|
|
|
"""Initialize a stream output."""
|
2021-02-08 15:19:41 +00:00
|
|
|
self._hass = hass
|
2020-08-11 21:12:41 +00:00
|
|
|
|
|
|
|
@property
|
2020-08-20 03:18:54 +00:00
|
|
|
def container_options(self) -> Callable[[int], dict]:
|
|
|
|
"""Return Callable which takes a sequence number and returns container options."""
|
2019-03-12 02:57:10 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
def put(self, segment: Segment) -> None:
|
|
|
|
"""Store output."""
|
2021-02-08 15:19:41 +00:00
|
|
|
self._hass.loop.call_soon_threadsafe(self._async_put, segment)
|
2021-01-11 13:34:45 +00:00
|
|
|
|
|
|
|
@callback
|
|
|
|
def _async_put(self, segment: Segment) -> None:
|
|
|
|
"""Store output from event loop."""
|
2019-03-12 02:57:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
class StreamView(HomeAssistantView):
|
|
|
|
"""
|
|
|
|
Base StreamView.
|
|
|
|
|
|
|
|
For implementation of a new stream format, define `url` and `name`
|
|
|
|
attributes, and implement `handle` method in a child class.
|
|
|
|
"""
|
|
|
|
|
|
|
|
requires_auth = False
|
|
|
|
platform = None
|
|
|
|
|
|
|
|
async def get(self, request, token, sequence=None):
|
|
|
|
"""Start a GET request."""
|
2019-07-31 19:25:30 +00:00
|
|
|
hass = request.app["hass"]
|
|
|
|
|
|
|
|
stream = next(
|
2021-02-09 03:53:28 +00:00
|
|
|
(s for s in hass.data[DOMAIN][ATTR_STREAMS] if s.access_token == token),
|
2019-07-31 19:25:30 +00:00
|
|
|
None,
|
|
|
|
)
|
2019-03-12 02:57:10 +00:00
|
|
|
|
|
|
|
if not stream:
|
|
|
|
raise web.HTTPNotFound()
|
|
|
|
|
|
|
|
# Start worker if not already started
|
|
|
|
stream.start()
|
|
|
|
|
|
|
|
return await self.handle(request, stream, sequence)
|
|
|
|
|
|
|
|
async def handle(self, request, stream, sequence):
|
|
|
|
"""Handle the stream request."""
|
|
|
|
raise NotImplementedError()
|