Skip to content

Commit e0bcad0

Browse files
committed
some tweaks to stream to handle edge cases
1 parent c3ee2bc commit e0bcad0

File tree

4 files changed

+32
-19
lines changed

4 files changed

+32
-19
lines changed

music_assistant/controllers/streams.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,8 @@ async def serve_queue_item_stream(self, request: web.Request) -> web.Response:
449449
):
450450
# crossfade is not supported on this player due to missing gapless playback
451451
self.logger.warning(
452-
"Crossfade disabled: Player %s does not support gapless playback",
452+
"Crossfade disabled: Player %s does not support gapless playback, "
453+
"consider enabling flow mode to enable crossfade on this player.",
453454
queue_player.display_name if queue_player else "Unknown Player",
454455
)
455456
smart_fades_mode = SmartFadesMode.DISABLED
@@ -493,7 +494,7 @@ async def serve_queue_item_stream(self, request: web.Request) -> web.Response:
493494
if queue_item.media_type == MediaType.RADIO:
494495
# keep very short buffer for radio streams
495496
# to keep them (more or less) realtime and prevent time outs
496-
read_rate_input_args = ["-readrate", "1.00", "-readrate_initial_burst", "1"]
497+
read_rate_input_args = ["-readrate", "1.01", "-readrate_initial_burst", "3"]
497498
elif "Network_Module" in user_agent or "transferMode.dlna.org" in request.headers:
498499
# and ofcourse we have an exception of the exception. Where most players actually NEED
499500
# the readrate filter to avoid disconnecting, some other players (DLNA/MusicCast)

music_assistant/helpers/audio.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,15 +668,15 @@ async def get_media_stream(
668668
elif ffmpeg_proc.returncode not in (0, None):
669669
raise AudioError(f"FFMpeg exited with code {ffmpeg_proc.returncode}")
670670
finished = True
671-
except (Exception, GeneratorExit) as err:
671+
except (Exception, GeneratorExit, asyncio.CancelledError) as err:
672672
if isinstance(err, asyncio.CancelledError | GeneratorExit):
673673
# we were cancelled, just raise
674674
cancelled = True
675-
raise
676675
logger.error("Error while streaming %s: %s", streamdetails.uri, err)
677676
# dump the last 10 lines of the log in case of an unclean exit
678677
logger.warning("\n".join(list(ffmpeg_proc.log_history)[-10:]))
679678
streamdetails.stream_error = True
679+
raise
680680
finally:
681681
# always ensure close is called which also handles all cleanup
682682
await ffmpeg_proc.close()

music_assistant/helpers/ffmpeg.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,14 @@ async def communicate(
103103
timeout: float | None = None,
104104
) -> tuple[bytes, bytes]:
105105
"""Override communicate to avoid blocking."""
106-
if self._stdin_task and not self._stdin_task.done():
107-
self._stdin_task.cancel()
108-
with suppress(asyncio.CancelledError):
106+
if self._stdin_task:
107+
if not self._stdin_task.done():
108+
self._stdin_task.cancel()
109+
# Always await the task to consume any exception and prevent
110+
# "Task exception was never retrieved" errors.
111+
# Suppress CancelledError (from cancel) and any other exception
112+
# since exceptions have already been propagated through the generator chain.
113+
with suppress(asyncio.CancelledError, Exception):
109114
await self._stdin_task
110115
if self._logger_task and not self._logger_task.done():
111116
self._logger_task.cancel()
@@ -115,9 +120,14 @@ async def close(self, send_signal: bool = True) -> None:
115120
"""Close/terminate the process and wait for exit."""
116121
if self.closed:
117122
return
118-
if self._stdin_task and not self._stdin_task.done():
119-
self._stdin_task.cancel()
120-
with suppress(asyncio.CancelledError):
123+
if self._stdin_task:
124+
if not self._stdin_task.done():
125+
self._stdin_task.cancel()
126+
# Always await the task to consume any exception and prevent
127+
# "Task exception was never retrieved" errors.
128+
# Suppress CancelledError (from cancel) and any other exception
129+
# since exceptions have already been propagated through the generator chain.
130+
with suppress(asyncio.CancelledError, Exception):
121131
await self._stdin_task
122132
await super().close(send_signal)
123133
if self._logger_task and not self._logger_task.done():

music_assistant/helpers/process.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,17 @@ async def close(self, send_signal: bool = False) -> None:
234234
if self.proc.stdin and not self.proc.stdin.is_closing():
235235
self.proc.stdin.close()
236236
# abort existing readers on stderr/stdout first before we send communicate
237-
waiter: asyncio.Future[None]
238-
if self.proc.stdout and (waiter := self.proc.stdout._waiter): # type: ignore[attr-defined]
239-
self.proc.stdout._waiter = None # type: ignore[attr-defined]
240-
if waiter and not waiter.done():
241-
waiter.set_exception(asyncio.CancelledError())
242-
if self.proc.stderr and (waiter := self.proc.stderr._waiter): # type: ignore[attr-defined]
243-
self.proc.stderr._waiter = None # type: ignore[attr-defined]
244-
if waiter and not waiter.done():
245-
waiter.set_exception(asyncio.CancelledError())
237+
# waiter: asyncio.Future[None]
238+
# stdout_waiter = self.proc.stdout._waiter # type: ignore[attr-defined]
239+
# if self.proc.stdout and stdout_waiter:
240+
# self.proc.stdout._waiter = None # type: ignore[attr-defined]
241+
# if stdout_waiter and not stdout_waiter.done():
242+
# stdout_waiter.set_exception(asyncio.CancelledError())
243+
# stderr_waiter = self.proc.stderr._waiter # type: ignore[attr-defined]
244+
# if self.proc.stderr and stderr_waiter:
245+
# self.proc.stderr._waiter = None # type: ignore[attr-defined]
246+
# if stderr_waiter and not stderr_waiter.done():
247+
# stderr_waiter.set_exception(asyncio.CancelledError())
246248
await asyncio.sleep(0) # yield to loop
247249

248250
# make sure the process is really cleaned up.

0 commit comments

Comments
 (0)