From: Marcel van der Veldt Date: Fri, 7 Nov 2025 10:38:49 +0000 (+0100) Subject: yield to loop tweaks X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=2acadec0e07c8ae7bcd67bb72ab672d86f23f48a;p=music-assistant-server.git yield to loop tweaks --- diff --git a/music_assistant/controllers/players/player_controller.py b/music_assistant/controllers/players/player_controller.py index fdfba724..a83eea45 100644 --- a/music_assistant/controllers/players/player_controller.py +++ b/music_assistant/controllers/players/player_controller.py @@ -2135,6 +2135,8 @@ class PlayerController(CoreController): str(err), exc_info=err if self.logger.isEnabledFor(10) else None, ) + # Yield to event loop to prevent blocking + await asyncio.sleep(0) await asyncio.sleep(1) async def _handle_select_plugin_source( diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py index cb212520..9c6b3f51 100644 --- a/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py @@ -80,6 +80,24 @@ CACHE_PROVIDER: Final[str] = "audio" STREAMDETAILS_EXPIRATION: Final[int] = 60 * 15 # 15 minutes +def align_audio_to_frame_boundary(audio_data: bytes, pcm_format: AudioFormat) -> bytes: + """Align audio data to frame boundaries by truncating incomplete frames. + + :param audio_data: Raw PCM audio data to align. + :param pcm_format: AudioFormat of the audio data. + """ + bytes_per_sample = pcm_format.bit_depth // 8 + frame_size = bytes_per_sample * pcm_format.channels + valid_bytes = (len(audio_data) // frame_size) * frame_size + if valid_bytes != len(audio_data): + LOGGER.debug( + "Truncating %d bytes from audio buffer to align to frame boundary", + len(audio_data) - valid_bytes, + ) + return audio_data[:valid_bytes] + return audio_data + + async def crossfade_pcm_parts( fade_in_part: bytes, fade_out_part: bytes, @@ -458,6 +476,8 @@ async def get_buffered_media_stream( ): chunk_count += 1 await audio_buffer.put(chunk) + # Yield to event loop to prevent blocking warnings + await asyncio.sleep(0) # Only set EOF if we completed successfully await audio_buffer.set_eof() except asyncio.CancelledError: @@ -1245,11 +1265,6 @@ async def resample_pcm_audio( ) return b"" # Ensure frame alignment after resampling - # Import inline to avoid circular dependency at module level - from music_assistant.helpers.smart_fades import ( # noqa: PLC0415 - align_audio_to_frame_boundary, - ) - return align_audio_to_frame_boundary(stdout, output_format) except Exception as err: LOGGER.exception( diff --git a/music_assistant/helpers/smart_fades.py b/music_assistant/helpers/smart_fades.py index de74f150..edb59dba 100644 --- a/music_assistant/helpers/smart_fades.py +++ b/music_assistant/helpers/smart_fades.py @@ -19,7 +19,11 @@ import numpy.typing as npt import shortuuid from music_assistant.constants import VERBOSE_LOG_LEVEL -from music_assistant.helpers.audio import crossfade_pcm_parts, strip_silence +from music_assistant.helpers.audio import ( + align_audio_to_frame_boundary, + crossfade_pcm_parts, + strip_silence, +) from music_assistant.helpers.process import communicate from music_assistant.helpers.util import remove_file from music_assistant.models.smart_fades import ( @@ -40,20 +44,6 @@ ANALYSIS_FPS = 100 TIME_STRETCH_BPM_PERCENTAGE_THRESHOLD = 5.0 -def align_audio_to_frame_boundary(audio_data: bytes, pcm_format: AudioFormat) -> bytes: - """Align audio data to frame boundaries by truncating incomplete frames.""" - bytes_per_sample = pcm_format.bit_depth // 8 - frame_size = bytes_per_sample * pcm_format.channels - valid_bytes = (len(audio_data) // frame_size) * frame_size - if valid_bytes != len(audio_data): - logging.getLogger(__name__).debug( - "Truncating %d bytes from audio buffer to align to frame boundary", - len(audio_data) - valid_bytes, - ) - return audio_data[:valid_bytes] - return audio_data - - class SmartFadesAnalyzer: """Smart fades analyzer that performs audio analysis.""" diff --git a/music_assistant/providers/airplay/protocols/raop.py b/music_assistant/providers/airplay/protocols/raop.py index c258e70b..9ece6daa 100644 --- a/music_assistant/providers/airplay/protocols/raop.py +++ b/music_assistant/providers/airplay/protocols/raop.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio import logging from typing import TYPE_CHECKING, cast @@ -196,6 +197,7 @@ class RaopStream(AirPlayProtocol): logger.debug("End of stream reached") break logger.log(VERBOSE_LOG_LEVEL, line) + await asyncio.sleep(0) # Yield to event loop # ensure we're cleaned up afterwards (this also logs the returncode) logger.debug("CLIRaop stderr reader ended")