diff --git a/tests/test_music_player.py b/tests/test_music_player.py index 7a80341..5bf6fb0 100644 --- a/tests/test_music_player.py +++ b/tests/test_music_player.py @@ -27,6 +27,17 @@ class MusicPlayerQueueTest(unittest.IsolatedAsyncioTestCase): self.assertIs(voice_client.playing, track) self.assertEqual(player.state(GUILD).index, 0) + self.assertEqual(track.plays_left, 5) + + async def test_finish_current_decrements_plays_without_going_below_zero(self): + player, _ = build_player(default_plays=1) + track = FakeTrack("a") + await player.enqueue(GUILD, track) + + player.finish_current(GUILD) + player.finish_current(GUILD) + + self.assertEqual(track.plays_left, 0) async def test_last_play_remains_pointed_to_until_playback_finishes(self): player, _ = build_player(default_plays=1) @@ -51,7 +62,7 @@ class MusicPlayerQueueTest(unittest.IsolatedAsyncioTestCase): await player.play_next(GUILD, advance=True) self.assertEqual([track.id for track in state.queue], ["a", "b", "c"]) self.assertEqual(state.index, 0) - self.assertEqual(state.current.plays_left, 0) + self.assertEqual(state.current.plays_left, 1) await player.play_next(GUILD, advance=True) @@ -162,7 +173,7 @@ class MusicPlayerQueueTest(unittest.IsolatedAsyncioTestCase): await player.enqueue(1, first) await player.enqueue(2, second) - self.assertEqual(first.plays_left, 1) + self.assertEqual(first.plays_left, 2) self.assertEqual(second.plays_left, 7) async def test_broken_tracks_stop_instead_of_looping_forever(self): diff --git a/zyrarbot/music/music_player.py b/zyrarbot/music/music_player.py index 08429b5..7fd36e0 100644 --- a/zyrarbot/music/music_player.py +++ b/zyrarbot/music/music_player.py @@ -103,7 +103,23 @@ class MusicPlayer: if not self._queues: return state = self.state(guild_id) - self._queues.save(guild_id, [self._track_snapshot(track) for track in state.queue]) + self._queues.save( + guild_id, + [ + self._track_snapshot(track) + for track in state.queue + if self.plays_left(track, guild_id) > 0 + ], + ) + + def finish_current(self, guild_id: int) -> None: + """Consume one play when playback ends outside the regular callback.""" + state = self.state(guild_id) + if state.current is None or not 0 <= state.index < len(state.queue): + return + current = state.queue[state.index] + if current is state.current: + current.plays_left = max(self.plays_left(current, guild_id) - 1, 0) async def restore_queue(self, guild_id: int) -> int: if not self._queues: @@ -274,6 +290,9 @@ class MusicPlayer: if state.consume_jump(): advance = False + if advance: + self.finish_current(guild_id) + # Utwór z ostatnim odtworzeniem musi pozostać w kolejce do chwili # zakończenia źródła. W przeciwnym razie panel dostaje `current`, ale # `queue_index` wskazuje pozycję spoza kolejki podczas faktycznego @@ -320,7 +339,7 @@ class MusicPlayer: return track.played = True - track.plays_left = self.plays_left(track, guild_id) - 1 + track.plays_left = self.plays_left(track, guild_id) track.volume = state.volume state.queue[next_index] = track diff --git a/zyrarbot/ui/api/routers/playback_router.py b/zyrarbot/ui/api/routers/playback_router.py index ba2c053..3878758 100644 --- a/zyrarbot/ui/api/routers/playback_router.py +++ b/zyrarbot/ui/api/routers/playback_router.py @@ -133,6 +133,7 @@ async def stop( player: MusicPlayer = Depends(get_player), broadcaster: StatusBroadcaster = Depends(get_broadcaster), ) -> dict[str, str]: + player.finish_current(guild.id) player.save_queue(guild.id) if player.is_active(guild.id): player.voice_client(guild.id).stop() diff --git a/zyrarbot/ui/api/routers/voice_router.py b/zyrarbot/ui/api/routers/voice_router.py index 22c55dc..5023169 100644 --- a/zyrarbot/ui/api/routers/voice_router.py +++ b/zyrarbot/ui/api/routers/voice_router.py @@ -90,6 +90,7 @@ async def disconnect( if not voice_client: raise HTTPException(status_code=400, detail="Bot not connected to voice on this server") + player.finish_current(guild.id) player.save_queue(guild.id) await voice_client.disconnect() player.clear(guild.id) diff --git a/zyrarbot/ui/bot/cogs/control_cog.py b/zyrarbot/ui/bot/cogs/control_cog.py index 5dfaaf0..1a552c9 100644 --- a/zyrarbot/ui/bot/cogs/control_cog.py +++ b/zyrarbot/ui/bot/cogs/control_cog.py @@ -49,6 +49,7 @@ class ControlCog(MusicCogBase, name="Control"): async def stop(self, interaction: discord.Interaction) -> None: guild_id = interaction.guild.id voice_client = interaction.guild.voice_client + self.player.finish_current(guild_id) self.player.save_queue(guild_id) if voice_client and voice_client.is_playing(): voice_client.stop() diff --git a/zyrarbot/ui/bot/cogs/voice_cog.py b/zyrarbot/ui/bot/cogs/voice_cog.py index a99f0e8..eb9872e 100644 --- a/zyrarbot/ui/bot/cogs/voice_cog.py +++ b/zyrarbot/ui/bot/cogs/voice_cog.py @@ -23,6 +23,7 @@ class VoiceCog(MusicCogBase, name="Voice"): and after.channel is None ) if is_bot_leaving: + self.player.finish_current(before.channel.guild.id) self.player.save_queue(before.channel.guild.id) self.player.clear(before.channel.guild.id) await self.player.trigger_state_change() @@ -34,6 +35,7 @@ class VoiceCog(MusicCogBase, name="Voice"): "Nie jestem na kanale głosowym.", ephemeral=True ) return + self.player.finish_current(interaction.guild.id) self.player.save_queue(interaction.guild.id) await interaction.guild.voice_client.disconnect() self.player.clear(interaction.guild.id)