From: Marcel van der Veldt Date: Mon, 10 Mar 2025 16:04:06 +0000 (+0100) Subject: Fix: Ensure we pad some silence before any live/radio stream to prevent jitter X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=524e270db369c8625fdaf45253b62ace59c2df66;p=music-assistant-server.git Fix: Ensure we pad some silence before any live/radio stream to prevent jitter --- diff --git a/music_assistant/controllers/streams.py b/music_assistant/controllers/streams.py index d3b37837..f4f5f31e 100644 --- a/music_assistant/controllers/streams.py +++ b/music_assistant/controllers/streams.py @@ -949,25 +949,30 @@ class StreamsController(CoreController): filter_params.append(f"volume={gain_correct}dB") streamdetails.volume_normalization_gain_correct = gain_correct - if streamdetails.media_type == MediaType.RADIO: - # pad some silence before the radio stream starts to create some headroom - # for radio stations that do not provide any look ahead buffer - # without this, some radio streams jitter a lot, especially with dynamic normalization - pad_seconds = ( - 5 - if streamdetails.volume_normalization_mode == VolumeNormalizationMode.DYNAMIC - else 2 - ) - async for chunk in get_silence(pad_seconds, pcm_format): - yield chunk - + pad_silence_seconds = 0 + if streamdetails.media_type == MediaType.RADIO or not streamdetails.duration: + # pad some silence before the radio/live stream starts to create some headroom + # for radio stations (or other live streams) that do not provide any look ahead buffer + # without this, some radio streams jitter a lot, especially with dynamic normalization, + # if the stream does not provide a look ahead buffer + pad_silence_seconds = 2 + + first_chunk_received = False async for chunk in get_media_stream( self.mass, streamdetails=streamdetails, pcm_format=pcm_format, filter_params=filter_params, ): + # yield silence when the chunk has been received from source but not yet sent to player + # so we have a bit of backpressure to prevent jittering + if not first_chunk_received and pad_silence_seconds: + first_chunk_received = True + async for silence in get_silence(pad_silence_seconds, pcm_format): + yield silence + del silence yield chunk + del chunk def _log_request(self, request: web.Request) -> None: """Log request.""" diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py index 185642d3..1dde1d2b 100644 --- a/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py @@ -597,7 +597,7 @@ async def get_media_stream( substream = await get_hls_substream(mass, streamdetails.path) audio_source = substream.path if streamdetails.media_type == MediaType.RADIO: - # Especially the BBC streams struggle when they're played directly + # HLS streams (especially the BBC) struggle when they're played directly # with ffmpeg, where they just stop after some minutes, # so we tell ffmpeg to loop around in this case. extra_input_args += ["-stream_loop", "-1", "-re"]