Established a connection between the Interface and the browser.
All we're doing right now is pinging, but it should be fairly simple to get an API working now that the browser and interface can talk.
This commit is contained in:
@@ -4,8 +4,11 @@
|
||||
|
||||
package edu.regis.universeplayer;
|
||||
|
||||
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/**
|
||||
* This interface serves as the connection to a music player of some sort, whether it be
|
||||
* browser-based or from a file.
|
||||
@@ -22,54 +25,65 @@ public interface Player<T extends Song>
|
||||
* @return The current song, or null if none is playing.
|
||||
*/
|
||||
Song getCurrentSong();
|
||||
|
||||
|
||||
/**
|
||||
* Loads up a song
|
||||
*
|
||||
* @param song - The song to load.
|
||||
* @return A confirmation of whether the command was successful or not.
|
||||
*/
|
||||
void play(T song);
|
||||
|
||||
QueryFuture<Void> loadSong(T song);
|
||||
|
||||
/**
|
||||
* Enables playback of the current song, if one is active.
|
||||
* @return A confirmation of whether the command was successful or not.
|
||||
*/
|
||||
void play();
|
||||
|
||||
QueryFuture<Void> play();
|
||||
|
||||
/**
|
||||
* Pauses playback of the current song.
|
||||
* @return A confirmation of whether the command was successful or not.
|
||||
*/
|
||||
void pause();
|
||||
|
||||
QueryFuture<Void> pause();
|
||||
|
||||
/**
|
||||
* Toggles between playing and pausing the current song.
|
||||
* @return A confirmation of whether the command was successful or not.
|
||||
*/
|
||||
void togglePlayback();
|
||||
|
||||
QueryFuture<Void> togglePlayback();
|
||||
|
||||
/**
|
||||
* Sets the current song time to the specified position.
|
||||
*
|
||||
* @param time - The specified time in the song, in seconds.
|
||||
* @return A confirmation of whether the command was successful or not.
|
||||
*/
|
||||
void seek(float time);
|
||||
|
||||
QueryFuture<Void> seek(float time);
|
||||
|
||||
/**
|
||||
* Checks to see if the song is paused.
|
||||
*
|
||||
* @return Whether or not the song is paused.
|
||||
*/
|
||||
boolean isPaused();
|
||||
|
||||
QueryFuture<Boolean> isPaused();
|
||||
|
||||
/**
|
||||
* Obtains the time we are currently at in the current song.
|
||||
*
|
||||
* @return - The current song position in seconds, or -1 if no song is playing.
|
||||
*/
|
||||
float getCurrentTime();
|
||||
|
||||
QueryFuture<Float> getCurrentTime();
|
||||
|
||||
/**
|
||||
* Gets the length of the current song.
|
||||
*
|
||||
* @return The song length, in seconds.
|
||||
*/
|
||||
float getLength();
|
||||
QueryFuture<Float> getLength();
|
||||
|
||||
/**
|
||||
* Closes the player.
|
||||
* @return A confirmation of whether the player was successfully closed.
|
||||
*/
|
||||
QueryFuture<Void> close();
|
||||
}
|
||||
|
||||
@@ -4,11 +4,20 @@
|
||||
|
||||
package edu.regis.universeplayer.browser;
|
||||
|
||||
import edu.regis.universeplayer.Player;
|
||||
import edu.regis.universeplayer.browserCommands.*;
|
||||
import edu.regis.universeplayer.data.Song;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.net.ConnectException;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* This serves as a central point for controlling the browser process.
|
||||
@@ -16,23 +25,88 @@ import java.io.IOException;
|
||||
* @author William Hubbard
|
||||
* @since 0.1
|
||||
*/
|
||||
public class Browser
|
||||
public class Browser extends MessageRunner implements Player<InternetSong>
|
||||
{
|
||||
private static Logger logger = LoggerFactory.getLogger(Browser.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(Browser.class);
|
||||
private final Socket socket;
|
||||
private final Process process;
|
||||
|
||||
private static Process process;
|
||||
public static Browser createBrowser() throws IOException, InterruptedException
|
||||
{
|
||||
/*
|
||||
* The maximum number of attempts that will be made to establish a
|
||||
* connection.
|
||||
*/
|
||||
final int MAX_ATTEMPTS = 20;
|
||||
/*
|
||||
* How long the thread will sleep between connection attempts, in
|
||||
* milliseconds.
|
||||
*/
|
||||
final long SLEEP_TIME = 500;
|
||||
int startExit;
|
||||
Socket socket = null;
|
||||
Browser browser;
|
||||
Process browserProcess = launchBrowser();
|
||||
/*
|
||||
* Wait for the browser to fully start.
|
||||
*/
|
||||
startExit = browserProcess.waitFor();
|
||||
if (startExit != 0)
|
||||
{
|
||||
logger.error("Error in browser launch (exit code {})", startExit);
|
||||
try (Scanner scanner = new Scanner(browserProcess.getErrorStream()))
|
||||
{
|
||||
while (scanner.hasNextLine())
|
||||
{
|
||||
logger.error(scanner.nextLine());
|
||||
}
|
||||
}
|
||||
throw new IOException("Error in browser launch (exit code " + startExit + ")");
|
||||
}
|
||||
logger.debug("Browser started.");
|
||||
|
||||
ConnectException connErr = null;
|
||||
logger.debug("Attempting connection");
|
||||
for (int attempts = 0; socket == null && attempts < MAX_ATTEMPTS; attempts++)
|
||||
{
|
||||
try
|
||||
{
|
||||
socket = new Socket(BrowserConstants.IP, BrowserConstants.PORT);
|
||||
}
|
||||
catch (ConnectException e)
|
||||
{
|
||||
connErr = e;
|
||||
logger.debug("Connection attempt {} failed, trying again", attempts);
|
||||
Thread.sleep(SLEEP_TIME);
|
||||
}
|
||||
}
|
||||
if (socket == null)
|
||||
{
|
||||
throw connErr;
|
||||
}
|
||||
logger.debug("Browser connection established.");
|
||||
browser = new Browser(socket, browserProcess);
|
||||
return browser;
|
||||
}
|
||||
|
||||
private Browser(Socket socket, Process process) throws IOException
|
||||
{
|
||||
super("BrowserRunner", socket.getInputStream(), socket.getOutputStream());
|
||||
this.socket = socket;
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method for launching a browser instance
|
||||
*
|
||||
* @throws IOException - Thrown if there is a problem launching the browser.
|
||||
*/
|
||||
public static void launchBrowser() throws IOException
|
||||
private static Process launchBrowser() throws IOException
|
||||
{
|
||||
Process process = null;
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
String arch = System.getProperty("os.arch").toLowerCase();
|
||||
String args;
|
||||
int startExit;
|
||||
File browserDir = new File(System.getProperty("user.dir"), "browser");
|
||||
|
||||
if (!browserDir.exists())
|
||||
@@ -46,12 +120,12 @@ public class Browser
|
||||
{
|
||||
if (arch.contains("64"))
|
||||
{
|
||||
logger.info("Starting Windows x86_64 browser");
|
||||
logger.debug("Starting Windows x86_64 browser");
|
||||
process = Runtime.getRuntime().exec(new File(browserDir, "windows64/firefox.exe").getAbsolutePath() + args);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.info("Starting Windows x86 browser");
|
||||
logger.debug("Starting Windows x86 browser");
|
||||
process = Runtime.getRuntime().exec(new File(browserDir, "windows32/firefox.exe").getAbsolutePath() + args);
|
||||
}
|
||||
}
|
||||
@@ -59,12 +133,12 @@ public class Browser
|
||||
{
|
||||
if (arch.contains("64"))
|
||||
{
|
||||
logger.info("Starting Linux x86_64 browser");
|
||||
logger.debug("Starting Linux x86_64 browser");
|
||||
process = Runtime.getRuntime().exec(new File(browserDir, "linux64/firefox").getAbsolutePath() + args);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.info("Starting Linux 86 browser");
|
||||
logger.debug("Starting Linux 86 browser");
|
||||
process = Runtime.getRuntime().exec(new File(browserDir, "linux32/firefox").getAbsolutePath() + args);
|
||||
}
|
||||
}
|
||||
@@ -72,12 +146,35 @@ public class Browser
|
||||
{
|
||||
throw new IOException("Could not find Firefox installation for OS " + os + " " + arch);
|
||||
}
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Song getCurrentSong()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Void> loadSong(InternetSong song)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ForwardedFuture<Void>(this.sendObject(new CommandLoadSong(song.location)));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not send message", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the browser process to shut down.
|
||||
*/
|
||||
public static void closeBrowser()
|
||||
@Override
|
||||
public QueryFuture<Void> close()
|
||||
{
|
||||
if (process != null)
|
||||
{
|
||||
@@ -89,5 +186,118 @@ public class Browser
|
||||
{
|
||||
logger.info("Process already destroyed.");
|
||||
}
|
||||
try
|
||||
{
|
||||
this.socket.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not close browser socket", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@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> seek(float time)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Boolean> isPaused()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Float> getCurrentTime()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryFuture<Float> getLength()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private class ForwardedFuture<T> implements QueryFuture<T>
|
||||
{
|
||||
private final Future future;
|
||||
|
||||
ForwardedFuture(Future future)
|
||||
{
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
private CommandReturn<T> getVal() throws ExecutionException, InterruptedException
|
||||
{
|
||||
return ((CommandReturn<T>) this.future.get());
|
||||
}
|
||||
|
||||
private CommandReturn<T> getVal(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException
|
||||
{
|
||||
return ((CommandReturn<T>) this.future.get(timeout, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException
|
||||
{
|
||||
return this.getVal().getConfirmation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
return this.getVal(timeout, unit).getConfirmation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning)
|
||||
{
|
||||
return this.future.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return this.future.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone()
|
||||
{
|
||||
return this.future.isDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws InterruptedException, ExecutionException
|
||||
{
|
||||
return this.getVal().getReturnValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||
{
|
||||
return this.getVal(timeout, unit).getReturnValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,16 @@ import java.awt.event.ComponentListener;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import edu.regis.universeplayer.Player;
|
||||
import edu.regis.universeplayer.browser.Browser;
|
||||
import edu.regis.universeplayer.browser.MessageManager;
|
||||
import edu.regis.universeplayer.data.CollectionType;
|
||||
@@ -54,43 +58,65 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
/**
|
||||
* A link to the browser.
|
||||
*/
|
||||
private MessageManager browser;
|
||||
private ArrayList<Player> players = new ArrayList<>();
|
||||
private int currentPlayer = -1;
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
/*
|
||||
* Add this just in case of a crash or something. It won't work if the
|
||||
* program is forcible terminated by the OS, but it could be helpful
|
||||
* otherwise.
|
||||
*/
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
Browser.closeBrowser();
|
||||
}));
|
||||
logger.info("Starting application");
|
||||
Interface inter = new Interface();
|
||||
Thread browserThread;
|
||||
Interface inter = null;
|
||||
Browser browser;
|
||||
try
|
||||
{
|
||||
inter.browser = new MessageManager();
|
||||
/*
|
||||
* Add this just in case of a crash or something. It won't work if the
|
||||
* program is forcible terminated by the OS, but it could be helpful
|
||||
* otherwise.
|
||||
*/
|
||||
logger.info("Starting application");
|
||||
inter = new Interface();
|
||||
inter.pack();
|
||||
inter.setVisible(true);
|
||||
|
||||
try
|
||||
{
|
||||
inter.players.add(browser = Browser.createBrowser());
|
||||
browserThread = new Thread(browser);
|
||||
browserThread.start();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(browser::close));
|
||||
|
||||
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, "Could not open browser background", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not open browser communication", e);
|
||||
JOptionPane.showMessageDialog(null, e, "Could not open browser communication", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
inter.pack();
|
||||
inter.setVisible(true);
|
||||
|
||||
/*
|
||||
* Launch the browser in the background.
|
||||
*/
|
||||
try
|
||||
{
|
||||
Browser.launchBrowser();
|
||||
}
|
||||
catch (IOException e)
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error("Could not open browser background", e);
|
||||
JOptionPane.showMessageDialog(inter, e, "Could not open browser background", JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(inter != null && inter.isVisible() ? inter : null, e, "Error!", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,18 +217,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
@Override
|
||||
public void windowClosing(WindowEvent windowEvent)
|
||||
{
|
||||
Browser.closeBrowser();
|
||||
if (this.browser != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.browser.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not close browser", e);
|
||||
}
|
||||
}
|
||||
this.players.forEach(Player::close);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -243,29 +258,31 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
||||
@Override
|
||||
public void onCommand(PlaybackCommand command, Object data)
|
||||
{
|
||||
Object message = null;
|
||||
if (this.browser != null)
|
||||
Player player;
|
||||
if (this.currentPlayer >= 0 && this.currentPlayer < this.players.size())
|
||||
{
|
||||
synchronized (this.browser)
|
||||
player = this.players.get(this.currentPlayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NullPointerException("No player available");
|
||||
}
|
||||
switch (command)
|
||||
{
|
||||
case PLAY -> {
|
||||
if (data instanceof Song)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.browser.ping();
|
||||
message = this.browser.getMessage();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not send message to browser", e);
|
||||
JOptionPane.showMessageDialog(this, e, "Could not send message to browser", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
player.loadSong((Song) data);
|
||||
}
|
||||
}
|
||||
if (message != null)
|
||||
{
|
||||
if ("ping".equals(message))
|
||||
{
|
||||
JOptionPane.showMessageDialog(this, "Ping received!");
|
||||
}
|
||||
case PAUSE -> {
|
||||
}
|
||||
case NEXT -> {
|
||||
}
|
||||
case PREVIOUS -> {
|
||||
}
|
||||
case SEEK -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user