Change the API boundary between camera and stream with initial improvement for nest expiring stream urls (#45431)

* Change the API boundary between stream and camera

Shift more of the stream lifecycle management to the camera.  The motivation is to support stream urls that expire
giving the camera the ability to change the stream once it is created.

* Document stream lifecycle and simplify stream/camera interaction

* Reorder create_stream function to reduce diffs

* Increase test coverage for camera_sdm.py

* Fix ffmpeg typo.

* Add a stream identifier for each stream, managed by camera

* Remove stream record service

* Update homeassistant/components/stream/__init__.py

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

* Unroll changes to Stream interface back into camera component

* Fix preload stream to actually start the background worker

* Reduce unncessary diffs for readability

* Remove redundant camera stream start code

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Allen Porter 2021-02-08 19:53:28 -08:00 committed by GitHub
parent 889baef456
commit 2bcf87b980
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 254 additions and 356 deletions

View file

@ -155,25 +155,20 @@ async def test_websocket_camera_thumbnail(hass, hass_ws_client, mock_camera):
async def test_websocket_stream_no_source(
hass, hass_ws_client, mock_camera, mock_stream
):
"""Test camera/stream websocket command."""
"""Test camera/stream websocket command with camera with no source."""
await async_setup_component(hass, "camera", {})
with patch(
"homeassistant.components.camera.request_stream",
return_value="http://home.assistant/playlist.m3u8",
) as mock_request_stream:
# Request playlist through WebSocket
client = await hass_ws_client(hass)
await client.send_json(
{"id": 6, "type": "camera/stream", "entity_id": "camera.demo_camera"}
)
msg = await client.receive_json()
# Request playlist through WebSocket
client = await hass_ws_client(hass)
await client.send_json(
{"id": 6, "type": "camera/stream", "entity_id": "camera.demo_camera"}
)
msg = await client.receive_json()
# Assert WebSocket response
assert not mock_request_stream.called
assert msg["id"] == 6
assert msg["type"] == TYPE_RESULT
assert not msg["success"]
# Assert WebSocket response
assert msg["id"] == 6
assert msg["type"] == TYPE_RESULT
assert not msg["success"]
async def test_websocket_camera_stream(hass, hass_ws_client, mock_camera, mock_stream):
@ -181,9 +176,9 @@ async def test_websocket_camera_stream(hass, hass_ws_client, mock_camera, mock_s
await async_setup_component(hass, "camera", {})
with patch(
"homeassistant.components.camera.request_stream",
"homeassistant.components.camera.Stream.endpoint_url",
return_value="http://home.assistant/playlist.m3u8",
) as mock_request_stream, patch(
) as mock_stream_view_url, patch(
"homeassistant.components.demo.camera.DemoCamera.stream_source",
return_value="http://example.com",
):
@ -195,7 +190,7 @@ async def test_websocket_camera_stream(hass, hass_ws_client, mock_camera, mock_s
msg = await client.receive_json()
# Assert WebSocket response
assert mock_request_stream.called
assert mock_stream_view_url.called
assert msg["id"] == 6
assert msg["type"] == TYPE_RESULT
assert msg["success"]
@ -248,9 +243,7 @@ async def test_play_stream_service_no_source(hass, mock_camera, mock_stream):
ATTR_ENTITY_ID: "camera.demo_camera",
camera.ATTR_MEDIA_PLAYER: "media_player.test",
}
with patch("homeassistant.components.camera.request_stream"), pytest.raises(
HomeAssistantError
):
with pytest.raises(HomeAssistantError):
# Call service
await hass.services.async_call(
camera.DOMAIN, camera.SERVICE_PLAY_STREAM, data, blocking=True
@ -265,7 +258,7 @@ async def test_handle_play_stream_service(hass, mock_camera, mock_stream):
)
await async_setup_component(hass, "media_player", {})
with patch(
"homeassistant.components.camera.request_stream"
"homeassistant.components.camera.Stream.endpoint_url",
) as mock_request_stream, patch(
"homeassistant.components.demo.camera.DemoCamera.stream_source",
return_value="http://example.com",
@ -289,7 +282,7 @@ async def test_no_preload_stream(hass, mock_stream):
"""Test camera preload preference."""
demo_prefs = CameraEntityPreferences({PREF_PRELOAD_STREAM: False})
with patch(
"homeassistant.components.camera.request_stream"
"homeassistant.components.camera.Stream.endpoint_url",
) as mock_request_stream, patch(
"homeassistant.components.camera.prefs.CameraPreferences.get",
return_value=demo_prefs,
@ -308,8 +301,8 @@ async def test_preload_stream(hass, mock_stream):
"""Test camera preload preference."""
demo_prefs = CameraEntityPreferences({PREF_PRELOAD_STREAM: True})
with patch(
"homeassistant.components.camera.request_stream"
) as mock_request_stream, patch(
"homeassistant.components.camera.create_stream"
) as mock_create_stream, patch(
"homeassistant.components.camera.prefs.CameraPreferences.get",
return_value=demo_prefs,
), patch(
@ -322,7 +315,7 @@ async def test_preload_stream(hass, mock_stream):
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
assert mock_request_stream.called
assert mock_create_stream.called
async def test_record_service_invalid_path(hass, mock_camera):
@ -348,10 +341,9 @@ async def test_record_service(hass, mock_camera, mock_stream):
"homeassistant.components.demo.camera.DemoCamera.stream_source",
return_value="http://example.com",
), patch(
"homeassistant.components.stream.async_handle_record_service",
) as mock_record_service, patch.object(
hass.config, "is_allowed_path", return_value=True
):
"homeassistant.components.stream.Stream.async_record",
autospec=True,
) as mock_record:
# Call service
await hass.services.async_call(
camera.DOMAIN,
@ -361,4 +353,4 @@ async def test_record_service(hass, mock_camera, mock_stream):
)
# So long as we call stream.record, the rest should be covered
# by those tests.
assert mock_record_service.called
assert mock_record.called