async def test_stream_ended(hass, stream_worker_sync): """Test hls stream packets ended.""" await async_setup_component(hass, "stream", {"stream": {}}) stream_worker_sync.pause() # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) track = stream.add_provider("hls") # Request stream request_stream(hass, source) # Run it dead while True: segment = await track.recv() if segment is None: break segments = segment.sequence # Allow worker to finalize once enough of the stream is been consumed if segments > 1: stream_worker_sync.resume() assert segments > 1 assert not track.get_segment() # Stop stream, if it hasn't quit already stream.stop()
async def test_stream_keepalive(hass): """Test hls stream retries the stream when keepalive=True.""" await async_setup_component(hass, "stream", {"stream": {}}) # Setup demo HLS track source = "test_stream_keepalive_source" stream = preload_stream(hass, source) track = stream.add_provider("hls") track.num_segments = 2 cur_time = 0 def time_side_effect(): nonlocal cur_time if cur_time >= 80: stream.keepalive = False # Thread should exit and be joinable. cur_time += 40 return cur_time with patch("av.open") as av_open, patch( "homeassistant.components.stream.worker.time" ) as mock_time, patch( "homeassistant.components.stream.worker.STREAM_RESTART_INCREMENT", 0): av_open.side_effect = av.error.InvalidDataError(-2, "error") mock_time.time.side_effect = time_side_effect # Request stream request_stream(hass, source, keepalive=True) stream._thread.join() stream._thread = None assert av_open.call_count == 2 # Stop stream, if it hasn't quit already stream.stop()
async def test_stream_ended(hass): """Test hls stream packets ended.""" await async_setup_component(hass, 'stream', { 'stream': {} }) # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) track = stream.add_provider('hls') track.num_segments = 2 # Request stream request_stream(hass, source) # Run it dead segments = 0 while await track.recv() is not None: segments += 1 assert segments == 3 assert not track.get_segment() # Stop stream, if it hasn't quit already stream.stop()
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a generic IP Camera.""" async_add_entities([GenericCamera(hass, config)]) if config[CONF_PRELOAD_STREAM] and config[CONF_STREAM_SOURCE]: request_stream(hass, config[CONF_STREAM_SOURCE], keepalive=True, options=dict(config[CONF_STREAM_OPTIONS]))
async def preload_stream(hass, _): for camera in component.entities: camera_prefs = prefs.get(camera.entity_id) if not camera_prefs.preload_stream: continue async with async_timeout.timeout(10): source = await camera.stream_source() if not source: continue request_stream(hass, source, keepalive=True)
async def async_handle_play_stream_service(camera, service_call): """Handle play stream services calls.""" if not camera.stream_source: raise HomeAssistantError( "{} does not support play stream service".format(camera.entity_id)) hass = camera.hass camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) fmt = service_call.data[ATTR_FORMAT] entity_ids = service_call.data[ATTR_MEDIA_PLAYER] url = request_stream(hass, camera.stream_source, fmt=fmt, keepalive=camera_prefs.preload_stream) data = { ATTR_ENTITY_ID: entity_ids, ATTR_MEDIA_CONTENT_ID: "{}{}".format(hass.config.api.base_url, url), ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt] } await hass.services.async_call(DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True, context=service_call.context)
async def async_handle_play_stream_service(camera, service_call): """Handle play stream services calls.""" async with async_timeout.timeout(10): source = await camera.stream_source() if not source: raise HomeAssistantError( f"{camera.entity_id} does not support play stream service") hass = camera.hass camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) fmt = service_call.data[ATTR_FORMAT] entity_ids = service_call.data[ATTR_MEDIA_PLAYER] url = request_stream( hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream, options=camera.stream_options, ) data = { ATTR_ENTITY_ID: entity_ids, ATTR_MEDIA_CONTENT_ID: f"{get_url(hass)}{url}", ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], } await hass.services.async_call(DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True, context=service_call.context)
async def test_stream_timeout(hass, hass_client): """Test hls stream timeout.""" await async_setup_component(hass, 'stream', {'stream': {}}) # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) stream.add_provider('hls') # Request stream url = request_stream(hass, source) http_client = await hass_client() # Fetch playlist parsed_url = urlparse(url) playlist_response = await http_client.get(parsed_url.path) assert playlist_response.status == 200 # Wait a minute future = dt_util.utcnow() + timedelta(minutes=1) async_fire_time_changed(hass, future) # Fetch again to reset timer playlist_response = await http_client.get(parsed_url.path) assert playlist_response.status == 200 # Wait 5 minutes future = dt_util.utcnow() + timedelta(minutes=5) async_fire_time_changed(hass, future) # Ensure playlist not accessable fail_response = await http_client.get(parsed_url.path) assert fail_response.status == 404
async def ws_camera_stream(hass, connection, msg): """Handle get camera stream websocket command. Async friendly. """ try: entity_id = msg["entity_id"] camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) async with async_timeout.timeout(10): source = await camera.stream_source() if not source: raise HomeAssistantError( f"{camera.entity_id} does not support play stream service") fmt = msg["format"] url = request_stream( hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream, options=camera.stream_options, ) connection.send_result(msg["id"], {"url": url}) except HomeAssistantError as ex: _LOGGER.error("Error requesting stream: %s", ex) connection.send_error(msg["id"], "start_stream_failed", str(ex)) except asyncio.TimeoutError: _LOGGER.error("Timeout getting stream source") connection.send_error(msg["id"], "start_stream_failed", "Timeout getting stream source")
async def async_request_stream(hass, entity_id, fmt): """Request a stream for a camera entity.""" camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) if not camera.stream_source: raise HomeAssistantError("{} does not support play stream service" .format(camera.entity_id)) return request_stream(hass, camera.stream_source, fmt=fmt, keepalive=camera_prefs.preload_stream)
async def async_request_stream(hass, entity_id, fmt): """Request a stream for a camera entity.""" camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) if not camera.stream_source: raise HomeAssistantError( "{} does not support play stream service".format(camera.entity_id)) return request_stream(hass, camera.stream_source, fmt=fmt, keepalive=camera_prefs.preload_stream)
async def async_request_stream(hass, entity_id, fmt): """Request a stream for a camera entity.""" camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) async with async_timeout.timeout(10): source = await camera.stream_source() if not source: raise HomeAssistantError( f"{camera.entity_id} does not support play stream service" ) return request_stream(hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream)
async def test_stream_ended(hass): """Test hls stream packets ended.""" await async_setup_component(hass, 'stream', {'stream': {}}) # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) track = stream.add_provider('hls') track.num_segments = 2 # Request stream request_stream(hass, source) # Run it dead segments = 0 while await track.recv() is not None: segments += 1 assert segments > 1 assert not track.get_segment() # Stop stream, if it hasn't quit already stream.stop()
async def test_stream_ended(hass): """Test hls stream packets ended.""" await async_setup_component(hass, "stream", {"stream": {}}) # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) track = stream.add_provider("hls") # Request stream request_stream(hass, source) # Run it dead while True: segment = await track.recv() if segment is None: break segments = segment.sequence assert segments > 1 assert not track.get_segment() # Stop stream, if it hasn't quit already stream.stop()
async def test_hls_stream(hass, hass_client, stream_worker_sync): """ Test hls stream. Purposefully not mocking anything here to test full integration with the stream component. """ await async_setup_component(hass, "stream", {"stream": {}}) stream_worker_sync.pause() # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) stream.add_provider("hls") # Request stream url = request_stream(hass, source) http_client = await hass_client() # Fetch playlist parsed_url = urlparse(url) playlist_response = await http_client.get(parsed_url.path) assert playlist_response.status == 200 # Fetch init playlist = await playlist_response.text() playlist_url = "/".join(parsed_url.path.split("/")[:-1]) init_url = playlist_url + "/init.mp4" init_response = await http_client.get(init_url) assert init_response.status == 200 # Fetch segment playlist = await playlist_response.text() playlist_url = "/".join(parsed_url.path.split("/")[:-1]) segment_url = playlist_url + "/" + playlist.splitlines()[-1] segment_response = await http_client.get(segment_url) assert segment_response.status == 200 stream_worker_sync.resume() # Stop stream, if it hasn't quit already stream.stop() # Ensure playlist not accessible after stream ends fail_response = await http_client.get(parsed_url.path) assert fail_response.status == HTTP_NOT_FOUND
async def ws_camera_stream(hass, connection, msg): """Handle get camera stream websocket command. Async friendly. """ try: camera = _get_camera_from_entity_id(hass, msg['entity_id']) if not camera.stream_source: raise HomeAssistantError("{} does not support play stream service" .format(camera.entity_id)) fmt = msg['format'] url = request_stream(hass, camera.stream_source, fmt=fmt) connection.send_result(msg['id'], {'url': url}) except HomeAssistantError as ex: _LOGGER.error(ex) connection.send_error( msg['id'], 'start_stream_failed', str(ex))
async def async_handle_play_stream_service(camera, service_call): """Handle play stream services calls.""" if not camera.stream_source: raise HomeAssistantError("{} does not support play stream service" .format(camera.entity_id)) hass = camera.hass fmt = service_call.data[ATTR_FORMAT] entity_ids = service_call.data[ATTR_MEDIA_PLAYER] url = request_stream(hass, camera.stream_source, fmt=fmt) data = { ATTR_ENTITY_ID: entity_ids, ATTR_MEDIA_CONTENT_ID: "{}{}".format(hass.config.api.base_url, url), ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt] } await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True, context=service_call.context)
async def ws_camera_stream(hass, connection, msg): """Handle get camera stream websocket command. Async friendly. """ try: entity_id = msg['entity_id'] camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) if not camera.stream_source: raise HomeAssistantError("{} does not support play stream service" .format(camera.entity_id)) fmt = msg['format'] url = request_stream(hass, camera.stream_source, fmt=fmt, keepalive=camera_prefs.preload_stream) connection.send_result(msg['id'], {'url': url}) except HomeAssistantError as ex: _LOGGER.error(ex) connection.send_error( msg['id'], 'start_stream_failed', str(ex))
async def test_hls_stream(hass, hass_client): """ Test hls stream. Purposefully not mocking anything here to test full integration with the stream component. """ await async_setup_component(hass, 'stream', { 'stream': {} }) # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) stream.add_provider('hls') # Request stream url = request_stream(hass, source) http_client = await hass_client() # Fetch playlist parsed_url = urlparse(url) playlist_response = await http_client.get(parsed_url.path) assert playlist_response.status == 200 # Fetch segment playlist = await playlist_response.text() playlist_url = '/'.join(parsed_url.path.split('/')[:-1]) segment_url = playlist_url + playlist.splitlines()[-1][1:] segment_response = await http_client.get(segment_url) assert segment_response.status == 200 # Stop stream, if it hasn't quit already stream.stop() # Ensure playlist not accessable after stream ends fail_response = await http_client.get(parsed_url.path) assert fail_response.status == 404
async def test_hls_stream(hass, hass_client): """ Test hls stream. Purposefully not mocking anything here to test full integration with the stream component. """ await async_setup_component(hass, 'stream', {'stream': {}}) # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) stream.add_provider('hls') # Request stream url = request_stream(hass, source) http_client = await hass_client() # Fetch playlist parsed_url = urlparse(url) playlist_response = await http_client.get(parsed_url.path) assert playlist_response.status == 200 # Fetch segment playlist = await playlist_response.text() playlist_url = '/'.join(parsed_url.path.split('/')[:-1]) segment_url = playlist_url + playlist.splitlines()[-1][1:] segment_response = await http_client.get(segment_url) assert segment_response.status == 200 # Stop stream, if it hasn't quit already stream.stop() # Ensure playlist not accessable after stream ends fail_response = await http_client.get(parsed_url.path) assert fail_response.status == 404
async def ws_camera_stream(hass, connection, msg): """Handle get camera stream websocket command. Async friendly. """ try: entity_id = msg['entity_id'] camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) if not camera.stream_source: raise HomeAssistantError( "{} does not support play stream service".format( camera.entity_id)) fmt = msg['format'] url = request_stream(hass, camera.stream_source, fmt=fmt, keepalive=camera_prefs.preload_stream) connection.send_result(msg['id'], {'url': url}) except HomeAssistantError as ex: _LOGGER.error(ex) connection.send_error(msg['id'], 'start_stream_failed', str(ex))
async def test_stream_timeout(hass, hass_client): """Test hls stream timeout.""" await async_setup_component(hass, 'stream', { 'stream': {} }) # Setup demo HLS track source = generate_h264_video() stream = preload_stream(hass, source) stream.add_provider('hls') # Request stream url = request_stream(hass, source) http_client = await hass_client() # Fetch playlist parsed_url = urlparse(url) playlist_response = await http_client.get(parsed_url.path) assert playlist_response.status == 200 # Wait a minute future = dt_util.utcnow() + timedelta(minutes=1) async_fire_time_changed(hass, future) # Fetch again to reset timer playlist_response = await http_client.get(parsed_url.path) assert playlist_response.status == 200 # Wait 5 minutes future = dt_util.utcnow() + timedelta(minutes=5) async_fire_time_changed(hass, future) # Ensure playlist not accessable fail_response = await http_client.get(parsed_url.path) assert fail_response.status == 404
def preload_stream(event): for camera in component.entities: camera_prefs = prefs.get(camera.entity_id) if camera.stream_source and camera_prefs.preload_stream: request_stream(hass, camera.stream_source, keepalive=True)
def preload_stream(event): for camera in component.entities: camera_prefs = prefs.get(camera.entity_id) if camera.stream_source and camera_prefs.preload_stream: request_stream(hass, camera.stream_source, keepalive=True)
async def async_handle_play_stream_service(camera, service_call): """Handle play stream services calls.""" async with async_timeout.timeout(10): source = await camera.stream_source() if not source: raise HomeAssistantError( f"{camera.entity_id} does not support play stream service") hass = camera.hass camera_prefs = hass.data[DATA_CAMERA_PREFS].get(camera.entity_id) fmt = service_call.data[ATTR_FORMAT] entity_ids = service_call.data[ATTR_MEDIA_PLAYER] url = request_stream( hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream, options=camera.stream_options, ) data = { ATTR_MEDIA_CONTENT_ID: f"{get_url(hass)}{url}", ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], } # It is required to send a different payload for cast media players cast_entity_ids = [ entity for entity, source in entity_sources(hass).items() if entity in entity_ids and source["domain"] == "cast" ] other_entity_ids = list(set(entity_ids) - set(cast_entity_ids)) if cast_entity_ids: await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: cast_entity_ids, **data, ATTR_MEDIA_EXTRA: { "stream_type": "LIVE", "media_info": { "hlsVideoSegmentFormat": "fmp4", }, }, }, blocking=True, context=service_call.context, ) if other_entity_ids: await hass.services.async_call( DOMAIN_MP, SERVICE_PLAY_MEDIA, { ATTR_ENTITY_ID: other_entity_ids, **data, }, blocking=True, context=service_call.context, )