Added volume controls and threaded library refresh. Setup for callbacks related to database update functions.
This commit is contained in:
parent
6ef92096e9
commit
24070231b6
6
src/main/java/musicplayer/LibraryCallbackInterface.java
Normal file
6
src/main/java/musicplayer/LibraryCallbackInterface.java
Normal file
@ -0,0 +1,6 @@
|
||||
package musicplayer;
|
||||
|
||||
public interface LibraryCallbackInterface {
|
||||
|
||||
void libraryUpdated(boolean successful);
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package musicplayer;
|
||||
|
||||
import musicplayer.db.DatabaseManager;
|
||||
import musicplayer.db.Gateway;
|
||||
import musicplayer.model.ExtractedMetadata;
|
||||
import musicplayer.model.Song;
|
||||
import org.jaudiotagger.audio.AudioFileIO;
|
||||
import org.jaudiotagger.audio.exceptions.CannotReadException;
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
@ -11,52 +9,15 @@ import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.tag.Tag;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
public class Application {
|
||||
public final class LibraryUtils {
|
||||
|
||||
static final String musicFileExtensionRegex = ".*\\.(mp3|mp4|flac)";
|
||||
|
||||
public Application() {
|
||||
DatabaseManager.init();
|
||||
List<Song> songs = Gateway.listAllSongs().get();
|
||||
boolean running = true;
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
||||
Player player = new Player(null);
|
||||
while (running) {
|
||||
try {
|
||||
System.out.println("Ready to read");
|
||||
String input = br.readLine();
|
||||
System.out.println(input);
|
||||
switch (input) {
|
||||
case "play":
|
||||
player.playSong(songs.get(0));
|
||||
break;
|
||||
case "stop":
|
||||
player.stop();
|
||||
break;
|
||||
case "pause":
|
||||
player.pause();
|
||||
break;
|
||||
case "resume":
|
||||
player.resume();
|
||||
break;
|
||||
case "quit":
|
||||
running = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all files and directories recursively and index any music files with a correct extension
|
||||
*
|
||||
@ -66,7 +27,7 @@ public class Application {
|
||||
try {
|
||||
Files.walk(rootDirectory)
|
||||
.filter(f -> f.toString().matches(musicFileExtensionRegex))
|
||||
.map(Application::autoParse).filter(Optional::isPresent).map(Optional::get).forEach(Gateway::addSong);
|
||||
.map(LibraryUtils::autoParse).filter(Optional::isPresent).map(Optional::get).forEach(Gateway::addSong);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -78,7 +39,7 @@ public class Application {
|
||||
* @param targetFile Path to file to extract metadata from.
|
||||
* @return Metadata contained in targetFile.
|
||||
*/
|
||||
public static Optional<ExtractedMetadata> autoParse(Path targetFile) {
|
||||
private static Optional<ExtractedMetadata> autoParse(Path targetFile) {
|
||||
Tag audioTags = null;
|
||||
try {
|
||||
audioTags = AudioFileIO.read(targetFile.toFile()).getTag();
|
@ -101,6 +101,21 @@ public class Player {
|
||||
return playBin.isPlaying() ? (int) (playBin.getClock().getTime().toSeconds() - playBin.getBaseTime().toSeconds()) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the player volume.
|
||||
* @param volume New volume in percent.
|
||||
*/
|
||||
public void setVolume(int volume){
|
||||
playBin.setVolumePercent(volume);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current Player volume in percent.
|
||||
*/
|
||||
public int getVolume(){
|
||||
return playBin.getVolumePercent();
|
||||
}
|
||||
|
||||
private class InternalThread implements Runnable {
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="musicplayer.PlayerGUI">
|
||||
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
@ -10,7 +10,7 @@
|
||||
<children>
|
||||
<splitpane id="79ccb">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="760" height="500"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
@ -28,7 +28,10 @@
|
||||
<children>
|
||||
<component id="6b9fd" class="javax.swing.JTree" binding="libraryView">
|
||||
<constraints/>
|
||||
<properties/>
|
||||
<properties>
|
||||
<rootVisible value="true"/>
|
||||
<showsRootHandles value="false"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</scrollpane>
|
||||
@ -51,7 +54,7 @@
|
||||
</splitpane>
|
||||
<component id="98c55" class="javax.swing.JSlider" binding="seekBar">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<inverted value="false"/>
|
||||
@ -64,7 +67,7 @@
|
||||
</component>
|
||||
<toolbar id="56df">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="-1" height="20"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
@ -91,8 +94,23 @@
|
||||
<text value="&Next"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="c8ecb" class="javax.swing.JSlider" binding="volumeSlider">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<majorTickSpacing value="20"/>
|
||||
<minorTickSpacing value="10"/>
|
||||
<paintTicks value="true"/>
|
||||
<value value="100"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</toolbar>
|
||||
<component id="e1626" class="javax.swing.JMenuBar" binding="menuBar">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
|
@ -12,7 +12,7 @@ import javax.swing.tree.TreeNode;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
|
||||
public class PlayerGUI implements PlayerCallbackInterface{
|
||||
public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterface{
|
||||
private JTree libraryView;
|
||||
private JTable playList;
|
||||
private JPanel mainPanel;
|
||||
@ -20,6 +20,8 @@ public class PlayerGUI implements PlayerCallbackInterface{
|
||||
private JButton stopButton;
|
||||
private JButton nextButton;
|
||||
private JSlider seekBar;
|
||||
private JSlider volumeSlider;
|
||||
private JMenuBar menuBar;
|
||||
private Player player = new Player(this);
|
||||
private PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>());
|
||||
|
||||
@ -35,23 +37,12 @@ public class PlayerGUI implements PlayerCallbackInterface{
|
||||
|
||||
public PlayerGUI() {
|
||||
DatabaseManager.init();
|
||||
populateLibrary(new TreeMap<>(Gateway.listAllSongsGroupedByAlbum().get()));
|
||||
libraryView.setRootVisible(false);
|
||||
libraryView.setToggleClickCount(1);
|
||||
libraryView.setCellRenderer(new LibraryTreeCellRenderer());
|
||||
|
||||
MouseListener mouseListener = new libraryMouseAdapter();
|
||||
|
||||
libraryView.addMouseListener(mouseListener);
|
||||
playList.setModel(playlistTableModel);
|
||||
playButton.addActionListener(e -> {
|
||||
if (playList.getSelectedColumn() == -1) {
|
||||
player.playSong(playlistTableModel.getFirst().get());
|
||||
}
|
||||
});
|
||||
stopButton.addActionListener(e -> player.stop());
|
||||
nextButton.addActionListener(e -> playNextSong());
|
||||
|
||||
resetTree();
|
||||
volumeSlider.setValue(player.getVolume());
|
||||
Thread seekBarUpdater = new Thread(() -> {
|
||||
boolean running = true;
|
||||
while (running) {
|
||||
@ -64,6 +55,20 @@ public class PlayerGUI implements PlayerCallbackInterface{
|
||||
}
|
||||
});
|
||||
seekBarUpdater.start();
|
||||
|
||||
//Action Listeners
|
||||
playButton.addActionListener(e -> {
|
||||
if (playList.getSelectedColumn() == -1) {
|
||||
player.playSong(playlistTableModel.getFirst().get());
|
||||
}
|
||||
});
|
||||
MouseListener mouseListener = new libraryMouseAdapter();
|
||||
libraryView.addMouseListener(mouseListener);
|
||||
stopButton.addActionListener(e -> player.stop());
|
||||
nextButton.addActionListener(e -> playNextSong());
|
||||
volumeSlider.addChangeListener(e -> player.setVolume(((JSlider)e.getSource()).getValue()));
|
||||
|
||||
populateMenuBar();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -167,6 +172,23 @@ public class PlayerGUI implements PlayerCallbackInterface{
|
||||
SwingUtilities.invokeLater(() -> seekBar.setMaximum(seconds));
|
||||
}
|
||||
|
||||
public void refreshLibrary(){
|
||||
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) libraryView.getModel().getRoot();
|
||||
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode("Refreshing Library...");
|
||||
addNodeToTreeModel(parentNode, newNode);
|
||||
Thread populateThread = new Thread(() -> {
|
||||
TreeMap treeMap = new TreeMap<>(Gateway.listAllSongsGroupedByAlbum().get());
|
||||
SwingUtilities.invokeLater(() -> populateLibrary(treeMap));
|
||||
});
|
||||
populateThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void libraryUpdated(boolean successful) {
|
||||
if(successful)
|
||||
populateLibrary(new TreeMap<>(Gateway.listAllSongsGroupedByAlbum().get()));
|
||||
}
|
||||
|
||||
private class libraryMouseAdapter extends MouseAdapter {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
@ -187,4 +209,25 @@ public class PlayerGUI implements PlayerCallbackInterface{
|
||||
} catch (NullPointerException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the top menu bar with items.
|
||||
*/
|
||||
private void populateMenuBar(){
|
||||
|
||||
// Tools menu
|
||||
JMenu tools = new JMenu("Tools");
|
||||
JMenuItem menuItem = new JMenuItem("Refresh Library");
|
||||
menuItem.addActionListener(e -> refreshLibrary());
|
||||
tools.add(menuItem);
|
||||
|
||||
menuItem = new JMenuItem("Update Library");
|
||||
menuItem.setToolTipText("Reindex and refresh library.");
|
||||
|
||||
tools.add(menuItem);
|
||||
|
||||
// Add everything to the menu bar itself
|
||||
menuBar.add(tools);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,6 @@ public class PlaylistTableModel extends AbstractTableModel {
|
||||
}
|
||||
|
||||
public void addSong(Song song){
|
||||
System.out.println("Adding " + song.toString());
|
||||
songList.add(song);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public class ExtractedMetadata {
|
||||
public final String songFile;
|
||||
public final String trackNumber;
|
||||
public final byte[] artwork;
|
||||
public final String discNumber;
|
||||
|
||||
private static final String albumArtRegex = ".*(Cover|Folder|cover|folder)\\.(jpg|png)";
|
||||
|
||||
@ -35,6 +36,7 @@ public class ExtractedMetadata {
|
||||
this.album = audioTags.getFirst(FieldKey.ALBUM);
|
||||
this.artist = audioTags.getFirst(FieldKey.ARTIST);
|
||||
this.genre = audioTags.getFirst(FieldKey.GENRE);
|
||||
this.discNumber = audioTags.getFirst(FieldKey.DISC_NO);
|
||||
this.songFile = songFile.getAbsolutePath();
|
||||
byte[] tmpArt = null;
|
||||
try {
|
||||
@ -54,16 +56,25 @@ public class ExtractedMetadata {
|
||||
artwork = tmpArt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an Image into its' byte array representation.
|
||||
* @param image Image to convert.
|
||||
* @return image as byte[].
|
||||
*/
|
||||
private static byte[] convertImageToBytes(Image image){
|
||||
int imageScaleToSize = 50;
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()){
|
||||
ImageIO.write(convertToBufferedImage(image.getScaledInstance(imageScaleToSize, imageScaleToSize, Image.SCALE_SMOOTH)), "jpg", baos);
|
||||
baos.flush();
|
||||
return baos.toByteArray();
|
||||
} catch (IOException ignored) {}
|
||||
return null;
|
||||
} catch (IOException e) { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an Image to a BufferedImage.
|
||||
* @param image Source image.
|
||||
* @return image as BufferedImage.
|
||||
*/
|
||||
private static BufferedImage convertToBufferedImage(Image image) {
|
||||
BufferedImage newImage = new BufferedImage(
|
||||
image.getWidth(null), image.getHeight(null),
|
||||
|
Loading…
Reference in New Issue
Block a user