mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
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:
parent
577d8b1469
commit
7024a5d7d9
@ -18,7 +18,14 @@ from .const import (
|
|||||||
MAX_SEGMENTS,
|
MAX_SEGMENTS,
|
||||||
NUM_PLAYLIST_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
|
from .fmp4utils import get_codec_string
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -44,7 +51,7 @@ class HlsStreamOutput(StreamOutput):
|
|||||||
"""Initialize HLS output."""
|
"""Initialize HLS output."""
|
||||||
super().__init__(hass, idle_timer, deque_maxlen=MAX_SEGMENTS)
|
super().__init__(hass, idle_timer, deque_maxlen=MAX_SEGMENTS)
|
||||||
self.stream_settings: StreamSettings = hass.data[DOMAIN][ATTR_SETTINGS]
|
self.stream_settings: StreamSettings = hass.data[DOMAIN][ATTR_SETTINGS]
|
||||||
self._target_duration = 0.0
|
self._target_duration = self.stream_settings.min_segment_duration
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -53,19 +60,22 @@ class HlsStreamOutput(StreamOutput):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def target_duration(self) -> float:
|
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,
|
@callback
|
||||||
and it is calculated only one time to avoid changing during playback.
|
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:
|
super()._async_put(segment)
|
||||||
return self._target_duration
|
self._target_duration = (
|
||||||
durations = [s.duration for s in self._segments if s.complete]
|
max((s.duration for s in self._segments), default=segment.duration)
|
||||||
if len(durations) < 2:
|
or self.stream_settings.min_segment_duration
|
||||||
return self.stream_settings.min_segment_duration
|
)
|
||||||
self._target_duration = max(durations)
|
|
||||||
return self._target_duration
|
|
||||||
|
|
||||||
|
|
||||||
class HlsMasterPlaylistView(StreamView):
|
class HlsMasterPlaylistView(StreamView):
|
||||||
|
@ -224,7 +224,9 @@ async def test_ll_hls_stream(hass, hls_stream, stream_worker_sync):
|
|||||||
datetimes[-1] - datetimes.popleft()
|
datetimes[-1] - datetimes.popleft()
|
||||||
).total_seconds()
|
).total_seconds()
|
||||||
if segment_duration:
|
if segment_duration:
|
||||||
assert datetime_duration == segment_duration
|
assert math.isclose(
|
||||||
|
datetime_duration, segment_duration, rel_tol=1e-3
|
||||||
|
)
|
||||||
tested[datetime_re] = True
|
tested[datetime_re] = True
|
||||||
continue
|
continue
|
||||||
match = inf_re.match(line)
|
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"))
|
segment_duration = float(match.group("segment_duration"))
|
||||||
# Check that segment durations are consistent with part durations
|
# Check that segment durations are consistent with part durations
|
||||||
if len(part_durations) > 1:
|
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
|
tested[inf_re] = True
|
||||||
part_durations.clear()
|
part_durations.clear()
|
||||||
# make sure all playlist tests were performed
|
# make sure all playlist tests were performed
|
||||||
|
@ -194,7 +194,7 @@ async def test_record_stream_audio(
|
|||||||
await async_setup_component(hass, "stream", {"stream": {}})
|
await async_setup_component(hass, "stream", {"stream": {}})
|
||||||
|
|
||||||
# Generate source video with no audio
|
# 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 (
|
for a_codec, expected_audio_streams in (
|
||||||
("aac", 1), # aac is a valid mp4 codec
|
("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
|
("empty", 0), # audio stream with no packets
|
||||||
(None, 0), # no audio stream
|
(None, 0), # no audio stream
|
||||||
):
|
):
|
||||||
|
|
||||||
# Remux source video with new audio
|
# 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()
|
record_worker_sync.reset()
|
||||||
stream_worker_sync.pause()
|
stream_worker_sync.pause()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user