Send keep-alive frames in image proxy stream (#113542)
This commit is contained in:
parent
51ece8b1ef
commit
b644c03fa7
2 changed files with 41 additions and 7 deletions
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue