Some more work with playback.
WARNING: UNSTABLE
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer;
|
||||
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
|
||||
import java.util.EventListener;
|
||||
import java.util.EventObject;
|
||||
|
||||
/**
|
||||
* A playback listener allows a class to listen for events regarding player
|
||||
* updates.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @verison 0.1
|
||||
*/
|
||||
public interface PlaybackListener extends EventListener
|
||||
{
|
||||
/**
|
||||
* Called when playback is changed.
|
||||
*
|
||||
* @param status - The playback status.
|
||||
*/
|
||||
void onPlaybackChanged(PlaybackInfo status);
|
||||
|
||||
class PlaybackInfo extends EventObject
|
||||
{
|
||||
private final Song currentSong;
|
||||
/**
|
||||
* The time we are currently at in the song, in seconds.
|
||||
*/
|
||||
private final float playTime;
|
||||
|
||||
/**
|
||||
* The current status of the player.
|
||||
*/
|
||||
private final PlaybackStatus status;
|
||||
|
||||
public PlaybackInfo(Player<? extends Song> player, Song song, float playTime, PlaybackStatus status)
|
||||
{
|
||||
super(player);
|
||||
this.currentSong = song;
|
||||
this.playTime = playTime;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player<? extends Song> getSource()
|
||||
{
|
||||
return (Player<? extends Song>) super.getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the song currently playing.
|
||||
*
|
||||
* @return The current song, or null if none is loaded.
|
||||
*/
|
||||
public Song getSong()
|
||||
{
|
||||
return this.currentSong;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the current play time.
|
||||
*
|
||||
* @return The play time of the player. This will be zero if the song is
|
||||
* stopped or not loaded.
|
||||
*/
|
||||
public float getPlayTime()
|
||||
{
|
||||
return this.playTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the current status of the player.
|
||||
*
|
||||
* @return - The player's playback status.
|
||||
*/
|
||||
public PlaybackStatus getStatus()
|
||||
{
|
||||
return this.status;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer;
|
||||
|
||||
/**
|
||||
* This contains possible states that a player can be in.
|
||||
*/
|
||||
public enum PlaybackStatus
|
||||
{
|
||||
/**
|
||||
* The player is currently playing a song.
|
||||
*/
|
||||
PLAYING,
|
||||
/**
|
||||
* The player has been paused.
|
||||
*/
|
||||
PAUSED,
|
||||
/**
|
||||
* The player has a song loaded, but is not playing it.
|
||||
*/
|
||||
STOPPED,
|
||||
/**
|
||||
* No song is loaded.
|
||||
*/
|
||||
EMPTY;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ package edu.regis.universeplayer;
|
||||
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This interface serves as the connection to a music player of some sort, whether it be
|
||||
@@ -19,6 +19,31 @@ import java.util.concurrent.Future;
|
||||
*/
|
||||
public interface Player<T extends Song>
|
||||
{
|
||||
HashMap<Class<? extends Song>, Player<?>> REGISTERED_PLAYERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Obtains a song player that can play the provided song.
|
||||
*
|
||||
* @param song - The song to play.
|
||||
* @return A compatible player, or null if none is found.
|
||||
*/
|
||||
static Player<?> getCompatiblePlayer(Song song)
|
||||
{
|
||||
Player<?> player = REGISTERED_PLAYERS.get(song.getClass());
|
||||
if (player == null)
|
||||
{
|
||||
for (Class<? extends Song> songClass : REGISTERED_PLAYERS.keySet())
|
||||
{
|
||||
if (songClass.isAssignableFrom(song.getClass()))
|
||||
{
|
||||
player = REGISTERED_PLAYERS.get(songClass);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the song currently playing.
|
||||
*
|
||||
@@ -36,22 +61,30 @@ public interface Player<T extends Song>
|
||||
|
||||
/**
|
||||
* Enables playback of the current song, if one is active.
|
||||
*
|
||||
* @return A confirmation of whether the command was successful or not.
|
||||
*/
|
||||
QueryFuture<Void> play();
|
||||
|
||||
/**
|
||||
* Pauses playback of the current song.
|
||||
*
|
||||
* @return A confirmation of whether the command was successful or not.
|
||||
*/
|
||||
QueryFuture<Void> pause();
|
||||
|
||||
/**
|
||||
* Toggles between playing and pausing the current song.
|
||||
*
|
||||
* @return A confirmation of whether the command was successful or not.
|
||||
*/
|
||||
QueryFuture<Void> togglePlayback();
|
||||
|
||||
/**
|
||||
* Stops playback of the current song.
|
||||
*/
|
||||
QueryFuture<Void> stopSong();
|
||||
|
||||
/**
|
||||
* Sets the current song time to the specified position.
|
||||
*
|
||||
@@ -61,11 +94,10 @@ public interface Player<T extends Song>
|
||||
QueryFuture<Void> seek(float time);
|
||||
|
||||
/**
|
||||
* Checks to see if the song is paused.
|
||||
*
|
||||
* @return Whether or not the song is paused.
|
||||
* Obtains the player's current playback status.
|
||||
* @return A future for the request.
|
||||
*/
|
||||
QueryFuture<Boolean> isPaused();
|
||||
QueryFuture<PlaybackStatus> getStatus();
|
||||
|
||||
/**
|
||||
* Obtains the time we are currently at in the current song.
|
||||
@@ -83,7 +115,30 @@ public interface Player<T extends Song>
|
||||
|
||||
/**
|
||||
* Closes the player.
|
||||
*
|
||||
* @return A confirmation of whether the player was successfully closed.
|
||||
*/
|
||||
QueryFuture<Void> close();
|
||||
|
||||
/**
|
||||
* Adds a listener for playback status updates.
|
||||
*
|
||||
* @param listener - The listener to add.
|
||||
*/
|
||||
void addPlaybackListener(PlaybackListener listener);
|
||||
|
||||
/**
|
||||
* Checks to see if a listener has been added.
|
||||
*
|
||||
* @param listener - The listener to look for.
|
||||
* @return True if the provided listener has been added, false otherwise.
|
||||
*/
|
||||
boolean hasPlaybackListener(PlaybackListener listener);
|
||||
|
||||
/**
|
||||
* Removes a listener for playback status updates.
|
||||
*
|
||||
* @param listener - The listener to remove.
|
||||
*/
|
||||
void removePlaybackListener(PlaybackListener listener);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browser;
|
||||
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class InternetSong extends Song
|
||||
{
|
||||
public URL location;
|
||||
}
|
||||
@@ -4,6 +4,13 @@
|
||||
package edu.regis.universeplayer.localPlayer;
|
||||
|
||||
import com.intervigil.wave.WaveReader;
|
||||
import edu.regis.universeplayer.PlaybackListener;
|
||||
import edu.regis.universeplayer.PlaybackStatus;
|
||||
import edu.regis.universeplayer.Player;
|
||||
import edu.regis.universeplayer.browserCommands.CommandConfirmation;
|
||||
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
||||
import edu.regis.universeplayer.data.LocalSong;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -13,6 +20,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* This allows for the control of playback of files on the local file system
|
||||
@@ -20,19 +28,22 @@ import java.util.LinkedList;
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class Player
|
||||
public class LocalPlayer implements Player<LocalSong>
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Player.class);
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LocalPlayer.class);
|
||||
|
||||
static
|
||||
{
|
||||
System.loadLibrary("player");
|
||||
}
|
||||
|
||||
|
||||
private int currentId;
|
||||
|
||||
|
||||
private LocalSong currentSong;
|
||||
private AudioFile currentFile;
|
||||
|
||||
|
||||
private final ExecutorService service = Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* Sets the file currently being used.
|
||||
*
|
||||
@@ -46,25 +57,25 @@ public class Player
|
||||
this.currentId = this.setCurrentFile(this.currentFile, (short) header.getChannels(), (short) header
|
||||
.getPcmFormat(), header.getSampleRate());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the file currently being played.
|
||||
*
|
||||
* @param stream - A reference to the audio data stream.
|
||||
* @param numChannels - The number of audio channels contained in the file.
|
||||
* @param stream - A reference to the audio data stream.
|
||||
* @param numChannels - The number of audio channels contained in the file.
|
||||
* @param bitsPerSample - The number if bits in every sample.
|
||||
* @param sampleRate - How many samples need to play every second.
|
||||
* @param sampleRate - How many samples need to play every second.
|
||||
* @return An ID for the current song.
|
||||
*/
|
||||
private native int setCurrentFile(InputStream stream, short numChannels, short bitsPerSample, int sampleRate);
|
||||
|
||||
|
||||
/**
|
||||
* Saves the current file to
|
||||
*
|
||||
* @param file - The file to save to.
|
||||
*/
|
||||
public native void save(String file);
|
||||
|
||||
|
||||
/**
|
||||
* Saves the current file as a WAVE file somewhere else. This method is meant for testing only.
|
||||
*
|
||||
@@ -88,7 +99,7 @@ public class Player
|
||||
logger.error("Could not save WAVE file", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtains the current file.
|
||||
*
|
||||
@@ -98,24 +109,127 @@ public class Player
|
||||
{
|
||||
return this.currentFile;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Song getCurrentSong()
|
||||
{
|
||||
return this.currentSong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Void> loadSong(LocalSong song)
|
||||
{
|
||||
if (this.currentSong == null)
|
||||
{
|
||||
this.stopSong();
|
||||
}
|
||||
this.currentSong = song;
|
||||
Future<Void> runnable = service.submit(() -> {
|
||||
try
|
||||
{
|
||||
AudioFile stream = getAudioStream(song.file);
|
||||
this.setCurrentFile(stream);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not load local file " + song.file.getAbsolutePath(), e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
return new WrappedFuture<>(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Void> play()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Void> pause()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Void> togglePlayback()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Void> stopSong()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPlaybackListener(PlaybackListener listener)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Void> seek(float time)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<PlaybackStatus> getStatus()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Float> getCurrentTime()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Float> getLength()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Void> close()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays the current audio file.
|
||||
*/
|
||||
public native void play();
|
||||
|
||||
public native void playSong();
|
||||
|
||||
/**
|
||||
* Pauses playback for the current audio file.
|
||||
*/
|
||||
public native void pause();
|
||||
|
||||
public native void pauseSong();
|
||||
|
||||
/**
|
||||
* Checks to see whether the current audio file is paused.
|
||||
*
|
||||
* @return True if there is an audio file present and it is paused, false otherwise.
|
||||
*/
|
||||
public native boolean isPaused();
|
||||
|
||||
public native boolean isSongPaused();
|
||||
|
||||
@Override
|
||||
public boolean hasPlaybackListener(PlaybackListener listener)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePlaybackListener(PlaybackListener listener)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains an input stream for the requested file.
|
||||
*
|
||||
@@ -128,7 +242,7 @@ public class Player
|
||||
{
|
||||
return new AudioFile(convertFile(file));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts any audio file to a stream containing WAV audio file data (courtesy of FFMPEG).
|
||||
*
|
||||
@@ -156,7 +270,7 @@ public class Player
|
||||
args.add("pipe:1");
|
||||
return Runtime.getRuntime().exec(args.toArray(new String[args.size()]));
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
byte[] buffer = new byte[200];
|
||||
@@ -164,7 +278,7 @@ public class Player
|
||||
File file = new File(args[0]);
|
||||
try (AudioFile stream = getAudioStream(new File(args[0])))
|
||||
{
|
||||
Player player = new Player();
|
||||
LocalPlayer player = new LocalPlayer();
|
||||
player.setCurrentFile(stream);
|
||||
player.save(args[0] + ".wav");
|
||||
}
|
||||
@@ -175,7 +289,7 @@ public class Player
|
||||
// Compare JNI vs Java
|
||||
try (AudioFile stream = getAudioStream(new File(args[0])))
|
||||
{
|
||||
Player player = new Player();
|
||||
LocalPlayer player = new LocalPlayer();
|
||||
player.setCurrentFile(stream);
|
||||
player.saveJava(args[0] + ".orig.wav");
|
||||
}
|
||||
@@ -184,4 +298,72 @@ public class Player
|
||||
logger.error("Could not save audio file " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WrappedFuture<T> implements QueryFuture<T>
|
||||
{
|
||||
private final Future<T> runnable;
|
||||
|
||||
WrappedFuture(Future<T> runnable)
|
||||
{
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException
|
||||
{
|
||||
try
|
||||
{
|
||||
this.runnable.get();
|
||||
return new CommandConfirmation();
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
return new CommandConfirmation(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
try
|
||||
{
|
||||
this.runnable.get(timeout, unit);
|
||||
return new CommandConfirmation();
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
return new CommandConfirmation(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning)
|
||||
{
|
||||
return this.runnable.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.runnable.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone()
|
||||
{
|
||||
return this.runnable.isDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException
|
||||
{
|
||||
return this.runnable.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
return this.runnable.get(timeout, unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -345,7 +345,9 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
}
|
||||
case PAUSE -> {
|
||||
}
|
||||
case NEXT -> Queue.getInstance().skipNext();
|
||||
case NEXT -> {
|
||||
|
||||
}
|
||||
case PREVIOUS -> Queue.getInstance().skipPrev();
|
||||
case SEEK -> {
|
||||
}
|
||||
|
||||
@@ -4,10 +4,20 @@
|
||||
|
||||
package edu.regis.universeplayer.player;
|
||||
|
||||
import edu.regis.universeplayer.PlaybackListener;
|
||||
import edu.regis.universeplayer.PlaybackStatus;
|
||||
import edu.regis.universeplayer.Player;
|
||||
import edu.regis.universeplayer.data.Queue;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -17,19 +27,30 @@ import javax.swing.*;
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class PlayerControls extends JPanel
|
||||
public class PlayerControls extends JPanel implements Queue.SongChangeListener, PlaybackListener
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(PlayerControls.class);
|
||||
private final ImageIcon PLAY_ICON, PAUSE_ICON;
|
||||
|
||||
/**
|
||||
* A reference to the song currently playing.
|
||||
*/
|
||||
private Song currentSong = null;
|
||||
private Player currentPlayer = null;
|
||||
|
||||
private final JButton playButton;
|
||||
private final JButton nextButton;
|
||||
private final JButton prevButton;
|
||||
private final JSlider progress;
|
||||
private final JProgressBar updateProgress;
|
||||
|
||||
|
||||
private final ForkJoinPool service = new ForkJoinPool();
|
||||
|
||||
/**
|
||||
* A list of all things interested in knowing when we trigger a command.
|
||||
*/
|
||||
private final LinkedList<PlaybackCommandListener> listeners = new LinkedList<>();
|
||||
|
||||
|
||||
public PlayerControls()
|
||||
{
|
||||
final Dimension BUTTON_SIZE = new Dimension(32, 32);
|
||||
@@ -38,54 +59,56 @@ public class PlayerControls extends JPanel
|
||||
JPanel buttonCont, progressCont;
|
||||
FlowLayout buttonLayout;
|
||||
SpringLayout progressLayout;
|
||||
|
||||
|
||||
SpringLayout layout = new SpringLayout();
|
||||
this.setLayout(layout);
|
||||
this.setFocusable(true);
|
||||
this.setFocusCycleRoot(false);
|
||||
|
||||
|
||||
buttonLayout = new FlowLayout();
|
||||
buttonCont = new JPanel(buttonLayout);
|
||||
this.add(buttonCont);
|
||||
|
||||
|
||||
this.prevButton = new JButton();
|
||||
icon = new ImageIcon(this.getClass()
|
||||
.getResource("/gui/icons/skipPrev.png"), "Previous Button");
|
||||
.getResource("/gui/icons/skipPrev.png"), "Previous Button");
|
||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||
this.prevButton.setIcon(icon);
|
||||
this.prevButton.setPreferredSize(BUTTON_SIZE);
|
||||
this.prevButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null));
|
||||
this.prevButton.addActionListener(actionEvent -> this.previousSong());
|
||||
buttonCont.add(this.prevButton);
|
||||
|
||||
|
||||
this.playButton = new JButton();
|
||||
icon = new ImageIcon(this.getClass().getResource("/gui/icons/play.png"), "Play Button");
|
||||
PAUSE_ICON = icon = new ImageIcon(this.getClass().getResource("/gui/icons/pause.png"), "Pause Button");
|
||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||
PLAY_ICON = icon = new ImageIcon(this.getClass().getResource("/gui/icons/play.png"), "Play Button");
|
||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||
this.playButton.setIcon(icon);
|
||||
this.playButton.setPreferredSize(BUTTON_SIZE);
|
||||
this.playButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PLAY, null));
|
||||
this.playButton.addActionListener(actionEvent -> this.togglePlayback());
|
||||
buttonCont.add(this.playButton);
|
||||
|
||||
|
||||
this.nextButton = new JButton();
|
||||
icon = new ImageIcon(this.getClass().getResource("/gui/icons/skipNext.png"), "Next Button");
|
||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||
this.nextButton.setIcon(icon);
|
||||
this.nextButton.setPreferredSize(BUTTON_SIZE);
|
||||
this.nextButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.NEXT, null));
|
||||
this.nextButton.addActionListener(actionEvent -> this.nextSong());
|
||||
buttonCont.add(this.nextButton);
|
||||
|
||||
|
||||
progressLayout = new SpringLayout();
|
||||
progressCont = new JPanel(progressLayout);
|
||||
this.add(progressCont);
|
||||
|
||||
|
||||
this.progress = new JSlider();
|
||||
this.progress.addChangeListener(changeEvent -> this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue()));
|
||||
this.progress.addChangeListener(changeEvent -> this.seek(((JSlider) changeEvent.getSource()).getValue()));
|
||||
this.add(this.progress);
|
||||
|
||||
|
||||
this.updateProgress = new JProgressBar();
|
||||
this.updateProgress.setStringPainted(true);
|
||||
this.setUpdateProgress(0, 0, null);
|
||||
this.add(this.updateProgress);
|
||||
|
||||
|
||||
this.addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
@@ -110,7 +133,7 @@ public class PlayerControls extends JPanel
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
layout.putConstraint(SpringLayout.NORTH, buttonCont, 0, SpringLayout.NORTH, this);
|
||||
layout.putConstraint(SpringLayout.WEST, buttonCont, 0, SpringLayout.WEST, this);
|
||||
layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this);
|
||||
@@ -119,9 +142,82 @@ public class PlayerControls extends JPanel
|
||||
layout.putConstraint(SpringLayout.EAST, this.progress, 5, SpringLayout.EAST, this);
|
||||
layout.putConstraint(SpringLayout.NORTH, this.updateProgress, 5, SpringLayout.SOUTH, this.progress);
|
||||
layout.putConstraint(SpringLayout.WEST, this.updateProgress, 5, SpringLayout.WEST, this);
|
||||
|
||||
|
||||
layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.updateProgress);
|
||||
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress);
|
||||
|
||||
Queue.getInstance().addSongChangeListener(this);
|
||||
}
|
||||
|
||||
private void seek(int value)
|
||||
{
|
||||
this.service.execute(() -> {
|
||||
if (this.currentPlayer != null)
|
||||
{
|
||||
this.currentPlayer.seek(value);
|
||||
}
|
||||
});
|
||||
this.triggerCommandListeners(PlaybackCommand.SEEK, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the playback of the current song.
|
||||
*/
|
||||
private void togglePlayback()
|
||||
{
|
||||
this.service.execute(() -> {
|
||||
try
|
||||
{
|
||||
if (this.currentPlayer != null)
|
||||
{
|
||||
switch ((PlaybackStatus) this.currentPlayer.getStatus().get())
|
||||
{
|
||||
case PAUSED -> {
|
||||
this.currentPlayer.play();
|
||||
}
|
||||
case PLAYING -> {
|
||||
this.currentPlayer.pause();
|
||||
}
|
||||
case STOPPED, EMPTY -> {
|
||||
if (Queue.getInstance().size() > 0)
|
||||
{
|
||||
if (Queue.getInstance().getCurrentSong() == null)
|
||||
{
|
||||
Queue.getInstance().skipToSong(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.currentPlayer.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e)
|
||||
{
|
||||
logger.error("Could not get current playback status", e);
|
||||
}
|
||||
});
|
||||
this.triggerCommandListeners(PlaybackCommand.PLAY, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips to the next song.
|
||||
*/
|
||||
private void previousSong()
|
||||
{
|
||||
Queue.getInstance().skipPrev();
|
||||
this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips to the next song.
|
||||
*/
|
||||
private void nextSong()
|
||||
{
|
||||
Queue.getInstance().skipNext();
|
||||
this.triggerCommandListeners(PlaybackCommand.NEXT, null);
|
||||
}
|
||||
|
||||
void setUpdateProgress(int updated, int toUpdate, String updating)
|
||||
@@ -148,7 +244,7 @@ public class PlayerControls extends JPanel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a listener for playback commands.
|
||||
*
|
||||
@@ -158,7 +254,7 @@ public class PlayerControls extends JPanel
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a playback listener.
|
||||
*
|
||||
@@ -168,7 +264,7 @@ public class PlayerControls extends JPanel
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers all the command listeners.
|
||||
*
|
||||
@@ -182,4 +278,51 @@ public class PlayerControls extends JPanel
|
||||
listener.onCommand(command, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSongChange(Queue queue)
|
||||
{
|
||||
if (this.currentSong != null)
|
||||
{
|
||||
this.currentPlayer.stopSong();
|
||||
}
|
||||
this.currentSong = queue.getCurrentSong();
|
||||
this.playButton.setIcon(PLAY_ICON);
|
||||
if (this.currentSong != null)
|
||||
{
|
||||
this.currentPlayer = Player.REGISTERED_PLAYERS.get(this.currentSong.getClass());
|
||||
if (!this.currentPlayer.hasPlaybackListener(this))
|
||||
{
|
||||
this.currentPlayer.addPlaybackListener(this);
|
||||
}
|
||||
this.progress.setMaximum((int) (this.currentSong.duration / 1000));
|
||||
if (this.currentPlayer != null)
|
||||
{
|
||||
this.currentPlayer.play();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error("No logger found for song {}", this.currentSong.getClass());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.currentPlayer = null;
|
||||
this.progress.setMaximum(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackChanged(PlaybackInfo status)
|
||||
{
|
||||
if (status.getSource() != null && status.getSource() == this.currentPlayer)
|
||||
{
|
||||
switch (status.getStatus())
|
||||
{
|
||||
case PLAYING -> this.playButton.setIcon(PAUSE_ICON);
|
||||
case PAUSED, STOPPED, EMPTY -> this.playButton.setIcon(PLAY_ICON);
|
||||
}
|
||||
this.progress.setValue((int) status.getPlayTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user