yield to loop tweaks
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Fri, 7 Nov 2025 10:38:49 +0000 (11:38 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Fri, 7 Nov 2025 10:38:49 +0000 (11:38 +0100)
music_assistant/controllers/players/player_controller.py
music_assistant/helpers/audio.py
music_assistant/helpers/smart_fades.py
music_assistant/providers/airplay/protocols/raop.py

index fdfba72449caaeb7ff7ba01563d9c91c412f43c5..a83eea458955abb29193d7d3ff96f47cbf1880dc 100644 (file)
@@ -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(
index cb212520d43623d8593684abe6b52cfe66050774..9c6b3f51343ddd9a644771fe8828b35830539fda 100644 (file)
@@ -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(
index de74f150884b3d621e538edcb00b1d2ff7ced3e4..edb59dbaa1954a112fa5b75b6a4d82db9ee2c8b4 100644 (file)
@@ -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."""
 
index c258e70b126055d8ed93e49dec8dc8e90ab9a67c..9ece6daaaf699a3c96d2b39f866d41922ee42a35 100644 (file)
@@ -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")