fix: advance playback after track completion
Backend / Lint i formatowanie (pull_request) Successful in 4s
Backend / Testy i kontrakt API (pull_request) Successful in 10s
Panel / Lint, typy i build (pull_request) Successful in 19s
Backend / Testowy build obrazu (pull_request) Successful in 9s

This commit is contained in:
mmacierzanka committed 2026-08-21 18:39:57 +02:00
1 parent 322087c50e
commit ed13d4af06
4 files changed
+83 -11

No files matched your search

+45 -4
View File
@@ -90,8 +90,13 @@ class MusicPlayer:
return discord.utils.get(self._bot.voice_clients, guild__id=guild_id)
def is_active(self, guild_id: int) -> bool:
state = self.state(guild_id)
voice_client = self.voice_client(guild_id)
return bool(voice_client and (voice_client.is_playing() or voice_client.is_paused()))
return bool(
state.current
and voice_client
and (voice_client.is_playing() or voice_client.is_paused())
)
def clear(self, guild_id: int) -> None:
self._playback_tokens[guild_id] = self._playback_tokens.get(guild_id, 0) + 1
@@ -130,6 +135,14 @@ class MusicPlayer:
self._interrupted_playbacks.add(guild_id)
voice_client.stop()
def _stop_stale_voice(self, guild_id: int, voice_client: discord.VoiceClient) -> None:
"""Stop a voice client that still reports the source from a finished queue."""
if not (voice_client.is_playing() or voice_client.is_paused()):
return
self._playback_tokens[guild_id] = self._playback_tokens.get(guild_id, 0) + 1
self._interrupted_playbacks.discard(guild_id)
voice_client.stop()
async def restore_queue(self, guild_id: int) -> int:
if not self._queues:
return 0
@@ -281,7 +294,18 @@ class MusicPlayer:
async def _restart_playback(self, guild_id: int) -> None:
voice_client = self.voice_client(guild_id)
if voice_client and (voice_client.is_playing() or voice_client.is_paused()):
voice_is_active = bool(
voice_client and (voice_client.is_playing() or voice_client.is_paused())
)
if voice_is_active and not self.is_active(guild_id):
# Po końcu kolejki discord.py może jeszcze przez chwilę trzymać stare źródło.
# Unieważniamy jego callback i uruchamiamy wybrany utwór bez czekania na niego.
self._playback_tokens[guild_id] = self._playback_tokens.get(guild_id, 0) + 1
self._interrupted_playbacks.discard(guild_id)
voice_client.stop()
await self.play_next(guild_id, advance=False)
return
if self.is_active(guild_id):
self._interrupted_playbacks.add(guild_id)
voice_client.stop() # `after` wywoła play_next, flaga przeskoku zablokuje advance
else:
@@ -325,6 +349,7 @@ class MusicPlayer:
if not state.queue:
state.index = -1
state.current = None
self._stop_stale_voice(guild_id, voice_client)
await self.trigger_state_change()
return
@@ -337,6 +362,7 @@ class MusicPlayer:
if next_index >= len(state.queue):
state.index = next_index
state.current = None
self._stop_stale_voice(guild_id, voice_client)
await self.trigger_state_change()
await self.notify(guild_id, "Kolejka zakończona.")
return
@@ -405,15 +431,30 @@ class MusicPlayer:
return index + 1
def _schedule_next(self, guild_id: int, error: Exception | None, playback_token: int) -> None:
if self._playback_tokens.get(guild_id) != playback_token:
logger.info(
"Zakończono odtwarzanie na serwerze %s (token=%s, error=%s)",
guild_id,
playback_token,
error,
)
if self._playback_tokens.get(guild_id, 0) != playback_token:
logger.info("Pomijam nieaktualny callback odtwarzania na serwerze %s", guild_id)
return
consume_current = guild_id not in self._interrupted_playbacks
self._interrupted_playbacks.discard(guild_id)
if error:
logger.error("Odtwarzanie zakończone błędem na serwerze %s: %s", guild_id, error)
asyncio.run_coroutine_threadsafe(
future = asyncio.run_coroutine_threadsafe(
self.play_next(guild_id, advance=True, consume_current=consume_current), self._bot.loop
)
future.add_done_callback(self._log_playback_failure)
@staticmethod
def _log_playback_failure(future: asyncio.Future) -> None:
try:
future.result()
except Exception:
logger.exception("Nie udało się przejść do kolejnego utworu")
def _record_history(self, guild_id: int, track: YTDLSource) -> None:
if not track.title:
+2 -2
View File
@@ -65,8 +65,8 @@ class StatusService:
notification_channel = self._player.notification_channel(guild.id)
return {
"playing": bool(voice_client and voice_client.is_playing()),
"paused": bool(voice_client and voice_client.is_paused()),
"playing": bool(self._player.is_active(guild.id) and voice_client.is_playing()),
"paused": bool(self._player.is_active(guild.id) and voice_client.is_paused()),
"loop_single": state.loop_mode.loops_track,
"loop_playlist": state.loop_mode.loops_queue,
"volume": int(state.volume * 100),