diff --git a/src/main/java/musicplayer/Player.java b/src/main/java/musicplayer/Player.java index b3ffa98..a3aca37 100644 --- a/src/main/java/musicplayer/Player.java +++ b/src/main/java/musicplayer/Player.java @@ -2,6 +2,7 @@ package musicplayer; import musicplayer.callbacks.PlayerCallbackInterface; import musicplayer.model.Song; +import org.gstreamer.ClockTime; import org.gstreamer.ElementFactory; import org.gstreamer.Gst; import org.gstreamer.State; @@ -115,7 +116,7 @@ public class Player { * @return Current playback position in seconds. */ public int currentSongPosition(){ - return playBin.isPlaying() || playBin.getState() == State.PAUSED ? (int) (playBin.getClock().getTime().toSeconds() - playBin.getBaseTime().toSeconds()) : 0; + return playBin.isPlaying() || playBin.getState() == State.PAUSED ? (int) playBin.queryPosition().toSeconds() : 0; } /** @@ -134,6 +135,14 @@ public class Player { return playBin.getVolumePercent(); } + /** + * Seek to a position in the song. + * @param position Position (in seconds) to seek to. + */ + public void seek(int position){ + playBin.seek(ClockTime.fromSeconds(position)); + } + private class InternalThread implements Runnable { @Override diff --git a/src/main/java/musicplayer/PlayerGUI.java b/src/main/java/musicplayer/PlayerGUI.java index 658088e..e8d29cf 100644 --- a/src/main/java/musicplayer/PlayerGUI.java +++ b/src/main/java/musicplayer/PlayerGUI.java @@ -16,10 +16,12 @@ import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; import java.io.File; import java.nio.file.Path; import java.util.*; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterface { @@ -32,6 +34,7 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf private PlaylistTableModel playlistTableModel = new PlaylistTableModel(new ArrayList<>()); private static DefaultMutableTreeNode updatingNode = new DefaultMutableTreeNode(); private boolean libraryUpdating = false; + private AtomicBoolean seeking = new AtomicBoolean(false); private Map libraryDisplayVariants = createDisplayVariantMap(); @@ -69,7 +72,10 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf * @param position New seekBar value. */ public void setSeekBarPosition(int position) { - SwingUtilities.invokeLater(() -> seekBar.setValue(position)); + SwingUtilities.invokeLater(() -> { + if(!seeking.get()) + seekBar.setValue(position); + }); } /** @@ -153,6 +159,9 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf player.playSong(playlistTableModel.nextSong(player.getCurrentSong())); } + /** + * Get the previous song in the playlist and dispatch it to the Player to be played. + */ public void playPreviousSong() { SwingUtilities.invokeLater(() -> seekBar.setValue(0)); player.playSong(playlistTableModel.previous(player.getCurrentSong())); @@ -222,6 +231,9 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf ((DefaultTreeModel)libraryView.getModel()).nodeChanged(updatingNode); } + /** + * @return Map of display types for the library paired code to populate the library with data in the correct format. + */ private Map createDisplayVariantMap() { Map value = new HashMap<>(); value.put("Song", () -> Gateway.listAllSongs().ifPresent(this::populateLibrary)); @@ -272,11 +284,15 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf seekBar = new JSlider(); seekBar.setMinorTickSpacing(1); seekBar.setValue(0); + seekBar.addMouseListener(new SeekbarMouseListener()); controlPane.add(seekBar, BorderLayout.NORTH); controlPane.add(createControlButtons(), BorderLayout.SOUTH); mainPanel.add(createMenuBar(), BorderLayout.NORTH); } + /** + * @return Area containing the library. + */ private JTree createLibraryArea() { libraryView.setRootVisible(false); libraryView.setToggleClickCount(1); @@ -285,6 +301,9 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf return libraryView; } + /** + * @return Area containing the playlist. + */ private JScrollPane createPlaylistArea() { JScrollPane playlistScroll = new JScrollPane(); playList.setRowSelectionAllowed(true); @@ -308,6 +327,9 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf return playlistScroll; } + /** + * @return Playback control buttons (play, stop, etc.) + */ private JToolBar createControlButtons() { JToolBar toolBar = new JToolBar(); toolBar.setFloatable(false); @@ -345,6 +367,9 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf return toolBar; } + /** + * @return Volume controls. + */ private JPanel createVolumeControls() { JPanel panel = new JPanel(); panel.setLayout(new FlowLayout()); @@ -410,4 +435,29 @@ public class PlayerGUI implements PlayerCallbackInterface, LibraryCallbackInterf } } } + + /** + * MouseListener implementation for the seekbar. + * Using this instead of the ChangeListener so that the polling for the current song position + * doesn't repeatedly fire seek events. + */ + private class SeekbarMouseListener implements MouseListener { + @Override + public void mouseClicked(MouseEvent e) { seeking.set(true); } + + @Override + public void mousePressed(MouseEvent e) { seeking.set(true); } + + @Override + public void mouseReleased(MouseEvent e) { + player.seek(seekBar.getValue()); + seeking.set(false); + } + + @Override + public void mouseEntered(MouseEvent e) {} + + @Override + public void mouseExited(MouseEvent e) {} + } } diff --git a/src/main/java/musicplayer/callbacks/LibraryCallbackInterface.java b/src/main/java/musicplayer/callbacks/LibraryCallbackInterface.java index 320b0d3..5a723eb 100644 --- a/src/main/java/musicplayer/callbacks/LibraryCallbackInterface.java +++ b/src/main/java/musicplayer/callbacks/LibraryCallbackInterface.java @@ -2,7 +2,15 @@ package musicplayer.callbacks; public interface LibraryCallbackInterface { + /** + * Call this after performing library updates so the front-end knows that updating has completed. + * @param successful Was the update function successful. + */ void libraryUpdated(boolean successful); + /** + * Update the current display with the file/folder currently being parsed. + * @param name Data about the file/folder to be shown to the user. + */ void currentlyUpdating(String name); } diff --git a/src/main/java/musicplayer/callbacks/PlayerCallbackInterface.java b/src/main/java/musicplayer/callbacks/PlayerCallbackInterface.java index fe15404..9243866 100644 --- a/src/main/java/musicplayer/callbacks/PlayerCallbackInterface.java +++ b/src/main/java/musicplayer/callbacks/PlayerCallbackInterface.java @@ -6,14 +6,32 @@ import java.util.Optional; public interface PlayerCallbackInterface { + /** + * Set the song currently shown to the user as playing. + * @param playingSong Song currently playing. + */ void setSongHighlighting(Song playingSong); + /** + * Set the seekbar position. + * @param seconds Position to set the seekbar to. + */ void setSeekBarDuration(int seconds); + /** + * Remove a song from the current playlist. + * @param invalidSong Song to remove. + */ void removeInvalidSong(Song invalidSong); + /** + * Playback has ceased. + */ void playerStopped(); + /** + * Start playing the next song. + */ void playNextSong(); }