From aaecd91407b631dc67bbb3e827b01802bb1a7e19 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 16 Feb 2021 12:10:26 -0800 Subject: [PATCH] Fix exception in stream idle callback (#46642) * Fix exception in stream idle callback Fix bug where idle timeout callback fails if the stream previously exited. * Add a test for stream idle timeout after stream is stopped. * Add clarifying comment to idle timer clear method * Clear hls timer only on stop --- homeassistant/components/stream/__init__.py | 3 ++ homeassistant/components/stream/core.py | 2 +- tests/components/stream/test_hls.py | 32 +++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 677d01e5006..9b5afb38ed0 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -212,6 +212,9 @@ class Stream: def stop(self): """Remove outputs and access token.""" self.access_token = None + if self._hls_timer: + self._hls_timer.clear() + self._hls_timer = None if self._hls: self._hls.cleanup() self._hls = None diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 4fc70eb856f..f7beb3aa754 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -63,7 +63,7 @@ class IdleTimer: self._unsub = async_call_later(self._hass, self._timeout, self.fire) def clear(self): - """Clear and disable the timer.""" + """Clear and disable the timer if it has not already fired.""" if self._unsub is not None: self._unsub() diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index 2a53e2c5169..55b79684b7b 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -159,6 +159,38 @@ async def test_stream_timeout(hass, hass_client, stream_worker_sync): assert fail_response.status == HTTP_NOT_FOUND +async def test_stream_timeout_after_stop(hass, hass_client, stream_worker_sync): + """Test hls stream timeout after the stream has been stopped already.""" + await async_setup_component(hass, "stream", {"stream": {}}) + + stream_worker_sync.pause() + + # Setup demo HLS track + source = generate_h264_video() + stream = create_stream(hass, source) + + # Request stream + stream.hls_output() + stream.start() + url = stream.endpoint_url() + + 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 + + stream_worker_sync.resume() + stream.stop() + + # Wait 5 minutes and fire callback. Stream should already have been + # stopped so this is a no-op. + future = dt_util.utcnow() + timedelta(minutes=5) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + async def test_stream_ended(hass, stream_worker_sync): """Test hls stream packets ended.""" await async_setup_component(hass, "stream", {"stream": {}})