2019-03-12 02:57:10 +00:00
|
|
|
"""
|
|
|
|
Provide functionality to stream video source.
|
|
|
|
|
|
|
|
For more details about this component, please refer to the documentation at
|
|
|
|
https://home-assistant.io/components/stream/
|
|
|
|
"""
|
|
|
|
import logging
|
|
|
|
import threading
|
|
|
|
|
|
|
|
import voluptuous as vol
|
|
|
|
|
|
|
|
from homeassistant.auth.util import generate_secret
|
|
|
|
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
|
|
|
from homeassistant.loader import bind_hass
|
|
|
|
|
|
|
|
from .const import DOMAIN, ATTR_STREAMS, ATTR_ENDPOINTS
|
|
|
|
from .core import PROVIDERS
|
|
|
|
from .worker import stream_worker
|
|
|
|
from .hls import async_setup_hls
|
|
|
|
|
|
|
|
REQUIREMENTS = ['av==6.1.2']
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
DEPENDENCIES = ['http']
|
|
|
|
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
|
|
DOMAIN: vol.Schema({}),
|
|
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
|
|
# Set log level to error for libav
|
|
|
|
logging.getLogger('libav').setLevel(logging.ERROR)
|
|
|
|
|
|
|
|
|
|
|
|
@bind_hass
|
|
|
|
def request_stream(hass, stream_source, *, fmt='hls',
|
|
|
|
keepalive=False, options=None):
|
|
|
|
"""Set up stream with token."""
|
|
|
|
if DOMAIN not in hass.config.components:
|
|
|
|
raise HomeAssistantError("Stream component is not set up.")
|
|
|
|
|
|
|
|
if options is None:
|
|
|
|
options = {}
|
|
|
|
|
|
|
|
try:
|
|
|
|
streams = hass.data[DOMAIN][ATTR_STREAMS]
|
|
|
|
stream = streams.get(stream_source)
|
|
|
|
if not stream:
|
|
|
|
stream = Stream(hass, stream_source,
|
|
|
|
options=options, keepalive=keepalive)
|
|
|
|
streams[stream_source] = stream
|
|
|
|
|
|
|
|
# Add provider
|
|
|
|
stream.add_provider(fmt)
|
|
|
|
|
|
|
|
if not stream.access_token:
|
|
|
|
stream.access_token = generate_secret()
|
|
|
|
stream.start()
|
|
|
|
return hass.data[DOMAIN][ATTR_ENDPOINTS][fmt].format(
|
2019-03-12 18:46:20 +00:00
|
|
|
stream.access_token)
|
2019-03-12 02:57:10 +00:00
|
|
|
except Exception:
|
|
|
|
raise HomeAssistantError('Unable to get stream')
|
|
|
|
|
|
|
|
|
|
|
|
async def async_setup(hass, config):
|
|
|
|
"""Set up stream."""
|
|
|
|
hass.data[DOMAIN] = {}
|
|
|
|
hass.data[DOMAIN][ATTR_ENDPOINTS] = {}
|
|
|
|
hass.data[DOMAIN][ATTR_STREAMS] = {}
|
|
|
|
|
|
|
|
# Setup HLS
|
|
|
|
hls_endpoint = async_setup_hls(hass)
|
|
|
|
hass.data[DOMAIN][ATTR_ENDPOINTS]['hls'] = hls_endpoint
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def shutdown(event):
|
|
|
|
"""Stop all stream workers."""
|
|
|
|
for stream in hass.data[DOMAIN][ATTR_STREAMS].values():
|
|
|
|
stream.keepalive = False
|
|
|
|
stream.stop()
|
|
|
|
_LOGGER.info("Stopped stream workers.")
|
|
|
|
|
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class Stream:
|
|
|
|
"""Represents a single stream."""
|
|
|
|
|
|
|
|
def __init__(self, hass, source, options=None, keepalive=False):
|
|
|
|
"""Initialize a stream."""
|
|
|
|
self.hass = hass
|
|
|
|
self.source = source
|
|
|
|
self.options = options
|
|
|
|
self.keepalive = keepalive
|
|
|
|
self.access_token = None
|
|
|
|
self._thread = None
|
|
|
|
self._thread_quit = None
|
|
|
|
self._outputs = {}
|
|
|
|
|
|
|
|
if self.options is None:
|
|
|
|
self.options = {}
|
|
|
|
|
|
|
|
@property
|
|
|
|
def outputs(self):
|
|
|
|
"""Return stream outputs."""
|
|
|
|
return self._outputs
|
|
|
|
|
|
|
|
def add_provider(self, fmt):
|
|
|
|
"""Add provider output stream."""
|
|
|
|
provider = PROVIDERS[fmt](self)
|
|
|
|
if not self._outputs.get(provider.format):
|
|
|
|
self._outputs[provider.format] = provider
|
|
|
|
return self._outputs[provider.format]
|
|
|
|
|
|
|
|
def remove_provider(self, provider):
|
|
|
|
"""Remove provider output stream."""
|
|
|
|
if provider.format in self._outputs:
|
|
|
|
del self._outputs[provider.format]
|
|
|
|
|
|
|
|
if not self._outputs:
|
|
|
|
self.stop()
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
"""Start a stream."""
|
|
|
|
if self._thread is None or not self._thread.isAlive():
|
|
|
|
self._thread_quit = threading.Event()
|
|
|
|
self._thread = threading.Thread(
|
|
|
|
name='stream_worker',
|
|
|
|
target=stream_worker,
|
|
|
|
args=(
|
|
|
|
self.hass, self, self._thread_quit))
|
|
|
|
self._thread.start()
|
|
|
|
_LOGGER.info("Started stream: %s", self.source)
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
"""Remove outputs and access token."""
|
|
|
|
self._outputs = {}
|
|
|
|
self.access_token = None
|
|
|
|
|
|
|
|
if not self.keepalive:
|
|
|
|
self._stop()
|
|
|
|
|
|
|
|
def _stop(self):
|
|
|
|
"""Stop worker thread."""
|
|
|
|
if self._thread is not None:
|
|
|
|
self._thread_quit.set()
|
|
|
|
self._thread.join()
|
|
|
|
self._thread = None
|
|
|
|
_LOGGER.info("Stopped stream: %s", self.source)
|