Minor cleanup on stream (#58486)

* Allow for rounding errors in playlist validation

* Allow EXT-X-TARGETDURATION to change

* Reuse original source in test_record_stream_audio
This commit is contained in:
uvjustin 2021-10-27 04:11:33 +08:00 committed by GitHub
parent 577d8b1469
commit 7024a5d7d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 30 additions and 19 deletions

View File

@ -18,7 +18,14 @@ from .const import (
MAX_SEGMENTS,
NUM_PLAYLIST_SEGMENTS,
)
from .core import PROVIDERS, IdleTimer, StreamOutput, StreamSettings, StreamView
from .core import (
PROVIDERS,
IdleTimer,
Segment,
StreamOutput,
StreamSettings,
StreamView,
)
from .fmp4utils import get_codec_string
if TYPE_CHECKING:
@ -44,7 +51,7 @@ class HlsStreamOutput(StreamOutput):
"""Initialize HLS output."""
super().__init__(hass, idle_timer, deque_maxlen=MAX_SEGMENTS)
self.stream_settings: StreamSettings = hass.data[DOMAIN][ATTR_SETTINGS]
self._target_duration = 0.0
self._target_duration = self.stream_settings.min_segment_duration
@property
def name(self) -> str:
@ -53,19 +60,22 @@ class HlsStreamOutput(StreamOutput):
@property
def target_duration(self) -> float:
"""
Return the target duration.
"""Return the target duration."""
return self._target_duration
The target duration is calculated as the max duration of any given segment,
and it is calculated only one time to avoid changing during playback.
@callback
def _async_put(self, segment: Segment) -> None:
"""Async put and also update the target duration.
The target duration is calculated as the max duration of any given segment.
Technically it should not change per the hls spec, but some cameras adjust
their GOPs periodically so we need to account for this change.
"""
if self._target_duration:
return self._target_duration
durations = [s.duration for s in self._segments if s.complete]
if len(durations) < 2:
return self.stream_settings.min_segment_duration
self._target_duration = max(durations)
return self._target_duration
super()._async_put(segment)
self._target_duration = (
max((s.duration for s in self._segments), default=segment.duration)
or self.stream_settings.min_segment_duration
)
class HlsMasterPlaylistView(StreamView):

View File

@ -224,7 +224,9 @@ async def test_ll_hls_stream(hass, hls_stream, stream_worker_sync):
datetimes[-1] - datetimes.popleft()
).total_seconds()
if segment_duration:
assert datetime_duration == segment_duration
assert math.isclose(
datetime_duration, segment_duration, rel_tol=1e-3
)
tested[datetime_re] = True
continue
match = inf_re.match(line)
@ -232,7 +234,7 @@ async def test_ll_hls_stream(hass, hls_stream, stream_worker_sync):
segment_duration = float(match.group("segment_duration"))
# Check that segment durations are consistent with part durations
if len(part_durations) > 1:
assert math.isclose(sum(part_durations), segment_duration)
assert math.isclose(sum(part_durations), segment_duration, rel_tol=1e-3)
tested[inf_re] = True
part_durations.clear()
# make sure all playlist tests were performed

View File

@ -194,7 +194,7 @@ async def test_record_stream_audio(
await async_setup_component(hass, "stream", {"stream": {}})
# Generate source video with no audio
source = generate_h264_video(container_format="mov")
orig_source = generate_h264_video(container_format="mov")
for a_codec, expected_audio_streams in (
("aac", 1), # aac is a valid mp4 codec
@ -202,9 +202,8 @@ async def test_record_stream_audio(
("empty", 0), # audio stream with no packets
(None, 0), # no audio stream
):
# Remux source video with new audio
source = remux_with_audio(source, "mov", a_codec) # mov can store PCM
source = remux_with_audio(orig_source, "mov", a_codec) # mov can store PCM
record_worker_sync.reset()
stream_worker_sync.pause()