Moves player logic to its own class, away from Interface
In the future, this will make testing and a CLI environment easier.
This commit is contained in:
@@ -0,0 +1,505 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.gui;
|
||||
|
||||
import edu.regis.universeplayer.PlaybackListener;
|
||||
import edu.regis.universeplayer.PlaybackStatus;
|
||||
import edu.regis.universeplayer.player.Player;
|
||||
import edu.regis.universeplayer.browserCommands.CommandConfirmation;
|
||||
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
||||
import edu.regis.universeplayer.data.PlaybackEvent;
|
||||
import edu.regis.universeplayer.data.Queue;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import edu.regis.universeplayer.player.PlayerManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.FocusAdapter;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
|
||||
/**
|
||||
* This panel contains the buttons necessary for controlling the playback of
|
||||
* audio.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class PlayerControls extends JPanel implements Queue.SongChangeListener, PlaybackListener
|
||||
{
|
||||
private static final Logger logger = LoggerFactory
|
||||
.getLogger(PlayerControls.class);
|
||||
private static final ResourceBundle langs = ResourceBundle
|
||||
.getBundle("lang.interface", Locale.getDefault());
|
||||
private final ImageIcon PLAY_ICON, PAUSE_ICON;
|
||||
|
||||
private final JButton playButton;
|
||||
private final JButton nextButton;
|
||||
private final JButton prevButton;
|
||||
private final JProgressBar 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);
|
||||
final Dimension ICON_SIZE = new Dimension(16, 16);
|
||||
ImageIcon icon;
|
||||
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(Interface.getInstance().actions
|
||||
.get("playback.skipPrev"));
|
||||
this.prevButton.setText("");
|
||||
icon = new ImageIcon(this.getClass()
|
||||
.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);
|
||||
buttonCont.add(this.prevButton);
|
||||
|
||||
this.playButton = new JButton(Interface.getInstance().actions
|
||||
.get("playback.toggle"));
|
||||
this.playButton.setText("");
|
||||
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);
|
||||
buttonCont.add(this.playButton);
|
||||
|
||||
this.nextButton = new JButton(Interface.getInstance().actions
|
||||
.get("playback.skipNext"));
|
||||
this.nextButton.setText("");
|
||||
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);
|
||||
buttonCont.add(this.nextButton);
|
||||
|
||||
progressLayout = new SpringLayout();
|
||||
progressCont = new JPanel(progressLayout);
|
||||
this.add(progressCont);
|
||||
|
||||
this.progress = new JProgressBar();
|
||||
this.progress.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
seek((float) e.getX() / (float) e.getComponent()
|
||||
.getWidth() * ((JProgressBar) e
|
||||
.getComponent()).getMaximum());
|
||||
logger.debug("Changing time");
|
||||
}
|
||||
});
|
||||
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
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
int index = -1;
|
||||
Component[] children = ((Container) e.getComponent())
|
||||
.getComponents();
|
||||
for (int i = 0, l = children.length; index == -1 && i < l; i++)
|
||||
{
|
||||
if (children[i] == e.getOppositeComponent())
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Only auto-switch focus if the previous component was not from
|
||||
* within.
|
||||
*/
|
||||
if (index == -1)
|
||||
{
|
||||
prevButton.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
layout.putConstraint(SpringLayout.NORTH, this.progress, 5, SpringLayout.SOUTH, buttonCont);
|
||||
layout.putConstraint(SpringLayout.WEST, this.progress, 5, SpringLayout.WEST, this);
|
||||
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.progress);
|
||||
layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.updateProgress);
|
||||
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress);
|
||||
|
||||
Queue.getInstance().addSongChangeListener(this);
|
||||
PlayerManager.getPlayers().addPlaybackListener(this);
|
||||
}
|
||||
|
||||
private void seek(float value)
|
||||
{
|
||||
this.service.execute(() -> {
|
||||
QueryFuture<Void> command = PlayerManager.getPlayers().seek(value);
|
||||
try
|
||||
{
|
||||
if (command != null && !command.getConfirmation()
|
||||
.wasSuccessful())
|
||||
{
|
||||
logger.error("Could not run command",
|
||||
command.getConfirmation().getError());
|
||||
JOptionPane.showMessageDialog(this,
|
||||
command.getConfirmation().getError(),
|
||||
command.getConfirmation().getMessage(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e)
|
||||
{
|
||||
logger.error("Could not run command",
|
||||
e);
|
||||
JOptionPane.showMessageDialog(this,
|
||||
e,
|
||||
e.getMessage(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
this.triggerCommandListeners(PlaybackCommand.SEEK, value);
|
||||
}
|
||||
|
||||
protected void play()
|
||||
{
|
||||
this.service.execute(() -> {
|
||||
try
|
||||
{
|
||||
QueryFuture<PlaybackStatus> status =
|
||||
PlayerManager.getPlayers().getStatus();
|
||||
CommandConfirmation confirmation = status.getConfirmation();
|
||||
if (status.getConfirmation().wasSuccessful())
|
||||
{
|
||||
switch (status.get())
|
||||
{
|
||||
case PAUSED -> confirmation = PlayerManager.getPlayers()
|
||||
.play()
|
||||
.getConfirmation();
|
||||
case STOPPED, EMPTY -> {
|
||||
if (Queue.getInstance().size() > 0)
|
||||
{
|
||||
if (Queue.getInstance()
|
||||
.getCurrentSong() == null)
|
||||
{
|
||||
Queue.getInstance().skipToSong(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
confirmation = PlayerManager.getPlayers().play()
|
||||
.getConfirmation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (confirmation != null && !confirmation.wasSuccessful())
|
||||
{
|
||||
logger.error("Could not run command",
|
||||
confirmation.getError());
|
||||
JOptionPane.showMessageDialog(this,
|
||||
confirmation.getError(),
|
||||
confirmation.getMessage(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e)
|
||||
{
|
||||
logger.error("Could not get current playback status", e);
|
||||
JOptionPane.showMessageDialog(this, e.getMessage(), langs
|
||||
.getString(
|
||||
"error.command"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
this.triggerCommandListeners(PlaybackCommand.PLAY, null);
|
||||
}
|
||||
|
||||
protected void pause()
|
||||
{
|
||||
this.service.execute(() -> {
|
||||
try
|
||||
{
|
||||
QueryFuture<PlaybackStatus> status =
|
||||
PlayerManager.getPlayers().getStatus();
|
||||
CommandConfirmation confirmation = status.getConfirmation();
|
||||
if (status.getConfirmation().wasSuccessful())
|
||||
{
|
||||
switch (status.get())
|
||||
{
|
||||
case PLAYING -> confirmation =
|
||||
PlayerManager.getPlayers().pause()
|
||||
.getConfirmation();
|
||||
}
|
||||
}
|
||||
if (confirmation != null && !confirmation.wasSuccessful())
|
||||
{
|
||||
logger.error("Could not run command",
|
||||
confirmation.getError());
|
||||
JOptionPane.showMessageDialog(this,
|
||||
confirmation.getError(),
|
||||
confirmation.getMessage(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e)
|
||||
{
|
||||
logger.error("Could not get current playback status", e);
|
||||
JOptionPane.showMessageDialog(this, e.getMessage(), langs
|
||||
.getString(
|
||||
"error.command"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
this.triggerCommandListeners(PlaybackCommand.PAUSE, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the playback of the current song.
|
||||
*/
|
||||
protected void togglePlayback()
|
||||
{
|
||||
this.service.execute(() -> {
|
||||
try
|
||||
{
|
||||
QueryFuture<PlaybackStatus> status =
|
||||
PlayerManager.getPlayers().getStatus();
|
||||
CommandConfirmation confirmation = status.getConfirmation();
|
||||
if (status.getConfirmation().wasSuccessful())
|
||||
{
|
||||
switch (status.get())
|
||||
{
|
||||
case PAUSED -> confirmation = PlayerManager.getPlayers()
|
||||
.play()
|
||||
.getConfirmation();
|
||||
case PLAYING -> confirmation = PlayerManager.getPlayers()
|
||||
.pause()
|
||||
.getConfirmation();
|
||||
case STOPPED, EMPTY -> {
|
||||
if (Queue.getInstance().size() > 0)
|
||||
{
|
||||
if (Queue.getInstance()
|
||||
.getCurrentSong() == null)
|
||||
{
|
||||
Queue.getInstance().skipToSong(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
confirmation = PlayerManager.getPlayers().play()
|
||||
.getConfirmation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (confirmation != null && !confirmation.wasSuccessful())
|
||||
{
|
||||
JOptionPane.showMessageDialog(this,
|
||||
confirmation.getError(),
|
||||
confirmation.getMessage(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
logger.error("Could not run command",
|
||||
confirmation.getError());
|
||||
}
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e)
|
||||
{
|
||||
logger.error("Could not get current playback status", e);
|
||||
JOptionPane.showMessageDialog(this, e.getMessage(), langs
|
||||
.getString(
|
||||
"error.command"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
this.triggerCommandListeners(PlaybackCommand.PLAY, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips to the next song.
|
||||
*/
|
||||
protected void previousSong()
|
||||
{
|
||||
Queue.getInstance().skipPrev();
|
||||
this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips to the next song.
|
||||
*/
|
||||
protected void nextSong()
|
||||
{
|
||||
Queue.getInstance().skipNext();
|
||||
this.triggerCommandListeners(PlaybackCommand.NEXT, null);
|
||||
}
|
||||
|
||||
void setUpdateProgress(int updated, int toUpdate, String updating)
|
||||
{
|
||||
this.updateProgress.setString(updating);
|
||||
if (toUpdate == 0)
|
||||
{
|
||||
this.updateProgress.setVisible(false);
|
||||
// this.updateProgress.setPreferredSize(new Dimension(this.updateProgress.getPreferredSize().width, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.updateProgress.setVisible(true);
|
||||
// this.updateProgress.setSize(new Dimension(this.updateProgress.getPreferredSize().width, this.updateSize));
|
||||
if (toUpdate < 0)
|
||||
{
|
||||
this.updateProgress.setIndeterminate(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.updateProgress.setIndeterminate(false);
|
||||
this.updateProgress.setMaximum(toUpdate);
|
||||
this.updateProgress.setValue(updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for playback commands.
|
||||
*
|
||||
* @param listener - The listener to add.
|
||||
*/
|
||||
public void addCommandListener(PlaybackCommandListener listener)
|
||||
{
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a playback listener.
|
||||
*
|
||||
* @param listener - The listener to remove.
|
||||
*/
|
||||
public void removeCommandListener(PlaybackCommandListener listener)
|
||||
{
|
||||
this.listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers all the command listeners.
|
||||
*
|
||||
* @param command - The command to trigger.
|
||||
* @param data - Extra command data.
|
||||
*/
|
||||
protected void triggerCommandListeners(PlaybackCommand command, Object data)
|
||||
{
|
||||
for (PlaybackCommandListener listener : this.listeners)
|
||||
{
|
||||
listener.onCommand(command, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSongChange(Queue queue)
|
||||
{
|
||||
this.service.execute(() -> {
|
||||
QueryFuture<Void> command = PlayerManager.getPlayers().stopSong();
|
||||
try
|
||||
{
|
||||
if (command != null && !command.getConfirmation().wasSuccessful())
|
||||
{
|
||||
JOptionPane.showMessageDialog(this,
|
||||
command.getConfirmation().getError(),
|
||||
command.getConfirmation().getMessage(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
logger.error("Could not run command",
|
||||
command.getConfirmation().getError());
|
||||
}
|
||||
else
|
||||
{
|
||||
command =
|
||||
PlayerManager.getPlayers()
|
||||
.playSong(queue.getCurrentSong());
|
||||
if (!command.getConfirmation().wasSuccessful())
|
||||
{
|
||||
JOptionPane.showMessageDialog(this,
|
||||
command.getConfirmation().getError(),
|
||||
command.getConfirmation().getMessage(),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
logger.error("Could not run command",
|
||||
command.getConfirmation().getError());
|
||||
}
|
||||
else
|
||||
{
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
this.playButton.setIcon(PLAY_ICON);
|
||||
this.progress.setMaximum((int) (PlayerManager
|
||||
.getPlayers()
|
||||
.getCurrentSong().duration / 1000));
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ExecutionException | InterruptedException e)
|
||||
{
|
||||
logger.error("Could not get current playback status", e);
|
||||
JOptionPane
|
||||
.showMessageDialog(this, e.getMessage(), langs
|
||||
.getString(
|
||||
"error.command"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackChanged(PlaybackEvent status)
|
||||
{
|
||||
switch (status.getInfo().getStatus())
|
||||
{
|
||||
case PLAYING -> this.playButton.setIcon(PAUSE_ICON);
|
||||
case FINISHED -> Queue.getInstance().skipNext();
|
||||
case PAUSED, STOPPED, EMPTY -> this.playButton.setIcon(PLAY_ICON);
|
||||
}
|
||||
this.progress.setValue((int) status.getInfo().getPlayTime());
|
||||
this.progress.setMaximum((int) (status.getInfo()
|
||||
.getSong().duration / 1000));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user