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:
Markil3
2021-09-14 16:39:40 -06:00
parent 92ebb75e9d
commit 999f3345cc
24 changed files with 791 additions and 519 deletions

View File

@@ -44,5 +44,5 @@ sourceCompatibility = JavaVersion.VERSION_16
targetCompatibility = JavaVersion.VERSION_16 targetCompatibility = JavaVersion.VERSION_16
// Define the main class for the application // Define the main class for the application
mainClassName = defaultPackage + '.player.Interface' mainClassName = defaultPackage + '.gui.Interface'
//mainClassName = defaultPackage + '.localPlayer.Player' //mainClassName = defaultPackage + '.localPlayer.Player'

View File

@@ -0,0 +1,93 @@
package edu.regis.universeplayer;
import net.harawata.appdirs.AppDirsFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class ConfigManager
{
private static final Logger logger = LoggerFactory
.getLogger(ConfigManager.class);
private static File dataDir;
private static File commDir;
private static File configDir;
/**
* Obtains the data storage directory for the application, creating it if
* needed.
*
* @return The data storage directory.
*/
public static File getDataDir()
{
if (dataDir == null)
{
dataDir = new File(AppDirsFactory.getInstance()
.getUserDataDir("universalmusic", null, null, true));
if (!dataDir.exists())
{
if (!dataDir.mkdir())
{
logger.error("Could not create data directory {}", dataDir);
}
}
}
return dataDir;
}
/**
* Obtains the configuration directory for the application, creating it if
* needed.
*
* @return The configuration directory.
*/
public static File getConfigDir()
{
if (configDir == null)
{
configDir = new File(AppDirsFactory.getInstance()
.getUserConfigDir("universalmusic", null, null, true));
if (!configDir.exists())
{
if (!configDir.mkdir())
{
logger.error("Could not create configuration directory {}", configDir);
}
}
}
return configDir;
}
/**
* Obtains the directory for memory mapped files
*
* @return The communications storage directory.
*/
public static File getCommDir()
{
if (commDir == null)
{
commDir = new File(AppDirsFactory.getInstance()
.getSharedDir("universalmusic", null, null), "comm");
if (!commDir.getParentFile().exists())
{
if (!commDir.getParentFile().mkdir())
{
logger.error("Could not create shared directory {}", commDir
.getParent());
}
}
if (!commDir.exists())
{
if (!commDir.mkdir())
{
logger.error("Could not create data directory {}", commDir);
}
}
}
return commDir;
}
}

View File

@@ -4,7 +4,8 @@
package edu.regis.universeplayer.data; package edu.regis.universeplayer.data;
import edu.regis.universeplayer.player.Interface; import edu.regis.universeplayer.ConfigManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -30,7 +31,8 @@ public class DatabaseManager
if (db == null || db.isClosed()) if (db == null || db.isClosed())
{ {
Class.forName("org.sqlite.JDBC"); Class.forName("org.sqlite.JDBC");
db = DriverManager.getConnection("jdbc:sqlite:" + new File(Interface.getDataDir().getAbsolutePath(), "universalmusic.db").getAbsolutePath()); db = DriverManager.getConnection("jdbc:sqlite:" + new File(ConfigManager
.getDataDir().getAbsolutePath(), "universalmusic.db").getAbsolutePath());
} }
warning = db.getWarnings(); warning = db.getWarnings();
while (warning != null) while (warning != null)

View File

@@ -4,8 +4,6 @@
package edu.regis.universeplayer.data; package edu.regis.universeplayer.data;
import edu.regis.universeplayer.player.Interface;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@@ -5,8 +5,7 @@
package edu.regis.universeplayer.data; package edu.regis.universeplayer.data;
import edu.regis.universeplayer.PlaybackInfo; import edu.regis.universeplayer.PlaybackInfo;
import edu.regis.universeplayer.Player; import edu.regis.universeplayer.player.Player;
import edu.regis.universeplayer.localPlayer.LocalPlayer;
import java.util.EventObject; import java.util.EventObject;

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.LineBorder; import javax.swing.border.LineBorder;

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import java.awt.*; import java.awt.*;
import java.awt.event.FocusAdapter; import java.awt.event.FocusAdapter;
@@ -14,7 +14,7 @@ import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import com.wordpress.tips4java.ScrollablePanel; import com.wordpress.tips4java.ScrollablePanel;
import edu.regis.universeplayer.ClickListener;
import edu.regis.universeplayer.data.Album; import edu.regis.universeplayer.data.Album;
import edu.regis.universeplayer.data.CollectionType; import edu.regis.universeplayer.data.CollectionType;
import edu.regis.universeplayer.data.Song; import edu.regis.universeplayer.data.Song;

View File

