From ab94a8a9ee6828e1a1c2b35183bd6b759f1d0412 Mon Sep 17 00:00:00 2001 From: JISAUAY Date: Wed, 10 Sep 2025 13:14:48 -0500 Subject: [PATCH] added a shuffle play feature + fixed some bugs with duplicate songs on a playlist --- textual1/app.py | 53 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/textual1/app.py b/textual1/app.py index 36b4dc0..c44a5af 100644 --- a/textual1/app.py +++ b/textual1/app.py @@ -1,4 +1,5 @@ import logging +import random import os import io import asyncio @@ -48,11 +49,12 @@ class AlbumSongs(Screen): BINDINGS = [ ('escape', 'die()', "Back"), ('p', 'play()', 'Play Song'), - ('q' 'queue()', 'Queue Song') + ('q' 'queue()', 'Queue Song'), + ('m', 'shuffle_mix()', 'Shuffle Play') ] selected_song_id: str | None = None - songs = None + songs: list[SubSonic.Song] def __init__(self, album_id: str): self.album_id = album_id @@ -96,6 +98,13 @@ class AlbumSongs(Screen): app.pop_screen() app.uninstall_screen("albumSongs") + def action_shuffle_mix(self) -> None: + q = self.songs.copy() + random.shuffle(q) + app.play_song(q.pop(0)) + app.song_queue = q + app.update_queue_table() + class ArtistAlbums(Screen): BINDINGS = [ ('escape', 'die()', "Back") @@ -207,10 +216,12 @@ class PlaylistSongs(Screen): BINDINGS = [ ('escape', 'die()', "Back"), ('p', 'play()', 'Play Song'), - ('q', 'queue()', 'Queue Song') + ('q', 'queue()', 'Queue Song'), + ('m', 'shuffle_mix()', 'Shuffle Play') ] selected_song_id: str | None = None + songs: list[SubSonic.Song] def __init__(self, playlist: SubSonic.Playlist): self.playlist = playlist @@ -229,8 +240,8 @@ class PlaylistSongs(Screen): songTable.cursor_type = "row" songTable.add_columns("Title", "Album", "Artist", "Plays", "Duration", "Bitrate") - for song in self.songs: - songTable.add_row(song.title, song.album, song.artist, song.playCount, song.duration, song.bitRate, key=song.id) + for i, song in enumerate(self.songs): + songTable.add_row(song.title, song.album, song.artist, song.playCount, song.duration, song.bitRate, key=str(i)) # Pops and uninstalls self def action_die(self) -> None: @@ -239,7 +250,7 @@ class PlaylistSongs(Screen): @on(DataTable.RowHighlighted) def row_highlighted(self, event: DataTable.RowHighlighted) -> None: - self.selected_song_id = event.row_key.value + self.selected_song_id = self.songs[int(event.row_key.value)].id def action_play(self) -> None: song_index = self._find_selected_song() @@ -257,13 +268,21 @@ class PlaylistSongs(Screen): if song.id == self.selected_song_id: return i + def action_shuffle_mix(self) -> None: + q = self.songs.copy() + random.shuffle(q) + app.play_song(q.pop(0)) + app.song_queue = q + app.update_queue_table() + class Favourites(Screen): BINDINGS = [ ('p', 'play()', 'Play Song'), - ('q', 'queue()', 'Queue Song') + ('q', 'queue()', 'Queue Song'), + ('m', 'shuffle_mix()', 'Shuffle Play') ] - songs: list[SubSonic.Song] | None = None + songs: list[SubSonic.Song] selected_song_id: str | None = None def compose(self) -> ComposeResult: @@ -293,7 +312,8 @@ class Favourites(Screen): app.update_queue_table() def action_queue(self) -> None: - app.queue_song(self._find_selected_song()) + song_index = self._find_selected_song() + app.queue_song(self.songs[song_index]) # Returns index of selected song def _find_selected_song(self) -> int: @@ -301,6 +321,13 @@ class Favourites(Screen): if song.id == self.selected_song_id: return i + def action_shuffle_mix(self) -> None: + q = self.songs.copy() + random.shuffle(q) + app.play_song(q.pop(0)) + app.song_queue = q + app.update_queue_table() + # TODO fix coverart class CoverArtPixels(Container): @@ -491,6 +518,8 @@ class TextualSubpyPlayer(App): ("r", "rewind()", "⏪"), ("space", "play_pause_song()", "⏯"), ("s", "skip()", "⏭"), + ("d", "volume_down()", "🕩"), + ("u", "volume_up()", "🕪"), ("f1", "switch_mode('main')", "Overview"), ("f2", "switch_mode('artists')", "Artists"), ("f3", "switch_mode('albums')", "Albums"), @@ -627,6 +656,12 @@ class TextualSubpyPlayer(App): def action_play_pause_song(self) -> None: self.play_pause_song() + def action_volume_down(self) -> None: + pygame.mixer.music.set_volume(max(pygame.mixer.music.get_volume() - 0.05, 0.0)) + + def action_volume_up(self) -> None: + pygame.mixer.music.set_volume(min(pygame.mixer.music.get_volume() + 0.05, 1.0)) + def play_pause_song(self) -> None: if not self.currently_playing: return None