Skip to content

Commit fd9f1a0

Browse files
committed
Fix: ensure playback continues even after a stream error
1 parent 0b1ff92 commit fd9f1a0

File tree

4 files changed

+32
-24
lines changed

4 files changed

+32
-24
lines changed

music_assistant/controllers/player_queues.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1204,7 +1204,7 @@ def track_loaded_in_buffer(self, queue_id: str, item_id: str) -> None:
12041204
return
12051205
# enqueue next track on the player if we're not in flow mode
12061206
task_id = f"enqueue_next_item_{queue_id}"
1207-
self.mass.call_later(5, self._enqueue_next_item, queue_id, item_id, task_id=task_id)
1207+
self.mass.call_later(2, self._enqueue_next_item, queue_id, item_id, task_id=task_id)
12081208

12091209
# Main queue manipulation methods
12101210

music_assistant/controllers/streams.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ async def serve_queue_item_stream(self, request: web.Request) -> web.Response:
403403
queue_item.uri,
404404
queue.display_name,
405405
)
406+
# some players do not like it when we dont return anything after an error
407+
# so we send some silence so they move on to the next track on their own (hopefully)
408+
async for chunk in get_silence(10, output_format):
409+
await resp.write(chunk)
406410
return resp
407411

408412
async def serve_queue_flow_stream(self, request: web.Request) -> web.Response:

music_assistant/helpers/audio.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
VolumeNormalizationMode,
2323
)
2424
from music_assistant_models.errors import (
25+
AudioError,
2526
InvalidDataError,
2627
MediaNotFoundError,
2728
MusicAssistantError,
@@ -364,6 +365,10 @@ async def get_media_stream(
364365
buffer = buffer[pcm_format.pcm_sample_size :]
365366

366367
# end of audio/track reached
368+
if bytes_sent == 0:
369+
# edge case: no audio data was sent
370+
raise AudioError("No audio was received")
371+
367372
logger.log(VERBOSE_LOG_LEVEL, "End of stream reached.")
368373
if strip_silence_end and buffer:
369374
# strip silence from end of audio
@@ -378,27 +383,25 @@ async def get_media_stream(
378383
yield buffer
379384
del buffer
380385
finished = True
381-
386+
except Exception as err:
387+
if isinstance(err, asyncio.CancelledError):
388+
# we were cancelled, just raise
389+
raise
390+
logger.error("Error while streaming %s: %s", streamdetails.uri, err)
391+
streamdetails.stream_error = True
382392
finally:
383393
logger.log(VERBOSE_LOG_LEVEL, "Closing ffmpeg...")
384394
await ffmpeg_proc.close()
385395

386-
if bytes_sent == 0:
387-
# edge case: no audio data was sent
388-
streamdetails.stream_error = True
389-
seconds_streamed = 0
390-
logger.warning("Stream error on %s", streamdetails.uri)
391-
else:
392-
# try to determine how many seconds we've streamed
393-
seconds_streamed = bytes_sent / pcm_format.pcm_sample_size if bytes_sent else 0
394-
logger.debug(
395-
"stream %s (with code %s) for %s - seconds streamed: %s",
396-
"finished" if finished else "aborted",
397-
ffmpeg_proc.returncode,
398-
streamdetails.uri,
399-
seconds_streamed,
400-
)
401-
396+
# try to determine how many seconds we've streamed
397+
seconds_streamed = bytes_sent / pcm_format.pcm_sample_size if bytes_sent else 0
398+
logger.debug(
399+
"stream %s (with code %s) for %s - seconds streamed: %s",
400+
"finished" if finished else "aborted",
401+
ffmpeg_proc.returncode,
402+
streamdetails.uri,
403+
seconds_streamed,
404+
)
402405
streamdetails.seconds_streamed = seconds_streamed
403406
# store accurate duration
404407
if finished and not streamdetails.seek_position and seconds_streamed:

music_assistant/helpers/ffmpeg.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,13 @@ async def _feed_stdin(self) -> None:
141141
generator_exhausted = True
142142
except Exception as err:
143143
cancelled = isinstance(err, asyncio.CancelledError)
144-
if not cancelled:
145-
self.logger.error(
146-
"Stream error: %s",
147-
str(err) or err.__class__.__name__,
148-
exc_info=err if self.logger.isEnabledFor(VERBOSE_LOG_LEVEL) else None,
149-
)
144+
if cancelled:
145+
raise
146+
self.logger.error(
147+
"Stream error: %s",
148+
str(err) or err.__class__.__name__,
149+
exc_info=err if self.logger.isEnabledFor(VERBOSE_LOG_LEVEL) else None,
150+
)
150151
finally:
151152
if not cancelled:
152153
await self.write_eof()

0 commit comments

Comments
 (0)