@@ -2,11 +2,10 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Locale; import java.util.Locale;
@@ -16,7 +15,6 @@ import javax.swing.*;
import edu.regis.universeplayer.data.CollectionType; import edu.regis.universeplayer.data.CollectionType;
import edu.regis.universeplayer.data.Song; import edu.regis.universeplayer.data.Song;
import edu.regis.universeplayer.data.SongProvider;
/** /**
* This panel links to various song collections the player has set up. * This panel links to various song collections the player has set up.

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import java.awt.*; import java.awt.*;

View File

@@ -2,19 +2,17 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import edu.regis.universeplayer.Player; import edu.regis.universeplayer.player.Player;
import edu.regis.universeplayer.browser.Browser; import edu.regis.universeplayer.browser.Browser;
import edu.regis.universeplayer.browser.BrowserPlayer; import edu.regis.universeplayer.player.BrowserPlayer;
import edu.regis.universeplayer.browserCommands.CommandError;
import edu.regis.universeplayer.browserCommands.QueryFuture; import edu.regis.universeplayer.browserCommands.QueryFuture;
import edu.regis.universeplayer.data.InternetSong; import edu.regis.universeplayer.data.InternetSong;
import edu.regis.universeplayer.data.Queue; import edu.regis.universeplayer.data.Queue;
import edu.regis.universeplayer.data.*; import edu.regis.universeplayer.data.*;
import edu.regis.universeplayer.localPlayer.LocalPlayer; import edu.regis.universeplayer.player.LocalPlayer;
import edu.regis.universeplayer.player.PlayerManager;
import net.harawata.appdirs.AppDirsFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -23,7 +21,6 @@ import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -51,10 +48,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
private static Interface INSTANCE; private static Interface INSTANCE;
private static File dataDir;
private static File commDir;
private static File configDir;
public final ActionMap actions = new ActionMap(); public final ActionMap actions = new ActionMap();
/** /**
@@ -82,10 +75,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
*/ */
private final PlayerControls controls; private final PlayerControls controls;
/**
* A link to the browser.
*/
private final ArrayList<Player<?>> players = new ArrayList<>();
private int currentPlayer = -1; private int currentPlayer = -1;
public static Interface getInstance() public static Interface getInstance()
@@ -95,10 +84,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
public static void main(String[] args) public static void main(String[] args)
{ {
Thread browserThread;
Interface inter = null; Interface inter = null;
Browser browser; PlayerManager.getPlayers();
BrowserPlayer browserPlayer;
try try
{ {
/* /*
@@ -112,51 +99,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
inter.setSize(700, 500); inter.setSize(700, 500);
SongProvider.INSTANCE.addUpdateListener(inter); SongProvider.INSTANCE.addUpdateListener(inter);
inter.setVisible(true); inter.setVisible(true);
browser = Browser.createBrowser();
browserThread = new Thread(browser);
browserThread.start();
Player.REGISTERED_PLAYERS.put(LocalSong.class, new LocalPlayer());
try
{
inter.players.add(browserPlayer = new BrowserPlayer());
logger.debug("Sending ping");
browser.sendObject("ping");
Runtime.getRuntime()
.addShutdownHook(new Thread(browserPlayer::close));
Player.REGISTERED_PLAYERS
.put(InternetSong.class, browserPlayer);
// LinkedList<Future<Object>> pingRequests = new LinkedList<>();
// for (int i = 0; i < 20; i++)
// {
// logger.info("Sending ping");
// pingRequests.add(browser.sendObject("ping"));
// }
//
// LinkedList<Future<Object>> toRemove = new LinkedList<>();
// while (pingRequests.size() > 0)
// {
// for (Future<Object> future : pingRequests)
// {
// if (future.isDone())
// {
// logger.info("Receiving {}", future.get());
// toRemove.add(future);
// }
// }
// pingRequests.removeAll(toRemove);
// toRemove.clear();
// }
}
catch (IOException e)
{
logger.error("Could not open browser background", e);
JOptionPane.showMessageDialog(inter, e, langs
.getString("error.browser.launch"), JOptionPane.ERROR_MESSAGE);
}
} }
catch (Throwable e) catch (Throwable e)
{ {
@@ -168,82 +110,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
} }
} }
/**
* Obtains the data storage directory for the application, creating it if
* needed.
*
* @return The data storage directory.
*/
public static File getDataDir()
{
if (dataDir == null)
{
dataDir = new File(AppDirsFactory.getInstance()
.getUserDataDir("universalmusic", null, null, true));
if (!dataDir.exists())
{
if (!dataDir.mkdir())
{
logger.error("Could not create data directory {}", dataDir);
}
}
}
return dataDir;
}
/**
* Obtains the configuration directory for the application, creating it if
* needed.
*
* @return The configuration directory.
*/
public static File getConfigDir()
{
if (configDir == null)
{
configDir = new File(AppDirsFactory.getInstance()
.getUserConfigDir("universalmusic", null, null, true));
if (!configDir.exists())
{
if (!configDir.mkdir())
{
logger.error("Could not create configuration directory {}", configDir);
}
}
}
return configDir;
}
/**
* Obtains the directory for memory mapped files
*
* @return The communications storage directory.
*/
public static File getCommDir()
{
if (commDir == null)
{
commDir = new File(AppDirsFactory.getInstance()
.getSharedDir("universalmusic", null, null), "comm");
if (!commDir.getParentFile().exists())
{
if (!commDir.getParentFile().mkdir())
{
logger.error("Could not create shared directory {}", commDir
.getParent());
}
}
if (!commDir.exists())
{
if (!commDir.mkdir())
{
logger.error("Could not create data directory {}", commDir);
}
}
}
return commDir;
}
/** /**
* Creates an interface * Creates an interface
*/ */
@@ -396,20 +262,10 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
@Override @Override
protected Void doInBackground() throws Exception protected Void doInBackground() throws Exception
{ {
Interface.this.players.stream() QueryFuture<Void> command = PlayerManager.getPlayers().throwError(false);
.filter(p -> p instanceof BrowserPlayer)
.map(p -> (BrowserPlayer) p)
.findFirst()
.ifPresentOrElse(p -> {
logger.debug("Throwing " +
"error");
QueryFuture<Void> command = p
.throwError((false));
try try
{ {
if (!command if (!command.getConfirmation().wasSuccessful())
.getConfirmation()
.wasSuccessful())
{ {
logger.error("Could not run command", logger.error("Could not run command",
command.getConfirmation() command.getConfirmation()
@@ -435,10 +291,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
.getString( .getString(
"error.command"), JOptionPane.ERROR_MESSAGE); "error.command"), JOptionPane.ERROR_MESSAGE);
} }
}, () -> JOptionPane
.showMessageDialog(Interface.this, langs
.getString("error.browser.null"), langs
.getString("error.debug.error"), JOptionPane.ERROR_MESSAGE));
return null; return null;
} }
}.execute(); }.execute();
@@ -578,7 +430,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
{ {
if (this.isEnabled()) if (this.isEnabled())
{ {
controls.play(); PlayerManager.getPlayers().play();
} }
} }
}); });
@@ -591,7 +443,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
{ {
if (this.isEnabled()) if (this.isEnabled())
{ {
controls.pause(); PlayerManager.getPlayers().pause();
} }
} }
}); });
@@ -604,7 +456,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
{ {
if (this.isEnabled()) if (this.isEnabled())
{ {
controls.togglePlayback(); PlayerManager.getPlayers().toggle();
} }
} }
}); });
@@ -796,7 +648,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
@Override @Override
public void windowClosing(WindowEvent windowEvent) public void windowClosing(WindowEvent windowEvent)
{ {
this.players.forEach(Player::close); PlayerManager.getPlayers().shutdownPlayers();
} }
@Override @Override

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import edu.regis.universeplayer.data.InternetSong; import edu.regis.universeplayer.data.InternetSong;
import edu.regis.universeplayer.data.InternetSongProvider; import edu.regis.universeplayer.data.InternetSongProvider;

