From: Marcel van der Veldt Date: Thu, 11 Dec 2025 23:40:33 +0000 (+0100) Subject: Fix for some announcements issues X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=7ca9fc91d4d070d3e2a4f0845fec4a8a8b7b12fa;p=music-assistant-server.git Fix for some announcements issues --- diff --git a/music_assistant/controllers/players/player_controller.py b/music_assistant/controllers/players/player_controller.py index 9d55e87b..85d36c96 100644 --- a/music_assistant/controllers/players/player_controller.py +++ b/music_assistant/controllers/players/player_controller.py @@ -1965,8 +1965,8 @@ class PlayerController(CoreController): await self.wait_for_state( player, PlaybackState.IDLE, - timeout=announcement.duration + 6, - minimal_time=float(announcement.duration), + timeout=announcement.duration + 10, + minimal_time=float(announcement.duration) + 2, ) self.logger.debug( "Announcement to player %s - restore previous state...", player.display_name diff --git a/music_assistant/controllers/streams/streams_controller.py b/music_assistant/controllers/streams/streams_controller.py index b23620b8..bd3dadf4 100644 --- a/music_assistant/controllers/streams/streams_controller.py +++ b/music_assistant/controllers/streams/streams_controller.py @@ -1210,37 +1210,71 @@ class StreamsController(CoreController): pre_announce_url: str = ANNOUNCE_ALERT_FILE, ) -> AsyncGenerator[bytes, None]: """Get the special announcement stream.""" - filter_params = ["loudnorm=I=-10:LRA=11:TP=-2"] - - if pre_announce: - # Note: TTS URLs might take a while to load cause the actual data are often generated - # asynchronously by the TTS provider. If we ask ffmpeg to mix the pre-announce, it will - # wait until it reads the TTS data, so the whole stream will be delayed. It is much - # faster to first play the pre-announce using a separate ffmpeg stream, and only - # afterwards play the TTS itself. - # - # For this to be effective the player itself needs to be able to start playback fast. - # Finally, if the output_format is non-PCM, raw concatenation can be problematic. - # So far players seem to tolerate this, but it might break some player in the future. + announcement_data: asyncio.Queue[bytes | None] = asyncio.Queue(10) + # we are doing announcement in PCM first to avoid multiple encodings + # when mixing pre-announce and announcement + # also we have to deal with some TTS sources being super slow in delivering audio + # so we take an approach where we start fetching the announcement in the background + # while we can already start playing the pre-announce sound (if any) + + pcm_format = ( + output_format + if output_format.content_type.is_pcm() + else AudioFormat( + sample_rate=output_format.sample_rate, + content_type=ContentType.PCM_S16LE, + bit_depth=16, + channels=output_format.channels, + ) + ) + async def fetch_announcement() -> None: + fmt = announcement_url.rsplit(".")[-1] async for chunk in get_ffmpeg_stream( - audio_input=pre_announce_url, - input_format=AudioFormat(content_type=ContentType.try_parse(pre_announce_url)), - output_format=output_format, - filter_params=filter_params, - chunk_size=get_chunksize(output_format, 1), + audio_input=announcement_url, + input_format=AudioFormat(content_type=ContentType.try_parse(fmt)), + output_format=pcm_format, + chunk_size=get_chunksize(pcm_format, 1), ): + await announcement_data.put(chunk) + await announcement_data.put(None) # signal end of stream + + self.mass.create_task(fetch_announcement()) + + async def _announcement_stream() -> AsyncGenerator[bytes, None]: + """Generate the PCM audio stream for the announcement + optional pre-announce.""" + if pre_announce: + async for chunk in get_ffmpeg_stream( + audio_input=pre_announce_url, + input_format=AudioFormat(content_type=ContentType.try_parse(pre_announce_url)), + output_format=pcm_format, + chunk_size=get_chunksize(pcm_format, 1), + ): + yield chunk + # pad silence while we're waiting for the announcement to be ready + while announcement_data.empty(): + yield b"\0" * int( + pcm_format.sample_rate * (pcm_format.bit_depth / 8) * pcm_format.channels + ) + await asyncio.sleep(0.1) + # stream announcement + while True: + announcement_chunk = await announcement_data.get() + if announcement_chunk is None: + break + yield announcement_chunk + + if output_format == pcm_format: + # no need to re-encode, just yield the raw PCM stream + async for chunk in _announcement_stream(): yield chunk + return - # work out output format/details - fmt = announcement_url.rsplit(".")[-1] - audio_format = AudioFormat(content_type=ContentType.try_parse(fmt)) + # stream final announcement in requested output format async for chunk in get_ffmpeg_stream( - audio_input=announcement_url, - input_format=audio_format, + audio_input=_announcement_stream(), + input_format=pcm_format, output_format=output_format, - filter_params=filter_params, - chunk_size=get_chunksize(output_format, 1), ): yield chunk