From 33e1c6de68386ea25c072fd3cee19aa85638d0d2 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Sat, 28 Jun 2025 22:20:47 +0200 Subject: [PATCH] Reduce idle timeout of HLS stream to conserve camera battery life (#147728) * Reduce IDLE timeout of HLS stream to conserve camera battery life * adjust tests --- homeassistant/components/stream/__init__.py | 12 ++++++++---- homeassistant/components/stream/const.py | 3 ++- homeassistant/components/stream/core.py | 4 +++- tests/components/stream/test_hls.py | 8 ++++---- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 9426b5b04de..a31ce433c06 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -55,6 +55,7 @@ from .const import ( MAX_SEGMENTS, OUTPUT_FORMATS, OUTPUT_IDLE_TIMEOUT, + OUTPUT_STARTUP_TIMEOUT, RECORDER_PROVIDER, RTSP_TRANSPORTS, SEGMENT_DURATION_ADJUSTER, @@ -363,11 +364,14 @@ class Stream: # without concern about self._outputs being modified from another thread. return MappingProxyType(self._outputs.copy()) - def add_provider( - self, fmt: str, timeout: int = OUTPUT_IDLE_TIMEOUT - ) -> StreamOutput: + def add_provider(self, fmt: str, timeout: int | None = None) -> StreamOutput: """Add provider output stream.""" if not (provider := self._outputs.get(fmt)): + startup_timeout = OUTPUT_STARTUP_TIMEOUT + if timeout is None: + timeout = OUTPUT_IDLE_TIMEOUT + else: + startup_timeout = timeout async def idle_callback() -> None: if ( @@ -379,7 +383,7 @@ class Stream: provider = PROVIDERS[fmt]( self.hass, - IdleTimer(self.hass, timeout, idle_callback), + IdleTimer(self.hass, timeout, idle_callback, startup_timeout), self._stream_settings, self.dynamic_stream_settings, ) diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index c81d2f6cb18..df50ecefd62 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -22,7 +22,8 @@ AUDIO_CODECS = {"aac", "mp3"} FORMAT_CONTENT_TYPE = {HLS_PROVIDER: "application/vnd.apple.mpegurl"} -OUTPUT_IDLE_TIMEOUT = 300 # Idle timeout due to inactivity +OUTPUT_STARTUP_TIMEOUT = 60 # timeout due to no startup +OUTPUT_IDLE_TIMEOUT = 30 # Idle timeout due to inactivity NUM_PLAYLIST_SEGMENTS = 3 # Number of segments to use in HLS playlist MAX_SEGMENTS = 5 # Max number of segments to keep around diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 44dfe2c323d..7dc6bab16b9 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -234,10 +234,12 @@ class IdleTimer: hass: HomeAssistant, timeout: int, idle_callback: Callable[[], Coroutine[Any, Any, None]], + startup_timeout: int | None = None, ) -> None: """Initialize IdleTimer.""" self._hass = hass self._timeout = timeout + self._startup_timeout = startup_timeout or timeout self._callback = idle_callback self._unsub: CALLBACK_TYPE | None = None self.idle = False @@ -246,7 +248,7 @@ class IdleTimer: """Start the idle timer if not already started.""" self.idle = False if self._unsub is None: - self._unsub = async_call_later(self._hass, self._timeout, self.fire) + self._unsub = async_call_later(self._hass, self._startup_timeout, self.fire) def awake(self) -> None: """Keep the idle time alive by resetting the timeout.""" diff --git a/tests/components/stream/test_hls.py b/tests/components/stream/test_hls.py index c96b7d9427f..eb554f2cf19 100644 --- a/tests/components/stream/test_hls.py +++ b/tests/components/stream/test_hls.py @@ -230,8 +230,8 @@ async def test_stream_timeout( playlist_response = await http_client.get(parsed_url.path) assert playlist_response.status == HTTPStatus.OK - # Wait a minute - future = dt_util.utcnow() + timedelta(minutes=1) + # Wait 40 seconds + future = dt_util.utcnow() + timedelta(seconds=40) async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -241,8 +241,8 @@ async def test_stream_timeout( stream_worker_sync.resume() - # Wait 5 minutes - future = dt_util.utcnow() + timedelta(minutes=5) + # Wait 2 minutes + future = dt_util.utcnow() + timedelta(minutes=2) async_fire_time_changed(hass, future) await hass.async_block_till_done()