View File

@@ -1,6 +1,5 @@
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LogEvent;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -8,9 +7,7 @@ import org.slf4j.LoggerFactory;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.FlowLayout; import java.awt.FlowLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
@@ -18,16 +15,10 @@ import java.awt.event.WindowEvent;
import java.util.Comparator; import java.util.Comparator;
import java.util.Formatter; import java.util.Formatter;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Box; import javax.swing.Box;

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
/** /**
* Contains possible playback commands that the play can send. * Contains possible playback commands that the play can send.

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import java.util.EventListener; import java.util.EventListener;

View File

@@ -2,18 +2,17 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import edu.regis.universeplayer.PlaybackInfo;
import edu.regis.universeplayer.PlaybackListener; import edu.regis.universeplayer.PlaybackListener;
import edu.regis.universeplayer.PlaybackStatus; import edu.regis.universeplayer.PlaybackStatus;
import edu.regis.universeplayer.Player; import edu.regis.universeplayer.player.Player;
import edu.regis.universeplayer.browserCommands.CommandConfirmation; import edu.regis.universeplayer.browserCommands.CommandConfirmation;
import edu.regis.universeplayer.browserCommands.CommandReturn;
import edu.regis.universeplayer.browserCommands.QueryFuture; import edu.regis.universeplayer.browserCommands.QueryFuture;
import edu.regis.universeplayer.data.PlaybackEvent; import edu.regis.universeplayer.data.PlaybackEvent;
import edu.regis.universeplayer.data.Queue; import edu.regis.universeplayer.data.Queue;
import edu.regis.universeplayer.data.Song; import edu.regis.universeplayer.data.Song;
import edu.regis.universeplayer.player.PlayerManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -46,12 +45,6 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
.getBundle("lang.interface", Locale.getDefault()); .getBundle("lang.interface", Locale.getDefault());
private final ImageIcon PLAY_ICON, PAUSE_ICON; 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 playButton;
private final JButton nextButton; private final JButton nextButton;
private final JButton prevButton; private final JButton prevButton;
@@ -183,33 +176,34 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress); layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress);
Queue.getInstance().addSongChangeListener(this); Queue.getInstance().addSongChangeListener(this);
PlayerManager.getPlayers().addPlaybackListener(this);
} }
private void seek(float value) private void seek(float value)
{ {
this.service.execute(() -> { this.service.execute(() -> {
if (this.currentPlayer != null) QueryFuture<Void> command = PlayerManager.getPlayers().seek(value);
{
QueryFuture<Void> command = this.currentPlayer.seek(value);
try try
{ {
if (!command.getConfirmation().wasSuccessful()) if (command != null && !command.getConfirmation()
.wasSuccessful())
{ {
logger.error("Could not run command",
command.getConfirmation().getError());
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
command.getConfirmation().getError(), command.getConfirmation().getError(),
command.getConfirmation().getMessage(), command.getConfirmation().getMessage(),
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
logger.error("Could not run command",
command.getConfirmation().getError());
} }
} }
catch (ExecutionException | InterruptedException e) catch (ExecutionException | InterruptedException e)
{ {
logger.error("Could not run command", e); logger.error("Could not run command",
JOptionPane.showMessageDialog(this, e.getMessage(), langs e);
.getString( JOptionPane.showMessageDialog(this,
"error.command"), JOptionPane.ERROR_MESSAGE); e,
} e.getMessage(),
JOptionPane.ERROR_MESSAGE);
} }
}); });
this.triggerCommandListeners(PlaybackCommand.SEEK, value); this.triggerCommandListeners(PlaybackCommand.SEEK, value);
@@ -219,17 +213,16 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
{ {
this.service.execute(() -> { this.service.execute(() -> {
try try
{
if (this.currentPlayer != null)
{ {
QueryFuture<PlaybackStatus> status = QueryFuture<PlaybackStatus> status =
this.currentPlayer.getStatus(); PlayerManager.getPlayers().getStatus();
CommandConfirmation confirmation = status.getConfirmation(); CommandConfirmation confirmation = status.getConfirmation();
if (status.getConfirmation().wasSuccessful()) if (status.getConfirmation().wasSuccessful())
{ {
switch (status.get()) switch (status.get())
{ {
case PAUSED -> confirmation = this.currentPlayer.play() case PAUSED -> confirmation = PlayerManager.getPlayers()
.play()
.getConfirmation(); .getConfirmation();
case STOPPED, EMPTY -> { case STOPPED, EMPTY -> {
if (Queue.getInstance().size() > 0) if (Queue.getInstance().size() > 0)
@@ -241,7 +234,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
} }
else else
{ {
confirmation = this.currentPlayer.play() confirmation = PlayerManager.getPlayers().play()
.getConfirmation(); .getConfirmation();
} }
} }
@@ -250,13 +243,12 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
} }
if (confirmation != null && !confirmation.wasSuccessful()) if (confirmation != null && !confirmation.wasSuccessful())
{ {
logger.error("Could not run command",
confirmation.getError());
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
confirmation.getError(), confirmation.getError(),
confirmation.getMessage(), confirmation.getMessage(),
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
logger.error("Could not run command",
confirmation.getError());
}
} }
} }
catch (ExecutionException | InterruptedException e) catch (ExecutionException | InterruptedException e)
@@ -274,29 +266,27 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
{ {
this.service.execute(() -> { this.service.execute(() -> {
try try
{
if (this.currentPlayer != null)
{ {
QueryFuture<PlaybackStatus> status = QueryFuture<PlaybackStatus> status =
this.currentPlayer.getStatus(); PlayerManager.getPlayers().getStatus();
CommandConfirmation confirmation = status.getConfirmation(); CommandConfirmation confirmation = status.getConfirmation();
if (status.getConfirmation().wasSuccessful()) if (status.getConfirmation().wasSuccessful())
{ {
switch (status.get()) switch (status.get())
{ {
case PLAYING -> confirmation = case PLAYING -> confirmation =
this.currentPlayer.pause().getConfirmation(); PlayerManager.getPlayers().pause()
.getConfirmation();
} }
} }
if (confirmation != null && !confirmation.wasSuccessful()) if (confirmation != null && !confirmation.wasSuccessful())
{ {
logger.error("Could not run command",
confirmation.getError());
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
confirmation.getError(), confirmation.getError(),
confirmation.getMessage(), confirmation.getMessage(),
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
logger.error("Could not run command",
confirmation.getError());
}
} }
} }
catch (ExecutionException | InterruptedException e) catch (ExecutionException | InterruptedException e)
@@ -317,19 +307,19 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
{ {
this.service.execute(() -> { this.service.execute(() -> {
try try
{
if (this.currentPlayer != null)
{ {
QueryFuture<PlaybackStatus> status = QueryFuture<PlaybackStatus> status =
this.currentPlayer.getStatus(); PlayerManager.getPlayers().getStatus();
CommandConfirmation confirmation = status.getConfirmation(); CommandConfirmation confirmation = status.getConfirmation();
if (status.getConfirmation().wasSuccessful()) if (status.getConfirmation().wasSuccessful())
{ {
switch (status.get()) switch (status.get())
{ {
case PAUSED -> confirmation = this.currentPlayer.play() case PAUSED -> confirmation = PlayerManager.getPlayers()
.play()
.getConfirmation(); .getConfirmation();
case PLAYING -> confirmation = this.currentPlayer.pause() case PLAYING -> confirmation = PlayerManager.getPlayers()
.pause()
.getConfirmation(); .getConfirmation();
case STOPPED, EMPTY -> { case STOPPED, EMPTY -> {
if (Queue.getInstance().size() > 0) if (Queue.getInstance().size() > 0)
@@ -341,7 +331,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
} }
else else
{ {
confirmation = this.currentPlayer.play() confirmation = PlayerManager.getPlayers().play()
.getConfirmation(); .getConfirmation();
} }
} }
@@ -358,7 +348,6 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
confirmation.getError()); confirmation.getError());
} }
} }
}
catch (ExecutionException | InterruptedException e) catch (ExecutionException | InterruptedException e)
{ {
logger.error("Could not get current playback status", e); logger.error("Could not get current playback status", e);
@@ -449,15 +438,25 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
@Override @Override
public void onSongChange(Queue queue) public void onSongChange(Queue queue)
{
if (this.currentSong != null)
{
if (this.currentPlayer != null)
{ {
this.service.execute(() -> { this.service.execute(() -> {
QueryFuture<Void> command = this.currentPlayer.stopSong(); QueryFuture<Void> command = PlayerManager.getPlayers().stopSong();
try 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()) if (!command.getConfirmation().wasSuccessful())
{ {
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this,
@@ -467,76 +466,31 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
logger.error("Could not run command", logger.error("Could not run command",
command.getConfirmation().getError()); command.getConfirmation().getError());
} }
} else
catch (ExecutionException | InterruptedException e)
{ {
logger.error("Could not get current playback status", e); SwingUtilities.invokeLater(() -> {
JOptionPane
.showMessageDialog(this, e.getMessage(), langs
.getString(
"error.command"), JOptionPane.ERROR_MESSAGE);
}
});
}
}
this.currentSong = queue.getCurrentSong();
this.playButton.setIcon(PLAY_ICON); this.playButton.setIcon(PLAY_ICON);
if (this.currentSong != null) this.progress.setMaximum((int) (PlayerManager
{ .getPlayers()
this.currentPlayer = Player.REGISTERED_PLAYERS .getCurrentSong().duration / 1000));
.get(this.currentSong.getClass());
this.progress.setMaximum((int) (this.currentSong.duration / 1000));
if (this.currentPlayer != null)
{
if (!this.currentPlayer.hasPlaybackListener(this))
{
this.currentPlayer.addPlaybackListener(this);
}
this.service.execute(() -> {
QueryFuture<Void> command =
this.currentPlayer.loadSong(this.currentSong);
try
{
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());
}
}
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.currentPlayer.play();
} }
else }
}
catch (ExecutionException | InterruptedException e)
{ {
logger.error("No logger found for song {}", this.currentSong logger.error("Could not get current playback status", e);
.getClass()); JOptionPane
} .showMessageDialog(this, e.getMessage(), langs
} .getString(
else "error.command"), JOptionPane.ERROR_MESSAGE);
{
this.currentPlayer = null;
this.progress.setMaximum(0);
} }
});
} }
@Override @Override
public void onPlaybackChanged(PlaybackEvent status) public void onPlaybackChanged(PlaybackEvent status)
{
if (status.getSource() != null && status
.getSource() == this.currentPlayer)
{ {
switch (status.getInfo().getStatus()) switch (status.getInfo().getStatus())
{ {
@@ -549,4 +503,3 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
.getSong().duration / 1000)); .getSong().duration / 1000));
} }
} }
}

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import com.wordpress.tips4java.ScrollablePanel; import com.wordpress.tips4java.ScrollablePanel;
import edu.regis.universeplayer.ClickListener; import edu.regis.universeplayer.ClickListener;

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import java.util.Collection; import java.util.Collection;
import java.util.EventListener; import java.util.EventListener;

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.player; package edu.regis.universeplayer.gui;
import com.wordpress.tips4java.ScrollablePanel; import com.wordpress.tips4java.ScrollablePanel;

View File

@@ -2,7 +2,7 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.localPlayer; package edu.regis.universeplayer.player;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;

View File

@@ -2,12 +2,12 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.browser; package edu.regis.universeplayer.player;
import edu.regis.universeplayer.PlaybackInfo; import edu.regis.universeplayer.PlaybackInfo;
import edu.regis.universeplayer.PlaybackListener; import edu.regis.universeplayer.PlaybackListener;
import edu.regis.universeplayer.PlaybackStatus; import edu.regis.universeplayer.PlaybackStatus;
import edu.regis.universeplayer.Player; import edu.regis.universeplayer.browser.Browser;
import edu.regis.universeplayer.browserCommands.*; import edu.regis.universeplayer.browserCommands.*;
import edu.regis.universeplayer.data.InternetSong; import edu.regis.universeplayer.data.InternetSong;
import edu.regis.universeplayer.data.PlaybackEvent; import edu.regis.universeplayer.data.PlaybackEvent;
@@ -59,6 +59,22 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
return browserRef; return browserRef;
} }
public BrowserPlayer()
{
Thread browserThread = new Thread(() -> {
try
{
this.browserRef = Browser.createBrowser();
this.browserRef.run();
}
catch (IOException | InterruptedException e)
{
logger.error("Could not initialize browser", e);
}
});
browserThread.start();
}
@Override @Override
public Song getCurrentSong() public Song getCurrentSong()
{ {

View File

@@ -1,20 +1,22 @@
/* /*
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer.localPlayer; package edu.regis.universeplayer.player;
import com.intervigil.wave.WaveReader; import com.intervigil.wave.WaveReader;
import edu.regis.universeplayer.PlaybackInfo; import edu.regis.universeplayer.PlaybackInfo;
import edu.regis.universeplayer.PlaybackListener; import edu.regis.universeplayer.PlaybackListener;
import edu.regis.universeplayer.PlaybackStatus; import edu.regis.universeplayer.PlaybackStatus;
import edu.regis.universeplayer.Player;
import edu.regis.universeplayer.browserCommands.CommandConfirmation; import edu.regis.universeplayer.browserCommands.CommandConfirmation;
import edu.regis.universeplayer.browserCommands.QueryFuture; import edu.regis.universeplayer.browserCommands.QueryFuture;
import edu.regis.universeplayer.data.LocalSong; import edu.regis.universeplayer.data.LocalSong;
import edu.regis.universeplayer.data.PlaybackEvent; import edu.regis.universeplayer.data.PlaybackEvent;
import edu.regis.universeplayer.data.Song; import edu.regis.universeplayer.data.Song;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import uk.co.caprica.vlcj.factory.MediaPlayerFactory; import uk.co.caprica.vlcj.factory.MediaPlayerFactory;
import uk.co.caprica.vlcj.media.MediaRef; import uk.co.caprica.vlcj.media.MediaRef;
import uk.co.caprica.vlcj.media.TrackType; import uk.co.caprica.vlcj.media.TrackType;
@@ -24,6 +26,7 @@ import uk.co.caprica.vlcj.player.base.StatusApi;
import uk.co.caprica.vlcj.player.component.AudioPlayerComponent; import uk.co.caprica.vlcj.player.component.AudioPlayerComponent;
import javax.swing.*; import javax.swing.*;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@@ -39,7 +42,8 @@ import java.util.concurrent.*;
*/ */
public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
{ {
private static final Logger logger = LoggerFactory.getLogger(LocalPlayer.class); private static final Logger logger = LoggerFactory
.getLogger(LocalPlayer.class);
private final MediaPlayerFactory playerFactory; private final MediaPlayerFactory playerFactory;
private final AudioPlayerComponent player; private final AudioPlayerComponent player;
@@ -73,7 +77,8 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
} }
/** /**
* Saves the current file as a WAVE file somewhere else. This method is meant for testing only. * Saves the current file as a WAVE file somewhere else. This method is
* meant for testing only.
* *
* @param file - The file to save to. * @param file - The file to save to.
*/ */
@@ -120,21 +125,35 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
this.stopSong(); this.stopSong();
} }
this.currentSong = song; this.currentSong = song;
this.player.mediaPlayer().media().play(song.file.getAbsolutePath()); if (!this.player.mediaPlayer().media()
.play(song.file.getAbsolutePath()))
{
return new NullFuture<>(new RuntimeException("Could not play " +
"song " + song));
}
else
{
return new NullFuture<>(); return new NullFuture<>();
} }
}
@Override @Override
public QueryFuture<Void> play() public QueryFuture<Void> play()
{ {
this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().play()); this.player.mediaPlayer()
.submit((WrappedRunnable) () -> this.player.mediaPlayer()
.controls()
.play());
return new NullFuture<>(); return new NullFuture<>();
} }
@Override @Override
public QueryFuture<Void> pause() public QueryFuture<Void> pause()
{ {
this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().pause()); this.player.mediaPlayer()
.submit((WrappedRunnable) () -> this.player.mediaPlayer()
.controls()
.pause());
return new NullFuture<>(); return new NullFuture<>();
} }
@@ -154,14 +173,19 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
@Override @Override
public QueryFuture<Void> stopSong() public QueryFuture<Void> stopSong()
{ {
this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().stop()); this.player.mediaPlayer()
.submit((WrappedRunnable) () -> this.player.mediaPlayer()
.controls()
.stop());
return new NullFuture<>(); return new NullFuture<>();
} }
@Override @Override
public QueryFuture<Void> seek(float time) public QueryFuture<Void> seek(float time)
{ {
this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls() this.player.mediaPlayer()
.submit((WrappedRunnable) () -> this.player.mediaPlayer()
.controls()
.setTime((long) (time * 1000))); .setTime((long) (time * 1000)));
return new NullFuture<>(); return new NullFuture<>();
} }
@@ -184,13 +208,15 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
@Override @Override
public QueryFuture<Float> getCurrentTime() public QueryFuture<Float> getCurrentTime()
{ {
return new NullFuture<>(this.player.mediaPlayer().status().time() / 1000F); return new NullFuture<>(this.player.mediaPlayer().status()
.time() / 1000F);
} }
@Override @Override
public QueryFuture<Float> getLength() public QueryFuture<Float> getLength()
{ {
return new NullFuture<>(this.player.mediaPlayer().status().length() / 1000F); return new NullFuture<>(this.player.mediaPlayer().status()
.length() / 1000F);
} }
@Override @Override
@@ -227,7 +253,8 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
* @param file - The file to read * @param file - The file to read
* @return An raw stream for the file * @return An raw stream for the file
* @throws FileNotFoundException - Thrown should the file not exist. * @throws FileNotFoundException - Thrown should the file not exist.
* @throws IOException - Thrown should an error occur when reading the file * @throws IOException - Thrown should an error occur when reading
* the file
*/ */
public static AudioFile getAudioStream(File file) throws IOException public static AudioFile getAudioStream(File file) throws IOException
{ {
@@ -235,12 +262,14 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
} }
/** /**
* Converts any audio file to a stream containing WAV audio file data (courtesy of FFMPEG). * Converts any audio file to a stream containing WAV audio file data
* (courtesy of FFMPEG).
* *
* @param file - The file to convert * @param file - The file to convert
* @return An input stream containing the file data * @return An input stream containing the file data
* @throws FileNotFoundException - Thrown should the file not exist. * @throws FileNotFoundException - Thrown should the file not exist.
* @throws IOException - Thrown should an error occur when reading the file * @throws IOException - Thrown should an error occur when reading
* the file
*/ */
protected static Process convertFile(File file) throws FileNotFoundException, IOException protected static Process convertFile(File file) throws FileNotFoundException, IOException
{ {
@@ -304,7 +333,8 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
/** /**
* The media started playing. * The media started playing.
* <p> * <p>
* There is no guarantee that a video output has been created at this point. * There is no guarantee that a video output has been created at this
* point.
* *
* @param mediaPlayer media player that raised the event * @param mediaPlayer media player that raised the event
*/ */
@@ -312,7 +342,10 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
public void playing(MediaPlayer mediaPlayer) public void playing(MediaPlayer mediaPlayer)
{ {
logger.debug("Local player playing."); logger.debug("Local player playing.");
SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, mediaPlayer.status().time(), PlaybackStatus.PLAYING))))); SwingUtilities.invokeLater(() -> this.listeners
.forEach(playbackListener -> playbackListener
.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, mediaPlayer
.status().time(), PlaybackStatus.PLAYING)))));
} }
/** /**
@@ -324,15 +357,19 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
public void paused(MediaPlayer mediaPlayer) public void paused(MediaPlayer mediaPlayer)
{ {
logger.debug("Local player paused."); logger.debug("Local player paused.");
SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, mediaPlayer.status().time(), PlaybackStatus.PAUSED))))); SwingUtilities.invokeLater(() -> this.listeners
.forEach(playbackListener -> playbackListener
.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, mediaPlayer
.status().time(), PlaybackStatus.PAUSED)))));
} }
/** /**
* Media stopped. * Media stopped.
* <p> * <p>
* A stopped event may be raised under certain circumstances even if the media player is not playing (e.g. as part * A stopped event may be raised under certain circumstances even if the
* of the associated media list player sub-item handling). Client applications must therefore be prepared to handle * media player is not playing (e.g. as part of the associated media list
* such a situation. * player sub-item handling). Client applications must therefore be prepared
* to handle such a situation.
* *
* @param mediaPlayer media player that raised the event * @param mediaPlayer media player that raised the event
*/ */
@@ -340,7 +377,10 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
public void stopped(MediaPlayer mediaPlayer) public void stopped(MediaPlayer mediaPlayer)
{ {
logger.debug("Local player stopped prematurely."); logger.debug("Local player stopped prematurely.");
SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, mediaPlayer.status().time(), PlaybackStatus.STOPPED))))); SwingUtilities.invokeLater(() -> this.listeners
.forEach(playbackListener -> playbackListener
.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, mediaPlayer
.status().time(), PlaybackStatus.STOPPED)))));
} }
/** /**
@@ -374,7 +414,10 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
public void finished(MediaPlayer mediaPlayer) public void finished(MediaPlayer mediaPlayer)
{ {
logger.debug("Local player finished."); logger.debug("Local player finished.");
SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, mediaPlayer.status().time(), PlaybackStatus.FINISHED))))); SwingUtilities.invokeLater(() -> this.listeners
.forEach(playbackListener -> playbackListener
.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, mediaPlayer
.status().time(), PlaybackStatus.FINISHED)))));
} }
/** /**
@@ -386,7 +429,9 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
@Override @Override
public void timeChanged(MediaPlayer mediaPlayer, long newTime) public void timeChanged(MediaPlayer mediaPlayer, long newTime)
{ {
SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, newTime / 1000F, PlaybackStatus.PLAYING))))); SwingUtilities.invokeLater(() -> this.listeners
.forEach(playbackListener -> playbackListener
.onPlaybackChanged(new PlaybackEvent(this, new PlaybackInfo(this.currentSong, newTime / 1000F, PlaybackStatus.PLAYING)))));
} }
/** /**
@@ -531,7 +576,8 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
* other application) starts/stops playing media. * other application) starts/stops playing media.
* *
* @param mediaPlayer media player that raised the event * @param mediaPlayer media player that raised the event
* @param corked <code>true</code> if corked; otherwise <code>false</code> * @param corked <code>true</code> if corked; otherwise
* <code>false</code>
*/ */
@Override @Override
public void corked(MediaPlayer mediaPlayer, boolean corked) public void corked(MediaPlayer mediaPlayer, boolean corked)
@@ -543,7 +589,8 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
* The audio was muted/un-muted. * The audio was muted/un-muted.
* *
* @param mediaPlayer media player that raised the event * @param mediaPlayer media player that raised the event
* @param muted <code>true</code> if muted; otherwise <code>false</code> * @param muted <code>true</code> if muted; otherwise
* <code>false</code>
*/ */
@Override @Override
public void muted(MediaPlayer mediaPlayer, boolean muted) public void muted(MediaPlayer mediaPlayer, boolean muted)
@@ -609,9 +656,10 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
* The event will be fired again if the media is played again after a native * The event will be fired again if the media is played again after a native
* stopped or finished event is received. * stopped or finished event is received.
* <p> * <p>
* Waiting for this event may be more reliable than using {@link #playing(MediaPlayer)} * Waiting for this event may be more reliable than using {@link
* or {@link #videoOutput(MediaPlayer, int)} in some cases (logo and marquee * #playing(MediaPlayer)} or {@link #videoOutput(MediaPlayer, int)} in some
* already mentioned, also setting audio tracks, sub-title tracks and so on). * cases (logo and marquee already mentioned, also setting audio tracks,
* sub-title tracks and so on).
* *
* @param mediaPlayer media player that raised the event * @param mediaPlayer media player that raised the event
*/ */
@@ -692,27 +740,40 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
private static class NullFuture<T> implements QueryFuture<T> private static class NullFuture<T> implements QueryFuture<T>
{ {
private final T value; private final T value;
private final CommandConfirmation confirmation;
NullFuture() NullFuture()
{ {
this(null); this((T) null);
} }
NullFuture(T returnVal) NullFuture(T returnVal)
{ {
this.value = returnVal; this.value = returnVal;
this.confirmation = new CommandConfirmation();
}
NullFuture(Throwable error)
{
this(error, null);
}
NullFuture(Throwable error, T returnVal)
{
this.value = returnVal;
this.confirmation = new CommandConfirmation(error);
} }
@Override @Override
public CommandConfirmation getConfirmation() throws CancellationException public CommandConfirmation getConfirmation() throws CancellationException
{ {
return new CommandConfirmation(); return this.confirmation;
} }
@Override @Override
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) public CommandConfirmation getConfirmation(long timeout, TimeUnit unit)
{ {
return new CommandConfirmation(); return this.getConfirmation();
} }
@Override @Override
@@ -742,7 +803,7 @@ public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
@Override @Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{ {
return this.value; return get();
} }
} }

