""" 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( stream.access_token) 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)