Send keep-alive frames in image proxy stream (#113542)

This commit is contained in:
On Freund 2024-03-16 02:00:33 +02:00 committed by GitHub
parent 51ece8b1ef
commit b644c03fa7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 7 deletions

View file

@ -339,25 +339,43 @@ async def async_get_still_stream(
return True return True
event = asyncio.Event() event = asyncio.Event()
timed_out = False
@callback @callback
def _async_image_state_update(_event: Event[EventStateChangedData]) -> None: def _async_image_state_update(_event: Event[EventStateChangedData]) -> None:
"""Write image to stream.""" """Write image to stream."""
event.set() event.set()
@callback
def _async_timeout_reached() -> None:
"""Handle timeout."""
nonlocal timed_out
timed_out = True
event.set()
hass = request.app[KEY_HASS] hass = request.app[KEY_HASS]
loop = hass.loop
remove = async_track_state_change_event( remove = async_track_state_change_event(
hass, hass,
image_entity.entity_id, image_entity.entity_id,
_async_image_state_update, _async_image_state_update,
) )
timeout_handle = None
try: try:
while True: while True:
if not await _write_frame(): if not await _write_frame():
return response return response
# Ensure that an image is sent at least every 55 seconds
# Otherwise some devices go blank
timeout_handle = loop.call_later(55, _async_timeout_reached)
await event.wait() await event.wait()
event.clear() event.clear()
if not timed_out:
timeout_handle.cancel()
timed_out = False
finally: finally:
if timeout_handle:
timeout_handle.cancel()
remove() remove()

View file

@ -1,11 +1,12 @@
"""The tests for the image component.""" """The tests for the image component."""
import datetime from datetime import datetime
from http import HTTPStatus from http import HTTPStatus
import ssl import ssl
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from aiohttp import hdrs from aiohttp import hdrs
from freezegun.api import FrozenDateTimeFactory
import httpx import httpx
import pytest import pytest
import respx import respx
@ -24,7 +25,12 @@ from .conftest import (
MockURLImageEntity, MockURLImageEntity,
) )
from tests.common import MockModule, mock_integration, mock_platform from tests.common import (
MockModule,
async_fire_time_changed,
mock_integration,
mock_platform,
)
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
@ -292,7 +298,9 @@ async def test_fetch_image_url_wrong_content_type(
async def test_image_stream( async def test_image_stream(
hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant,
hass_client: ClientSessionGenerator,
freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test image stream.""" """Test image stream."""
@ -323,18 +331,26 @@ async def test_image_stream(
assert not resp.closed assert not resp.closed
assert resp.status == HTTPStatus.OK assert resp.status == HTTPStatus.OK
mock_image.image_last_updated = datetime.datetime.now() mock_image.image_last_updated = datetime.now()
mock_image.async_write_ha_state() mock_image.async_write_ha_state()
# Two blocks to ensure the frame is written # Two blocks to ensure the frame is written
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done() await hass.async_block_till_done()
with patch.object(mock_image, "async_image", return_value=b"") as mock:
# Simulate a "keep alive" frame
freezer.tick(55)
async_fire_time_changed(hass)
# Two blocks to ensure the frame is written
await hass.async_block_till_done()
await hass.async_block_till_done()
mock.assert_called_once()
with patch.object(mock_image, "async_image", return_value=None): with patch.object(mock_image, "async_image", return_value=None):
mock_image.image_last_updated = datetime.datetime.now() freezer.tick(55)
mock_image.async_write_ha_state() async_fire_time_changed(hass)
# Two blocks to ensure the frame is written # Two blocks to ensure the frame is written
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done() await hass.async_block_till_done()
await close_future await close_future
assert resp.closed