View File

@@ -2,8 +2,10 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
package edu.regis.universeplayer; package edu.regis.universeplayer.player;
import edu.regis.universeplayer.PlaybackListener;
import edu.regis.universeplayer.PlaybackStatus;
import edu.regis.universeplayer.browserCommands.QueryFuture; import edu.regis.universeplayer.browserCommands.QueryFuture;
import edu.regis.universeplayer.data.Song; import edu.regis.universeplayer.data.Song;
@@ -19,31 +21,6 @@ import java.util.HashMap;
*/ */
public interface Player<T extends Song> 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. * Obtains the song currently playing.
* *

View File

@@ -0,0 +1,380 @@
package edu.regis.universeplayer.player;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.swing.JOptionPane;
import edu.regis.universeplayer.PlaybackListener;
import edu.regis.universeplayer.PlaybackStatus;
import edu.regis.universeplayer.browserCommands.CommandConfirmation;
import edu.regis.universeplayer.browserCommands.QueryFuture;
import edu.regis.universeplayer.data.InternetSong;
import edu.regis.universeplayer.data.LocalSong;
import edu.regis.universeplayer.data.PlaybackEvent;
import edu.regis.universeplayer.data.Queue;
import edu.regis.universeplayer.data.Song;
import edu.regis.universeplayer.gui.Interface;
/**
* The PlayerManager serves as the central access point for playing songs of any
* type.
*
* @author William Hubbard
* @version 0.1
*/
public class PlayerManager implements PlaybackListener
{
private static final Logger logger =
LoggerFactory.getLogger(PlayerManager.class);
private static PlayerManager INSTANCE;
private LinkedList<PlaybackListener> listeners = new LinkedList<>();
public static PlayerManager getPlayers()
{
if (INSTANCE == null)
{
INSTANCE = new PlayerManager();
}
return INSTANCE;
}
/**
* Links to other players.
*/
private final HashMap<Class<? extends Song>, Player> players =
new HashMap<>();
/**
* A reference to the player currently playing a song.
*/
private Player currentPlayer;
/**
* A reference to the song currently being played.
*/
private Song currentSong;
public PlayerManager()
{
this.initPlayers();
}
/**
* 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.
*/
public Player<?> getCompatiblePlayer(Song song)
{
Player<?> player = this.players.get(song.getClass());
if (player == null)
{
for (Class<? extends Song> songClass : this.players.keySet())
{
if (songClass.isAssignableFrom(song.getClass()))
{
player = this.players.get(songClass);
break;
}
}
}
return player;
}
/**
* Initializes the default players.
*/
private void initPlayers()
{
this.initPlayer(LocalSong.class, new LocalPlayer());
this.initPlayer(InternetSong.class, new BrowserPlayer());
}
private <T extends Song> void initPlayer(Class<T> songType,
Player<T> player)
{
boolean playerRegistered = this.players.containsValue(player);
this.players.put(songType, player);
if (!playerRegistered)
{
player.addPlaybackListener(this);
}
}
/**
* Performs the necessary shutdown procedures for all players.
*/
public void shutdownPlayers()
{
this.players.values().forEach(Player::close);
}
/**
* Obtains the active song player.
*
* @return - The player currently playing a song.
*/
protected Player getCurrentPlayer()
{
return this.currentPlayer;
}
/**
* Obtains the active song.
*
* @return - The song currently playing.
*/
public Song getCurrentSong()
{
return this.currentSong;
}
/**
* Creates a QueryFuture response for if no player is found.
*
* @param message - The message to send.
* @return A new future to return.
*/
private <T> QueryFuture<T> getNullPlayer(String message, T returnValue)
{
return new QueryFuture<T>()
{
@Override
public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException
{
if (returnValue instanceof Throwable)
{
return new CommandConfirmation((Throwable) returnValue);
}
else
{
return new CommandConfirmation(message);
}
}
@Override
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
return this.getConfirmation();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning)
{
return false;
}
@Override
public boolean isCancelled()
{
return false;
}
@Override
public boolean isDone()
{
return true;
}
@Override
public T get() throws InterruptedException, ExecutionException
{
return returnValue;
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
return this.get();
}
};
}
/**
* Creates a QueryFuture response for if no player is found.
*
* @param message - The error to send.
* @return A new future to return.
*/
private <T> QueryFuture<T> getNullPlayer(Throwable message, T returnValue)
{
return new QueryFuture<T>()
{
@Override
public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException
{
return new CommandConfirmation(message);
}
@Override
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
return this.getConfirmation();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning)
{
return false;
}
@Override
public boolean isCancelled()
{
return false;
}
@Override
public boolean isDone()
{
return true;
}
@Override
public T get() throws InterruptedException, ExecutionException
{
return returnValue;
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
return this.get();
}
};
}
public QueryFuture<Void> playSong(Song song)
{
if (this.currentSong != null)
{
this.currentPlayer.stopSong();
}
this.currentSong = null;
this.currentPlayer = this.getCompatiblePlayer(song);
if (this.currentPlayer == null)
{
throw new IllegalArgumentException("Unknown song type " + song
.getClass());
}
this.currentSong = song;
return this.currentPlayer.loadSong(song);
}
public QueryFuture<PlaybackStatus> getStatus()
{
if (this.currentPlayer != null)
{
return this.currentPlayer.getStatus();
}
return this.getNullPlayer("Command successful", PlaybackStatus.EMPTY);
}
public QueryFuture<Void> seek(float time)
{
if (this.currentPlayer != null)
{
return this.currentPlayer.seek(time);
}
return null;
}
public QueryFuture<Void> play()
{
if (this.currentPlayer != null && this.currentSong != null)
{
return this.currentPlayer.play();
}
return null;
}
public QueryFuture<Void> pause()
{
if (this.currentPlayer != null && this.currentSong != null)
{
return this.currentPlayer.pause();
}
return null;
}
public QueryFuture<Void> toggle()
{
if (this.currentPlayer != null && this.currentSong != null)
{
return this.currentPlayer.togglePlayback();
}
return null;
}
public QueryFuture<Void> stopSong()
{
if (this.currentPlayer != null && this.currentSong != null)
{
this.currentSong = null;
return this.currentPlayer.stopSong();
}
return null;
}
public void addPlaybackListener(PlaybackListener listener)
{
if (!this.hasPlaybackListener(listener))
{
this.listeners.add(listener);
}
}
public boolean hasPlaybackListener(PlaybackListener listener)
{
return this.listeners.contains(listener);
}
public void removePlaybackListener(PlaybackListener listener)
{
this.listeners.remove(listener);
}
/**
* Called when playback is changed.
*
* @param status - The playback status.
*/
@Override
public void onPlaybackChanged(PlaybackEvent status)
{
if (status.getSource() == this.currentPlayer)
{
for (PlaybackListener l : this.listeners)
{
l.onPlaybackChanged(status);
}
}
}
/**
* Sends an error to the browser player.
* @param forward - Whether the error should be thrown in a foreground
* script or a background script.
* @return The command future.
*/
public QueryFuture<Void> throwError(boolean forward)
{
BrowserPlayer player =
(BrowserPlayer) this.players.get(InternetSong.class);
if (player == null)
{
return this.getNullPlayer(new NullPointerException(
"No browser found"), null);
}
return player.throwError(forward);
}
}

