diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index e646c11f2e9..d75475dbb26 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -203,8 +203,7 @@ class AmcrestCam(Camera): """Return the camera model.""" return self._model - @property - def stream_source(self): + async def stream_source(self): """Return the source of the stream.""" return self._api.rtsp_url(typeno=self._resolution) diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 08e40f4999a..c993e9d9f64 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -58,8 +58,7 @@ class AxisCamera(AxisEntityBase, MjpegCamera): """Return supported features.""" return SUPPORT_STREAM - @property - def stream_source(self): + async def stream_source(self): """Return the stream source.""" return AXIS_STREAM.format( self.device.config_entry.data[CONF_DEVICE][CONF_USERNAME], diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 352a9dd5060..b6e41e2cf11 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -107,11 +107,14 @@ async def async_request_stream(hass, entity_id, fmt): camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) - if not camera.stream_source: + async with async_timeout.timeout(10): + source = await camera.stream_source() + + if not source: raise HomeAssistantError("{} does not support play stream service" .format(camera.entity_id)) - return request_stream(hass, camera.stream_source, fmt=fmt, + return request_stream(hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream) @@ -121,7 +124,7 @@ async def async_get_image(hass, entity_id, timeout=10): camera = _get_camera_from_entity_id(hass, entity_id) with suppress(asyncio.CancelledError, asyncio.TimeoutError): - with async_timeout.timeout(timeout): + async with async_timeout.timeout(timeout): image = await camera.async_camera_image() if image: @@ -221,8 +224,16 @@ async def async_setup(hass, config): async def preload_stream(hass, _): 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) + 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_when_setup(hass, DOMAIN_STREAM, preload_stream) @@ -328,8 +339,7 @@ class Camera(Entity): """Return the interval between frames of the mjpeg stream.""" return 0.5 - @property - def stream_source(self): + async def stream_source(self): """Return the source of the stream.""" return None @@ -481,7 +491,7 @@ class CameraImageView(CameraView): async def handle(self, request, camera): """Serve camera image.""" with suppress(asyncio.CancelledError, asyncio.TimeoutError): - with async_timeout.timeout(10): + async with async_timeout.timeout(10): image = await camera.async_camera_image() if image: @@ -547,18 +557,25 @@ async def ws_camera_stream(hass, connection, msg): camera = _get_camera_from_entity_id(hass, entity_id) camera_prefs = hass.data[DATA_CAMERA_PREFS].get(entity_id) - if not camera.stream_source: + async with async_timeout.timeout(10): + source = await camera.stream_source() + + if not 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, + url = request_stream(hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream) connection.send_result(msg['id'], {'url': url}) except HomeAssistantError as ex: - _LOGGER.error(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") @websocket_api.async_response @@ -622,7 +639,10 @@ async def async_handle_snapshot_service(camera, service): async def async_handle_play_stream_service(camera, service_call): """Handle play stream services calls.""" - if not camera.stream_source: + async with async_timeout.timeout(10): + source = await camera.stream_source() + + if not source: raise HomeAssistantError("{} does not support play stream service" .format(camera.entity_id)) @@ -631,7 +651,7 @@ async def async_handle_play_stream_service(camera, service_call): fmt = service_call.data[ATTR_FORMAT] entity_ids = service_call.data[ATTR_MEDIA_PLAYER] - url = request_stream(hass, camera.stream_source, fmt=fmt, + url = request_stream(hass, source, fmt=fmt, keepalive=camera_prefs.preload_stream) data = { ATTR_ENTITY_ID: entity_ids, @@ -646,7 +666,10 @@ async def async_handle_play_stream_service(camera, service_call): async def async_handle_record_service(camera, call): """Handle stream recording service calls.""" - if not camera.stream_source: + async with async_timeout.timeout(10): + source = await camera.stream_source() + + if not source: raise HomeAssistantError("{} does not support record service" .format(camera.entity_id)) @@ -657,7 +680,7 @@ async def async_handle_record_service(camera, call): variables={ATTR_ENTITY_ID: camera}) data = { - CONF_STREAM_SOURCE: camera.stream_source, + CONF_STREAM_SOURCE: source, CONF_FILENAME: video_path, CONF_DURATION: call.data[CONF_DURATION], CONF_LOOKBACK: call.data[CONF_LOOKBACK], diff --git a/homeassistant/components/doorbird/camera.py b/homeassistant/components/doorbird/camera.py index 6da2cd1447d..b4bd40c442c 100644 --- a/homeassistant/components/doorbird/camera.py +++ b/homeassistant/components/doorbird/camera.py @@ -57,8 +57,7 @@ class DoorBirdCamera(Camera): self._last_update = datetime.datetime.min super().__init__() - @property - def stream_source(self): + async def stream_source(self): """Return the stream source.""" return self._stream_url diff --git a/homeassistant/components/ffmpeg/camera.py b/homeassistant/components/ffmpeg/camera.py index e803155d254..20b4e538085 100644 --- a/homeassistant/components/ffmpeg/camera.py +++ b/homeassistant/components/ffmpeg/camera.py @@ -47,8 +47,7 @@ class FFmpegCamera(Camera): """Return supported features.""" return SUPPORT_STREAM - @property - def stream_source(self): + async def stream_source(self): """Return the stream source.""" return self._input.split(' ')[-1] diff --git a/homeassistant/components/foscam/camera.py b/homeassistant/components/foscam/camera.py index f83c3f1966a..3bb000380d7 100644 --- a/homeassistant/components/foscam/camera.py +++ b/homeassistant/components/foscam/camera.py @@ -77,8 +77,7 @@ class FoscamCam(Camera): return SUPPORT_STREAM return 0 - @property - def stream_source(self): + async def stream_source(self): """Return the stream source.""" if self._rtsp_port: return 'rtsp://{}:{}@{}:{}/videoMain'.format( diff --git a/homeassistant/components/generic/camera.py b/homeassistant/components/generic/camera.py index 7f63a832779..8b98d84c06d 100644 --- a/homeassistant/components/generic/camera.py +++ b/homeassistant/components/generic/camera.py @@ -146,7 +146,6 @@ class GenericCamera(Camera): """Return the name of this device.""" return self._name - @property - def stream_source(self): + async def stream_source(self): """Return the source of the stream.""" return self._stream_source diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 976e0794938..7a0c1b0e513 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -123,8 +123,7 @@ class NetatmoCamera(Camera): """Return supported features.""" return SUPPORT_STREAM - @property - def stream_source(self): + async def stream_source(self): """Return the stream source.""" url = '{0}/live/files/{1}/index.m3u8' if self._localurl: diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index c308ba2c4d2..230aa913791 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -339,8 +339,7 @@ class ONVIFHassCamera(Camera): return SUPPORT_STREAM return 0 - @property - def stream_source(self): + async def stream_source(self): """Return the stream source.""" return self._input diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index e12cca75c61..75ee8f6c665 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -209,8 +209,7 @@ async def test_websocket_camera_stream(hass, hass_ws_client, return_value='http://home.assistant/playlist.m3u8' ) as mock_request_stream, \ patch('homeassistant.components.demo.camera.DemoCamera.stream_source', - new_callable=PropertyMock) as mock_stream_source: - mock_stream_source.return_value = io.BytesIO() + return_value=mock_coro('http://example.com')): # Request playlist through WebSocket client = await hass_ws_client(hass) await client.send_json({ @@ -289,8 +288,7 @@ async def test_handle_play_stream_service(hass, mock_camera, mock_stream): with patch('homeassistant.components.camera.request_stream' ) as mock_request_stream, \ patch('homeassistant.components.demo.camera.DemoCamera.stream_source', - new_callable=PropertyMock) as mock_stream_source: - mock_stream_source.return_value = io.BytesIO() + return_value=mock_coro('http://example.com')): # Call service await hass.services.async_call( camera.DOMAIN, camera.SERVICE_PLAY_STREAM, data, blocking=True) @@ -331,8 +329,7 @@ async def test_preload_stream(hass, mock_stream): patch('homeassistant.components.camera.prefs.CameraPreferences.get', return_value=demo_prefs), \ patch('homeassistant.components.demo.camera.DemoCamera.stream_source', - new_callable=PropertyMock) as mock_stream_source: - mock_stream_source.return_value = io.BytesIO() + return_value=mock_coro("http://example.com")): await async_setup_component(hass, 'camera', { DOMAIN: { 'platform': 'demo' @@ -364,12 +361,11 @@ async def test_record_service(hass, mock_camera, mock_stream): } with patch('homeassistant.components.demo.camera.DemoCamera.stream_source', - new_callable=PropertyMock) as mock_stream_source, \ + return_value=mock_coro("http://example.com")), \ patch( 'homeassistant.components.stream.async_handle_record_service', return_value=mock_coro()) as mock_record_service, \ patch.object(hass.config, 'is_allowed_path', return_value=True): - mock_stream_source.return_value = io.BytesIO() # Call service await hass.services.async_call( camera.DOMAIN, camera.SERVICE_RECORD, data, blocking=True)