diff --git a/src/main/java/musicplayer/LibraryUtils.java b/src/main/java/musicplayer/LibraryUtils.java index fe3a3d1..1f8361f 100644 --- a/src/main/java/musicplayer/LibraryUtils.java +++ b/src/main/java/musicplayer/LibraryUtils.java @@ -21,7 +21,7 @@ import java.util.stream.Collectors; public final class LibraryUtils { - static final String musicFileExtensionRegex = ".*\\.(mp3|mp4|flac|ogg)"; + static final String musicFileExtensionRegex = "(?iu).*\\.(mp3|mp4|flac|ogg)"; static final Properties librarySettings = new Properties(); static final String settingsFilename = "settings.cfg"; static final File propertiesFile = new File(settingsFilename); @@ -59,44 +59,23 @@ public final class LibraryUtils { public static void processSongsWithCallback(List rootDirectory, LibraryCallbackInterface callbackInterface){ Thread thread = new Thread(() -> { - try { - processSongs(rootDirectory); - callbackInterface.libraryUpdated(true); - } catch (IOException e) { - callbackInterface.libraryUpdated(false); - } + rootDirectory.forEach(dir -> { + try { + Files.walk(dir) + .filter(f -> f.toString().matches(musicFileExtensionRegex)).map(LibraryUtils::autoParse) + .forEach(x -> x.ifPresent(i -> { + Gateway.addSong(i); + callbackInterface.currentlyUpdating(i.toString()); + })); + } catch (IOException e) { + callbackInterface.libraryUpdated(false); + } + }); + callbackInterface.libraryUpdated(true); }); thread.start(); } - public static void processSongsWithCallback(Path rootDirectory, LibraryCallbackInterface callbackInterface){ - Thread thread = new Thread(() -> { - try { - processSongs(rootDirectory); - callbackInterface.libraryUpdated(true); - } catch (IOException e) { - callbackInterface.libraryUpdated(false); - } - }); - thread.start(); - } - - public static void processSongs(List rootDirectories) throws IOException { - for(Path rootDirectory: rootDirectories) - processSongs(rootDirectory); - } - - /** - * Walk through all files and directories recursively and index any music files with a correct extension - * - * @param rootDirectory Directory from which to start searching - */ - public static void processSongs(Path rootDirectory) throws IOException { - Files.walk(rootDirectory) - .filter(f -> f.toString().matches(musicFileExtensionRegex)) - .map(LibraryUtils::autoParse).forEach(x -> x.ifPresent(Gateway::addSong)); - } - /** * Extract music metadata from the target file. * diff --git a/src/main/java/musicplayer/Player.java b/src/main/java/musicplayer/Player.java index c8a4160..b811155 100644 --- a/src/main/java/musicplayer/Player.java +++ b/src/main/java/musicplayer/Player.java @@ -82,6 +82,7 @@ public class Player { playBin.stop(); internalThread.stop(); thread = null; + callbackInterface.playerStopped(); } } diff --git a/src/main/java/musicplayer/PlayerGUI.java b/src/main/java/musicplayer/PlayerGUI.java index 76c13e7..dd35094 100644 --- a/src/main/java/musicplayer/PlayerGUI.java +++ b/src/main/java/musicplayer/PlayerGUI.java @@ -30,6 +30,8 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf private JComboBox libraryDisplayType = new JComboBox(); private Player player = new Player(this); private PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>()); + private static DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode(); + private boolean libraryUpdating = false; private Map libraryDisplayVariants = createDisplayVariantMap(); @@ -91,7 +93,8 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf sortedData.forEach((k, v) -> { DefaultMutableTreeNode albumNode = new DefaultMutableTreeNode(k); addNodeToTreeModel(model, parentNode, albumNode); - new TreeSet<>(v).forEach(x -> addNodeToTreeModel(model, albumNode, new DefaultMutableTreeNode(x))); + Collections.sort(v); + v.forEach(x -> addNodeToTreeModel(model, albumNode, new DefaultMutableTreeNode(x))); }); libraryView.setModel(model); } @@ -152,6 +155,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf public void setSongHighlighting(Song playingSong) { int index = playlistTableModel.getSongIndex(playingSong); playList.setRowSelectionInterval(index, index); + playlistTableModel.setPlayingRow(index); } /** @@ -176,6 +180,8 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf playlistTableModel.removeSong(invalidSong); } + public void playerStopped(){ playlistTableModel.setPlayingRow(-1); } + /** * Refresh the library with songs in the currently selected display format. */ @@ -200,10 +206,12 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf } private void updateLibrary(){ + libraryUpdating = true; DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode()); DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) model.getRoot(); DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Updating Library..."); addNodeToTreeModel(model, parentNode, newNode); + addNodeToTreeModel(model, parentNode, updatingNode); libraryView.setModel(model); List dirs = LibraryUtils.getLibraryDirectories().stream().map(File::toPath).collect(Collectors.toList()); LibraryUtils.processSongsWithCallback(dirs, this); @@ -213,6 +221,13 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf public void libraryUpdated(boolean successful) { if (successful) refreshLibrary(); + libraryUpdating = false; + } + + @Override + public void currentlyUpdating(String name) { + updatingNode.setUserObject(name); + ((DefaultTreeModel)libraryView.getModel()).nodeChanged(updatingNode); } private Map createDisplayVariantMap() { @@ -247,7 +262,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf libraryAndComboboxPane.setLayout(new BorderLayout(0, 0)); libraryAndPlaylistPane.setLeftComponent(libraryAndComboboxPane); libraryDisplayType.addItemListener(e -> { - if (e.getStateChange() == ItemEvent.SELECTED) + if (!libraryUpdating && e.getStateChange() == ItemEvent.SELECTED) libraryDisplayVariants.get(libraryDisplayType.getSelectedItem().toString()).run(); }); libraryAndComboboxPane.add(libraryDisplayType, BorderLayout.NORTH); @@ -278,6 +293,9 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf JScrollPane playlistScroll = new JScrollPane(); playList.setRowSelectionAllowed(true); playList.setModel(playlistTableModel); + playList.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + playList.getColumnModel().getColumn(0).setPreferredWidth(10); + playList.getColumnModel().getColumn(0).setMaxWidth(10); JPopupMenu popupMenu = new JPopupMenu(); JMenuItem menuItem = new JMenuItem("Remove"); menuItem.addActionListener((e) -> playlistTableModel.removeSong(playList.getSelectedRows())); diff --git a/src/main/java/musicplayer/callbacks/LibraryCallbackInterface.java b/src/main/java/musicplayer/callbacks/LibraryCallbackInterface.java index 535e2bc..320b0d3 100644 --- a/src/main/java/musicplayer/callbacks/LibraryCallbackInterface.java +++ b/src/main/java/musicplayer/callbacks/LibraryCallbackInterface.java @@ -3,4 +3,6 @@ package musicplayer.callbacks; public interface LibraryCallbackInterface { void libraryUpdated(boolean successful); + + void currentlyUpdating(String name); } diff --git a/src/main/java/musicplayer/callbacks/PlayerCallbackInterface.java b/src/main/java/musicplayer/callbacks/PlayerCallbackInterface.java index 45aaa9b..f081b91 100644 --- a/src/main/java/musicplayer/callbacks/PlayerCallbackInterface.java +++ b/src/main/java/musicplayer/callbacks/PlayerCallbackInterface.java @@ -14,4 +14,6 @@ public interface PlayerCallbackInterface { void removeInvalidSong(Song invalidSong); + void playerStopped(); + } diff --git a/src/main/java/musicplayer/model/Album.java b/src/main/java/musicplayer/model/Album.java index a88f13a..313c873 100644 --- a/src/main/java/musicplayer/model/Album.java +++ b/src/main/java/musicplayer/model/Album.java @@ -19,7 +19,7 @@ public class Album implements Comparable { @Column(name = "name") private String name; @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "album") - @OrderBy("trackNumber ASC") + @OrderBy("discNumber ASC, trackNumber ASC") private Set songs; @Lob @Column(name = "art", nullable = true, length = 10000) diff --git a/src/main/java/musicplayer/model/ExtractedMetadata.java b/src/main/java/musicplayer/model/ExtractedMetadata.java index bb0e5c4..0a5c118 100644 --- a/src/main/java/musicplayer/model/ExtractedMetadata.java +++ b/src/main/java/musicplayer/model/ExtractedMetadata.java @@ -84,4 +84,9 @@ public class ExtractedMetadata { g.dispose(); return newImage; } + + @Override + public String toString() { + return title + " - " + artist; + } } diff --git a/src/main/java/musicplayer/swingmodels/PlaylistTableModel.java b/src/main/java/musicplayer/swingmodels/PlaylistTableModel.java index 11809be..e442b7c 100644 --- a/src/main/java/musicplayer/swingmodels/PlaylistTableModel.java +++ b/src/main/java/musicplayer/swingmodels/PlaylistTableModel.java @@ -3,15 +3,14 @@ package musicplayer.swingmodels; import musicplayer.model.Song; import javax.swing.table.AbstractTableModel; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; public class PlaylistTableModel extends AbstractTableModel { private List songList; + private int playingRow = -1; public PlaylistTableModel(List songList){ this.songList = songList; @@ -24,7 +23,7 @@ public class PlaylistTableModel extends AbstractTableModel { @Override public int getColumnCount() { - return 4; + return 6; } @Override @@ -32,10 +31,12 @@ public class PlaylistTableModel extends AbstractTableModel { Object o = "?"; Song song = songList.get(rowIndex); switch (columnIndex){ - case 0: o = song.getTrackNumber(); break; - case 1: o = song.getTitle(); break; - case 2: o = song.getArtist(); break; - case 3: o = song.getAlbum(); break; + case 0: o = (playingRow == rowIndex) ? "▶" : " "; break; + case 1: o = song.getTrackNumber(); break; + case 2: o = song.getTitle(); break; + case 3: o = song.getArtist(); break; + case 4: o = song.getAlbum(); break; + case 5: o = song.getDiscNumber(); break; } return o; } @@ -49,10 +50,12 @@ public class PlaylistTableModel extends AbstractTableModel { public String getColumnName(int column) { String name = ""; switch (column){ - case 0: name = "Track"; break; - case 1: name = "Title"; break; - case 2: name = "Artist"; break; - case 3: name = "Album"; break; + case 0 : name = " "; break; + case 1: name = "Track"; break; + case 2: name = "Title"; break; + case 3: name = "Artist"; break; + case 4: name = "Album"; break; + case 5: name = "DiscNo"; break; } return name; } @@ -108,4 +111,9 @@ public class PlaylistTableModel extends AbstractTableModel { public Optional getSong(int index){ return songList.size() > 0 && index >= 0 && index < songList.size() ? Optional.of(songList.get(index)) : Optional.empty(); } + + public void setPlayingRow(int index){ + playingRow = index; + fireTableDataChanged(); + } }