View File

@@ -2,7 +2,8 @@
* Copyright (c) 2021 William Hubbard. All Rights Reserved. * Copyright (c) 2021 William Hubbard. All Rights Reserved.
*/ */
import edu.regis.universeplayer.player.Interface; import edu.regis.universeplayer.ConfigManager;
import org.junit.Test; import org.junit.Test;
import java.io.File; import java.io.File;
@@ -13,53 +14,4 @@ import static junit.framework.Assert.assertTrue;
public class DBTest public class DBTest
{ {
@Test
public void test() throws ClassNotFoundException, SQLException
{
try
{
Class.forName("org.sqlite.JDBC");
Connection db;
Statement stat;
ResultSet tableResult;
// db = DriverManager.getConnection("jdbc:sqlite:" + new File(Interface.getDataDir().getAbsolutePath(), "universalmusictest.db").getAbsolutePath());
// db.setAutoCommit(false);
// stat = db.createStatement();
// tableResult = stat.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='INFO';");
// if (!tableResult.next())
// {
// System.out.println("Creating song table");
// /*
// * Create the table
// */
// stat.executeUpdate("CREATE TABLE INFO" +
// "(DATA INTEGER PRIMARY KEY NOT NULL);");
// }
// else
// {
// System.out.println("Clearing table");
// stat.executeUpdate("DELETE FROM INFO;");
// }
// tableResult.close();
// stat.executeUpdate("INSERT INTO INFO (DATA) VALUES (1);");
// stat.close();
// db.commit();
// db.close();
db = DriverManager.getConnection("jdbc:sqlite:" + new File(Interface.getDataDir().getAbsolutePath(), "universalmusictest.db").getAbsolutePath());
db.setAutoCommit(false);
stat = db.createStatement();
tableResult = stat.executeQuery("SELECT (DATA) FROM INFO");
assertTrue(tableResult.next());
assertEquals(1, tableResult.getInt("DATA"));
tableResult.close();
stat.close();
db.close();
}
catch (SQLException e)
{
e.printStackTrace();
throw e;
}
}
} }