mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
Enable Low Latency HLS (LL-HLS) by default to lower stream latency (#64643)
This commit is contained in:
parent
4e376181f5
commit
e74fe0e390
@ -97,19 +97,21 @@ def create_stream(
|
|||||||
return stream
|
return stream
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_LL_HLS, default=True): cv.boolean,
|
||||||
|
vol.Optional(CONF_SEGMENT_DURATION, default=6): vol.All(
|
||||||
|
cv.positive_float, vol.Range(min=2, max=10)
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_PART_DURATION, default=1): vol.All(
|
||||||
|
cv.positive_float, vol.Range(min=0.2, max=1.5)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: DOMAIN_SCHEMA,
|
||||||
{
|
|
||||||
vol.Optional(CONF_LL_HLS, default=False): cv.boolean,
|
|
||||||
vol.Optional(CONF_SEGMENT_DURATION, default=6): vol.All(
|
|
||||||
cv.positive_float, vol.Range(min=2, max=10)
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_PART_DURATION, default=1): vol.All(
|
|
||||||
cv.positive_float, vol.Range(min=0.2, max=1.5)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
@ -154,7 +156,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {}
|
||||||
hass.data[DOMAIN][ATTR_ENDPOINTS] = {}
|
hass.data[DOMAIN][ATTR_ENDPOINTS] = {}
|
||||||
hass.data[DOMAIN][ATTR_STREAMS] = []
|
hass.data[DOMAIN][ATTR_STREAMS] = []
|
||||||
if (conf := config.get(DOMAIN)) and conf[CONF_LL_HLS]:
|
conf = DOMAIN_SCHEMA(config.get(DOMAIN, {}))
|
||||||
|
if conf[CONF_LL_HLS]:
|
||||||
assert isinstance(conf[CONF_SEGMENT_DURATION], float)
|
assert isinstance(conf[CONF_SEGMENT_DURATION], float)
|
||||||
assert isinstance(conf[CONF_PART_DURATION], float)
|
assert isinstance(conf[CONF_PART_DURATION], float)
|
||||||
hass.data[DOMAIN][ATTR_SETTINGS] = StreamSettings(
|
hass.data[DOMAIN][ATTR_SETTINGS] = StreamSettings(
|
||||||
|
@ -29,6 +29,18 @@ SEGMENT_DURATION = 10
|
|||||||
TEST_TIMEOUT = 5.0 # Lower than 9s home assistant timeout
|
TEST_TIMEOUT = 5.0 # Lower than 9s home assistant timeout
|
||||||
MAX_ABORT_SEGMENTS = 20 # Abort test to avoid looping forever
|
MAX_ABORT_SEGMENTS = 20 # Abort test to avoid looping forever
|
||||||
|
|
||||||
|
HLS_CONFIG = {
|
||||||
|
"stream": {
|
||||||
|
"ll_hls": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def setup_component(hass) -> None:
|
||||||
|
"""Test fixture to setup the stream component."""
|
||||||
|
await async_setup_component(hass, "stream", HLS_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
class HlsClient:
|
class HlsClient:
|
||||||
"""Test fixture for fetching the hls stream."""
|
"""Test fixture for fetching the hls stream."""
|
||||||
@ -114,14 +126,15 @@ def make_playlist(
|
|||||||
return "\n".join(response)
|
return "\n".join(response)
|
||||||
|
|
||||||
|
|
||||||
async def test_hls_stream(hass, hls_stream, stream_worker_sync, h264_video):
|
async def test_hls_stream(
|
||||||
|
hass, setup_component, hls_stream, stream_worker_sync, h264_video
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Test hls stream.
|
Test hls stream.
|
||||||
|
|
||||||
Purposefully not mocking anything here to test full
|
Purposefully not mocking anything here to test full
|
||||||
integration with the stream component.
|
integration with the stream component.
|
||||||
"""
|
"""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
|
|
||||||
@ -164,10 +177,10 @@ async def test_hls_stream(hass, hls_stream, stream_worker_sync, h264_video):
|
|||||||
assert fail_response.status == HTTPStatus.NOT_FOUND
|
assert fail_response.status == HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
async def test_stream_timeout(hass, hass_client, stream_worker_sync, h264_video):
|
async def test_stream_timeout(
|
||||||
|
hass, hass_client, setup_component, stream_worker_sync, h264_video
|
||||||
|
):
|
||||||
"""Test hls stream timeout."""
|
"""Test hls stream timeout."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
|
|
||||||
# Setup demo HLS track
|
# Setup demo HLS track
|
||||||
@ -217,11 +230,9 @@ async def test_stream_timeout(hass, hass_client, stream_worker_sync, h264_video)
|
|||||||
|
|
||||||
|
|
||||||
async def test_stream_timeout_after_stop(
|
async def test_stream_timeout_after_stop(
|
||||||
hass, hass_client, stream_worker_sync, h264_video
|
hass, hass_client, setup_component, stream_worker_sync, h264_video
|
||||||
):
|
):
|
||||||
"""Test hls stream timeout after the stream has been stopped already."""
|
"""Test hls stream timeout after the stream has been stopped already."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
|
|
||||||
# Setup demo HLS track
|
# Setup demo HLS track
|
||||||
@ -241,10 +252,8 @@ async def test_stream_timeout_after_stop(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
async def test_stream_keepalive(hass):
|
async def test_stream_keepalive(hass, setup_component):
|
||||||
"""Test hls stream retries the stream when keepalive=True."""
|
"""Test hls stream retries the stream when keepalive=True."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
# Setup demo HLS track
|
# Setup demo HLS track
|
||||||
source = "test_stream_keepalive_source"
|
source = "test_stream_keepalive_source"
|
||||||
stream = create_stream(hass, source, {})
|
stream = create_stream(hass, source, {})
|
||||||
@ -291,10 +300,8 @@ async def test_stream_keepalive(hass):
|
|||||||
assert available_states == [True, False, True]
|
assert available_states == [True, False, True]
|
||||||
|
|
||||||
|
|
||||||
async def test_hls_playlist_view_no_output(hass, hls_stream):
|
async def test_hls_playlist_view_no_output(hass, setup_component, hls_stream):
|
||||||
"""Test rendering the hls playlist with no output segments."""
|
"""Test rendering the hls playlist with no output segments."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream = create_stream(hass, STREAM_SOURCE, {})
|
stream = create_stream(hass, STREAM_SOURCE, {})
|
||||||
stream.add_provider(HLS_PROVIDER)
|
stream.add_provider(HLS_PROVIDER)
|
||||||
|
|
||||||
@ -305,10 +312,8 @@ async def test_hls_playlist_view_no_output(hass, hls_stream):
|
|||||||
assert resp.status == HTTPStatus.NOT_FOUND
|
assert resp.status == HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
async def test_hls_playlist_view(hass, hls_stream, stream_worker_sync):
|
async def test_hls_playlist_view(hass, setup_component, hls_stream, stream_worker_sync):
|
||||||
"""Test rendering the hls playlist with 1 and 2 output segments."""
|
"""Test rendering the hls playlist with 1 and 2 output segments."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream = create_stream(hass, STREAM_SOURCE, {})
|
stream = create_stream(hass, STREAM_SOURCE, {})
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
hls = stream.add_provider(HLS_PROVIDER)
|
hls = stream.add_provider(HLS_PROVIDER)
|
||||||
@ -338,10 +343,8 @@ async def test_hls_playlist_view(hass, hls_stream, stream_worker_sync):
|
|||||||
stream.stop()
|
stream.stop()
|
||||||
|
|
||||||
|
|
||||||
async def test_hls_max_segments(hass, hls_stream, stream_worker_sync):
|
async def test_hls_max_segments(hass, setup_component, hls_stream, stream_worker_sync):
|
||||||
"""Test rendering the hls playlist with more segments than the segment deque can hold."""
|
"""Test rendering the hls playlist with more segments than the segment deque can hold."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream = create_stream(hass, STREAM_SOURCE, {})
|
stream = create_stream(hass, STREAM_SOURCE, {})
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
hls = stream.add_provider(HLS_PROVIDER)
|
hls = stream.add_provider(HLS_PROVIDER)
|
||||||
@ -389,9 +392,10 @@ async def test_hls_max_segments(hass, hls_stream, stream_worker_sync):
|
|||||||
stream.stop()
|
stream.stop()
|
||||||
|
|
||||||
|
|
||||||
async def test_hls_playlist_view_discontinuity(hass, hls_stream, stream_worker_sync):
|
async def test_hls_playlist_view_discontinuity(
|
||||||
|
hass, setup_component, hls_stream, stream_worker_sync
|
||||||
|
):
|
||||||
"""Test a discontinuity across segments in the stream with 3 segments."""
|
"""Test a discontinuity across segments in the stream with 3 segments."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream = create_stream(hass, STREAM_SOURCE, {})
|
stream = create_stream(hass, STREAM_SOURCE, {})
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
@ -426,10 +430,10 @@ async def test_hls_playlist_view_discontinuity(hass, hls_stream, stream_worker_s
|
|||||||
stream.stop()
|
stream.stop()
|
||||||
|
|
||||||
|
|
||||||
async def test_hls_max_segments_discontinuity(hass, hls_stream, stream_worker_sync):
|
async def test_hls_max_segments_discontinuity(
|
||||||
|
hass, setup_component, hls_stream, stream_worker_sync
|
||||||
|
):
|
||||||
"""Test a discontinuity with more segments than the segment deque can hold."""
|
"""Test a discontinuity with more segments than the segment deque can hold."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream = create_stream(hass, STREAM_SOURCE, {})
|
stream = create_stream(hass, STREAM_SOURCE, {})
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
hls = stream.add_provider(HLS_PROVIDER)
|
hls = stream.add_provider(HLS_PROVIDER)
|
||||||
@ -469,10 +473,10 @@ async def test_hls_max_segments_discontinuity(hass, hls_stream, stream_worker_sy
|
|||||||
stream.stop()
|
stream.stop()
|
||||||
|
|
||||||
|
|
||||||
async def test_remove_incomplete_segment_on_exit(hass, stream_worker_sync):
|
async def test_remove_incomplete_segment_on_exit(
|
||||||
|
hass, setup_component, stream_worker_sync
|
||||||
|
):
|
||||||
"""Test that the incomplete segment gets removed when the worker thread quits."""
|
"""Test that the incomplete segment gets removed when the worker thread quits."""
|
||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
|
||||||
|
|
||||||
stream = create_stream(hass, STREAM_SOURCE, {})
|
stream = create_stream(hass, STREAM_SOURCE, {})
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
stream.start()
|
stream.start()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user