Enable Low Latency HLS (LL-HLS) by default to lower stream latency (#64643)

This commit is contained in:
Allen Porter 2022-01-23 06:38:29 -08:00 committed by GitHub
parent 4e376181f5
commit e74fe0e390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 47 additions and 40 deletions

View File

@ -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(

View File

@ -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()