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:
@@ -27,15 +27,15 @@ import java.util.HashMap;
|
||||
*/
|
||||
public class BrowserLink extends MessageRunner
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(BrowserLink.class);
|
||||
// private static final Logger logger = LoggerFactory.getLogger(BrowserLink.class);
|
||||
public static final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Creates a message runner.
|
||||
*/
|
||||
public BrowserLink()
|
||||
public BrowserLink(String name)
|
||||
{
|
||||
super(System.in, System.out);
|
||||
super(name, System.in, System.out);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,7 +64,7 @@ public class BrowserLink extends MessageRunner
|
||||
|
||||
ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
|
||||
lengthBuffer.order(ByteOrder.nativeOrder());
|
||||
logger.debug("Writing message {} {}", messageNum, messageString);
|
||||
getLogger().debug("Writing message {} {}", messageNum, messageString);
|
||||
/*
|
||||
* Writes the message length
|
||||
*/
|
||||
@@ -92,7 +92,7 @@ public class BrowserLink extends MessageRunner
|
||||
readLength = in.read(lengthBuffer.array());
|
||||
if (readLength == 0)
|
||||
{
|
||||
logger.debug("Input stream closed, no more messages.");
|
||||
getLogger().debug("Input stream closed, no more messages.");
|
||||
return null;
|
||||
}
|
||||
messageLength = lengthBuffer.getInt();
|
||||
@@ -101,7 +101,7 @@ public class BrowserLink extends MessageRunner
|
||||
readLength = in.read(message);
|
||||
if (readLength < message.length)
|
||||
{
|
||||
logger.warn("Message shorter than reported (expected {} bytes, got {} bytes)", message.length, readLength);
|
||||
getLogger().warn("Message shorter than reported (expected {} bytes, got {} bytes)", message.length, readLength);
|
||||
}
|
||||
messageJson = gson.fromJson(new String(message, StandardCharsets.UTF_8), JsonObject.class);
|
||||
messageNum = messageJson.get("messageNum").getAsInt();
|
||||
@@ -115,7 +115,7 @@ public class BrowserLink extends MessageRunner
|
||||
lengthBuffer.put(new byte[4]);
|
||||
lengthBuffer.clear();
|
||||
lengthBuffer.putInt(messageNum);
|
||||
logger.debug("Reading message {}", messageNum);
|
||||
getLogger().debug("Reading message {}", messageNum);
|
||||
return new byte[][] {lengthBuffer.array(), message};
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ public class BrowserLink extends MessageRunner
|
||||
}
|
||||
catch (ClassNotFoundException | ClassCastException e)
|
||||
{
|
||||
logger.error("Illegal class type " + message.get("type"), e);
|
||||
getLogger().error("Illegal class type " + message.get("type"), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ public class BrowserLink extends MessageRunner
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not parse message entry " + entry.getKey() + " (" + entry.getValue() + ")", e);
|
||||
getLogger().error("Could not parse message entry " + entry.getKey() + " (" + entry.getValue() + ")", e);
|
||||
}
|
||||
});
|
||||
return type;
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
|
||||
package edu.regis.universeplayer.addon;
|
||||
|
||||
import edu.regis.universeplayer.browserCommands.BrowserConstants;
|
||||
import edu.regis.universeplayer.browserCommands.MessageHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
@@ -16,10 +20,91 @@ public class Main
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Main.class);
|
||||
|
||||
public static void main(String[] args) throws ExecutionException, InterruptedException
|
||||
public static void main(String[] args) throws IOException, InterruptedException
|
||||
{
|
||||
ServerSocket socketSource = null;
|
||||
Socket socket = null;
|
||||
MessageHandler interfaceLink;
|
||||
BrowserLink browserLink;
|
||||
Thread interfaceThread, browserThread;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.debug("Connecting to browser");
|
||||
browserLink = new BrowserLink("BrowserLink");
|
||||
logger.debug("Setting up server");
|
||||
socketSource = new ServerSocket(BrowserConstants.PORT);
|
||||
logger.debug("Server started");
|
||||
socket = socketSource.accept();
|
||||
logger.debug("Connection received");
|
||||
interfaceLink = new MessageHandler("InterfaceHandler", socket.getInputStream(), socket.getOutputStream());
|
||||
/*
|
||||
* Pretty much just forwards any messages to the browser and
|
||||
* returns their value.
|
||||
*/
|
||||
interfaceLink.addListener((providedValue, previousReturn) -> {
|
||||
logger.debug("Forwarding message to browser: {}", providedValue);
|
||||
Object returnValue = browserLink.sendObject(providedValue).get();
|
||||
logger.debug("Forwarding return to interface: {}", returnValue);
|
||||
return returnValue;
|
||||
});
|
||||
|
||||
browserThread = new Thread(browserLink);
|
||||
interfaceThread = new Thread(interfaceLink);
|
||||
|
||||
logger.debug("Starting threads.");
|
||||
browserThread.start();
|
||||
interfaceThread.start();
|
||||
logger.debug("Joining threads.");
|
||||
browserThread.join();
|
||||
interfaceThread.join();
|
||||
logger.debug("Threads completed.");
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not initialize socket", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error("ERROR", e);
|
||||
throw e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
logger.debug("Shutting down interface link");
|
||||
if (socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not close socket", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
logger.debug("Shutting down server");
|
||||
try
|
||||
{
|
||||
socketSource.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not close server", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void testMain() throws ExecutionException, InterruptedException
|
||||
{
|
||||
LinkedList<Future<Object>> requests = new LinkedList<>();
|
||||
BrowserLink link = new BrowserLink();
|
||||
BrowserLink link = new BrowserLink("BrowserLink");
|
||||
Thread linkThread = new Thread(link);
|
||||
linkThread.start();
|
||||
logger.info("Sending message");
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</Console>
|
||||
<File name="File" fileName="addonInter.log" append="false">
|
||||
<PatternLayout>
|
||||
<Pattern>%d{HH:mm:ss.SSS} - %45c:%-4L - %-5level - %msg%n</Pattern>
|
||||
<Pattern>%d{HH:mm:ss.SSS} - %c.%M(%F:%L) - %-5level - %msg%n</Pattern>
|
||||
</PatternLayout>
|
||||
</File>
|
||||
</Appenders>
|
||||
@@ -17,5 +17,6 @@
|
||||
<Root level="debug">
|
||||
<AppenderRef ref="File"/>
|
||||
</Root>
|
||||
<Logger name="edu.regis.universeplayer.browserCommands.MessageRunner" level="trace"/>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
/**
|
||||
* Browser commands are used to send orders to the browser, requesting
|
||||
* confirmation as to whether those commands were completed or not.
|
||||
*/
|
||||
public interface BrowserCommand extends BrowserQuery<Void>
|
||||
{
|
||||
@Override
|
||||
default Class<Void> getReturnType()
|
||||
{
|
||||
return Void.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
public class BrowserConstants
|
||||
{
|
||||
/**
|
||||
* The IP that the connection will be hosted on.
|
||||
*/
|
||||
public static final String IP = "127.0.0.1";
|
||||
/**
|
||||
* The port both processes use for connection.
|
||||
*/
|
||||
public static final int PORT = 3000;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* A browser error is a special Throwable that contains information about an
|
||||
* error that occurred within the browser. This is so that we can properly log
|
||||
* browser errors under standard Java logging.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class BrowserError extends Throwable
|
||||
{
|
||||
private final String name;
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* Creats a browser error.
|
||||
*
|
||||
* @param name - The type of error thrown.
|
||||
* @param message - The message as reported by the browser.
|
||||
* @param stackTrace - The stack trace string provided by the browser.
|
||||
*/
|
||||
public BrowserError(String name, String message, String stackTrace)
|
||||
{
|
||||
super(name + ": " + message, null, false, true);
|
||||
this.name = name;
|
||||
this.message = message;
|
||||
this.setStackTrace(this.parseFirefoxTrace(stackTrace));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of error returned by the browser.
|
||||
*
|
||||
* @return - The browser error type.
|
||||
*/
|
||||
public String getType()
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the original message from the browser.
|
||||
*
|
||||
* @return - The browser error message.
|
||||
*/
|
||||
public String getBrowserMessage()
|
||||
{
|
||||
return this.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a stack trace string as per the specifications of Mozilla Firefox
|
||||
* version 30+.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack.
|
||||
*
|
||||
* @param stackTrace - The stack trace string provided by the browser.
|
||||
* @return A stack trace that Java can parse.
|
||||
*/
|
||||
private StackTraceElement[] parseFirefoxTrace(String stackTrace)
|
||||
{
|
||||
StackTraceElement trace;
|
||||
String methodName;
|
||||
String file;
|
||||
String fileLoc;
|
||||
int line;
|
||||
// int column;
|
||||
|
||||
/*
|
||||
* Parses the Firefox trace into
|
||||
*/
|
||||
String[] scriptTrace = stackTrace.split("\n");
|
||||
StackTraceElement[] javaTrace = new StackTraceElement[scriptTrace.length];
|
||||
for (int i = 0, l = scriptTrace.length; i < l; i++)
|
||||
{
|
||||
methodName = scriptTrace[i].substring(0, scriptTrace[i].indexOf('@'));
|
||||
file = scriptTrace[i].substring(methodName.length() + 1, scriptTrace[i].indexOf(':'));
|
||||
// column = Integer.parseInt(scriptTrace[i].substring(scriptTrace[i].lastIndexOf(':')));
|
||||
/*
|
||||
* Windows paths often have colons in their file name, so we can't use the first index of one.
|
||||
*/
|
||||
fileLoc = scriptTrace[i].substring(0, scriptTrace[i].lastIndexOf(':'));
|
||||
line = Integer.parseInt(fileLoc.substring(fileLoc.lastIndexOf(':')));
|
||||
|
||||
trace = new StackTraceElement(null, null, null, "", methodName, file, line);
|
||||
javaTrace[i] = trace;
|
||||
}
|
||||
|
||||
return javaTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a stack trace string as per the specifications of IE 10+ (and used
|
||||
* by modern Chromium browsers).
|
||||
* https://web.archive.org/web/20140210004225/https://msdn.microsoft.com/en-us/library/windows/apps/hh699850.aspx
|
||||
*
|
||||
* @param stackTrace - The stack trace string to parse.
|
||||
* @return A stack trace that Java can parse.
|
||||
*/
|
||||
private StackTraceElement[] parseChromeTrace(String stackTrace)
|
||||
{
|
||||
StackTraceElement trace;
|
||||
String methodName;
|
||||
String fileLoc;
|
||||
int line;
|
||||
// int column;
|
||||
|
||||
/*
|
||||
* Parses the Chromium trace.
|
||||
*/
|
||||
String[] scriptTrace = stackTrace.split("\n");
|
||||
/*
|
||||
* The first line of the trace contains the error message itself. We do
|
||||
* not want this in the stack trace.
|
||||
*/
|
||||
StackTraceElement[] javaTrace = new StackTraceElement[scriptTrace.length - 1];
|
||||
|
||||
for (int i = 1, l = scriptTrace.length; i < l; i++)
|
||||
{
|
||||
// column = Integer.parseInt(scriptTrace[i].substring(scriptTrace[i].lastIndexOf(':')));
|
||||
fileLoc = scriptTrace[i].substring(0, scriptTrace[i].lastIndexOf(':'));
|
||||
line = Integer.parseInt(fileLoc.substring(fileLoc.lastIndexOf(':')));
|
||||
methodName = fileLoc.substring(fileLoc.indexOf("at") + 3, fileLoc.lastIndexOf(':'));
|
||||
|
||||
trace = new StackTraceElement(null, null, null, "", methodName, null, line);
|
||||
javaTrace[i - 1] = trace;
|
||||
}
|
||||
|
||||
return javaTrace;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Objects of this type are used by the interface to request information from
|
||||
* the browser.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public interface BrowserQuery<T> extends Serializable
|
||||
{
|
||||
/**
|
||||
* Obtains the name of the command.
|
||||
* @return The command name.
|
||||
*/
|
||||
String getCommandName();
|
||||
|
||||
/**
|
||||
* Obtains the type of value this command returns.
|
||||
* @return The return type.
|
||||
*/
|
||||
Class<T> getReturnType();
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This class contains information as to the success of a command.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class CommandConfirmation implements Serializable
|
||||
{
|
||||
/**
|
||||
* The error returned by the browser, or null if execution was successful.
|
||||
*/
|
||||
private BrowserError errorCode;
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* Confirms a successful command.
|
||||
*/
|
||||
public CommandConfirmation()
|
||||
{
|
||||
this("Command successful");
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms a successful command.
|
||||
*
|
||||
* @param message - Details on the nature of the command execution.
|
||||
*/
|
||||
public CommandConfirmation(String message)
|
||||
{
|
||||
this((BrowserError) null);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms an unsuccessful command.
|
||||
*
|
||||
* @param error - The error thrown, or null if the command was successful.
|
||||
*/
|
||||
public CommandConfirmation(BrowserError error)
|
||||
{
|
||||
this.errorCode = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the command executed properly.
|
||||
*
|
||||
* @return True if the command was successful, false otherwise.
|
||||
*/
|
||||
public boolean wasSuccessful()
|
||||
{
|
||||
return this.errorCode == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the error returned by the browser.
|
||||
*
|
||||
* @return The error, or null if the command was successful.
|
||||
*/
|
||||
public BrowserError getError()
|
||||
{
|
||||
return this.errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains details as to the nature of the command execution.
|
||||
*
|
||||
* @return The detailed message on the command.
|
||||
*/
|
||||
public String getMessage()
|
||||
{
|
||||
return this.errorCode != null ? this.errorCode.getMessage() : this.message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* This command is used to tell the browser to load a song URL, such as a
|
||||
* YouTube video.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class CommandLoadSong implements BrowserCommand
|
||||
{
|
||||
/**
|
||||
* The song to load.
|
||||
*/
|
||||
private URL song;
|
||||
|
||||
/**
|
||||
* Used for serialization only. Do not use.
|
||||
*/
|
||||
public CommandLoadSong()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the browser to load a song.
|
||||
*
|
||||
* @param song - The song to load.
|
||||
*/
|
||||
public CommandLoadSong(URL song)
|
||||
{
|
||||
this.song = song;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the browser to load a song.
|
||||
*
|
||||
* @param song - The song to load.
|
||||
*/
|
||||
public CommandLoadSong(String song) throws MalformedURLException
|
||||
{
|
||||
this.song = new URL(song);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return "loadSong";
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the song that is to be loaded.
|
||||
*
|
||||
* @return - The loaded song.
|
||||
*/
|
||||
public URL getSong()
|
||||
{
|
||||
return this.song;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This wrapper class serves to return a browser return value over serialization protocols.
|
||||
*
|
||||
* @param <T> The type of return value.
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class CommandReturn<T> implements Serializable
|
||||
{
|
||||
private T returnValue;
|
||||
|
||||
private CommandConfirmation confirmation;
|
||||
|
||||
/**
|
||||
* For serialization only. Do not use.
|
||||
*/
|
||||
public CommandReturn()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new return object.
|
||||
*
|
||||
* @param value - The value to return.
|
||||
* @param confirmation - Information on the execution of the command.
|
||||
*/
|
||||
public CommandReturn(T value, CommandConfirmation confirmation)
|
||||
{
|
||||
this.returnValue = value;
|
||||
this.confirmation = confirmation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the value returned.
|
||||
*
|
||||
* @return The returned value. May be null.
|
||||
*/
|
||||
public T getReturnValue()
|
||||
{
|
||||
return this.returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains information about the execution of this command.
|
||||
*
|
||||
* @return The execution status.
|
||||
*/
|
||||
public CommandConfirmation getConfirmation()
|
||||
{
|
||||
return this.confirmation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
/**
|
||||
* The seek command tells the browser to move playback to a certain location.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class CommandSeek implements BrowserCommand
|
||||
{
|
||||
/**
|
||||
* The time to seek to.
|
||||
*/
|
||||
private float time;
|
||||
/**
|
||||
* Whether the {@link #time} parameter should be interpreted as relative to
|
||||
* the current position or absolute.
|
||||
*/
|
||||
private boolean relative;
|
||||
|
||||
/**
|
||||
* For serialization only. Do not use.
|
||||
*/
|
||||
public CommandSeek()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the browser to seek to a certain absolute time in the song.
|
||||
*
|
||||
* @param time - The time to seek to.
|
||||
*/
|
||||
public CommandSeek(float time)
|
||||
{
|
||||
this.time = time;
|
||||
this.relative = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the browser to seek to a certain time in the song.
|
||||
*
|
||||
* @param time - The time to seek to.
|
||||
* @param relative - Whether or not the time should be treated as relative
|
||||
* to the current play time or not.
|
||||
*/
|
||||
public CommandSeek(float time, boolean relative)
|
||||
{
|
||||
this.time = time;
|
||||
this.relative = relative;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return "seek";
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the time to seek to.
|
||||
*
|
||||
* @return The new playback time.
|
||||
*/
|
||||
public float getSeekTime()
|
||||
{
|
||||
return this.time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see whether the command should be interpreted as local to the
|
||||
* current play time.
|
||||
*
|
||||
* @return Whether the seek time is local or not.
|
||||
*/
|
||||
public boolean isRelative()
|
||||
{
|
||||
return this.relative;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
/**
|
||||
* This command sets the playback status of the browser.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class CommandSetPlayback implements BrowserCommand
|
||||
{
|
||||
/**
|
||||
* An enum for playback status.
|
||||
*/
|
||||
public enum Playback
|
||||
{
|
||||
PLAY, PAUSE
|
||||
}
|
||||
|
||||
/**
|
||||
* The playback status communicated by the interface.
|
||||
*/
|
||||
private Playback status;
|
||||
|
||||
/**
|
||||
* Creates a play command.
|
||||
* @return A new play command.
|
||||
*/
|
||||
public static CommandSetPlayback playCommand()
|
||||
{
|
||||
return new CommandSetPlayback(Playback.PLAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pause command.
|
||||
* @return A new pause command.
|
||||
*/
|
||||
public static CommandSetPlayback pauseCommand()
|
||||
{
|
||||
return new CommandSetPlayback(Playback.PAUSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for serialization only. Do not use.
|
||||
*/
|
||||
public CommandSetPlayback()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the browser to chang the playback.
|
||||
*
|
||||
* @param status - Whether the browser should be playing or pausing.
|
||||
*/
|
||||
public CommandSetPlayback(Playback status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return "playback";
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the playback command.
|
||||
*
|
||||
* @return Whether the browser should be playing or not.
|
||||
*/
|
||||
public Playback getPlayback()
|
||||
{
|
||||
return this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this command sets the browser playing.
|
||||
*
|
||||
* @return Whether the browser should be playing or not.
|
||||
*/
|
||||
public boolean shouldPlay()
|
||||
{
|
||||
return this.status == Playback.PLAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this command pauses
|
||||
*
|
||||
* @return Whether the browser should be playing or not.
|
||||
*/
|
||||
public boolean shouldPause()
|
||||
{
|
||||
return this.status == Playback.PAUSE;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
@@ -22,7 +23,9 @@ import java.util.concurrent.Future;
|
||||
*/
|
||||
public class MessageHandler implements Runnable, MessageSerializer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);
|
||||
private final Logger logger;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final InputStream input;
|
||||
private final OutputStream output;
|
||||
@@ -34,16 +37,25 @@ public class MessageHandler implements Runnable, MessageSerializer
|
||||
/**
|
||||
* Creates a message handler.
|
||||
*
|
||||
* @param name - The name of the handler. This is used in logging.
|
||||
* @param input - The input from our external source.
|
||||
* @param output - The output to the external source.
|
||||
*/
|
||||
public MessageHandler(InputStream input, OutputStream output)
|
||||
public MessageHandler(String name, InputStream input, OutputStream output)
|
||||
{
|
||||
this.name = name;
|
||||
this.logger = LoggerFactory.getLogger(name);
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
this.executor = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger()
|
||||
{
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
@@ -84,6 +96,7 @@ public class MessageHandler implements Runnable, MessageSerializer
|
||||
messageResponse = this.triggerListeners(messageOb);
|
||||
synchronized (this.messageResponses)
|
||||
{
|
||||
numBuffer.clear();
|
||||
this.messageResponses.put(numBuffer.getInt(), messageResponse);
|
||||
}
|
||||
}
|
||||
@@ -108,7 +121,7 @@ public class MessageHandler implements Runnable, MessageSerializer
|
||||
if (responses.getValue().isDone())
|
||||
{
|
||||
toRemove.add(responses.getKey());
|
||||
messageByte = serializeObject(responses.getValue());
|
||||
messageByte = serializeObject(responses.getValue().get());
|
||||
writeMessage(browserOut, responses.getKey(), messageByte);
|
||||
}
|
||||
}
|
||||
@@ -123,6 +136,10 @@ public class MessageHandler implements Runnable, MessageSerializer
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error(this.getClass().getName() + " Error", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
/*
|
||||
@@ -255,6 +272,6 @@ public class MessageHandler implements Runnable, MessageSerializer
|
||||
* have generated return values.
|
||||
* @return The value to be returned to the remote.
|
||||
*/
|
||||
Object onMessage(Object providedValue, Object previousReturn);
|
||||
Object onMessage(Object providedValue, Object previousReturn) throws IOException, ExecutionException, InterruptedException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ import java.util.concurrent.TimeoutException;
|
||||
*/
|
||||
public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MessageRunner.class);
|
||||
private final Logger logger;
|
||||
|
||||
private final String name;
|
||||
private final InputStream input;
|
||||
private final OutputStream output;
|
||||
private final Object readLock = new Object();
|
||||
@@ -41,15 +42,24 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
/**
|
||||
* Creates a message runner.
|
||||
*
|
||||
* @param name - The name of the runner. This is used in logging.
|
||||
* @param input - The input from our external source.
|
||||
* @param output - The output to the external source.
|
||||
*/
|
||||
public MessageRunner(InputStream input, OutputStream output)
|
||||
public MessageRunner(String name, InputStream input, OutputStream output)
|
||||
{
|
||||
this.name = name;
|
||||
this.logger = LoggerFactory.getLogger(name);
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger()
|
||||
{
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
@@ -91,7 +101,7 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
logger.error("Could not send message " + new String(packet.message, StandardCharsets.UTF_8), e);
|
||||
logger.error("Could not send message " + this.messagesSent + " " + new String(packet.message, StandardCharsets.UTF_8), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +155,10 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
logger.error(this.getClass().getName() + " Error", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
/*
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public interface MessageSerializer
|
||||
{
|
||||
Logger logger = LoggerFactory.getLogger(MessageSerializer.class);
|
||||
Logger getLogger();
|
||||
|
||||
/**
|
||||
* Converts an object into a form that can be sent.
|
||||
@@ -63,7 +62,7 @@ public interface MessageSerializer
|
||||
default void writeMessage(OutputStream out, int messageNum, byte[] message) throws IOException
|
||||
{
|
||||
ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
|
||||
logger.debug("Writing message {} {}", messageNum, message);
|
||||
getLogger().trace("Writing message {} {}", messageNum, message);
|
||||
/*
|
||||
* Writes the message number.
|
||||
*/
|
||||
@@ -110,7 +109,7 @@ public interface MessageSerializer
|
||||
readLength = in.read(messageNum);
|
||||
if (readLength == 0)
|
||||
{
|
||||
logger.debug("Input stream closed, no more messages.");
|
||||
getLogger().debug("Input stream closed, no more messages.");
|
||||
return null;
|
||||
}
|
||||
/*
|
||||
@@ -120,7 +119,7 @@ public interface MessageSerializer
|
||||
readLength = in.read(lengthBuffer.array());
|
||||
if (readLength == 0)
|
||||
{
|
||||
logger.error("Malformed message, could not get message length.");
|
||||
getLogger().error("Malformed message, could not get message length.");
|
||||
return null;
|
||||
}
|
||||
message = new byte[lengthBuffer.getInt()];
|
||||
@@ -130,9 +129,9 @@ public interface MessageSerializer
|
||||
readLength = in.read(message);
|
||||
if (readLength < message.length)
|
||||
{
|
||||
logger.warn("Message shorter than reported (expected {} bytes, got {} bytes)", message.length, readLength);
|
||||
getLogger().warn("Message shorter than reported (expected {} bytes, got {} bytes)", message.length, readLength);
|
||||
}
|
||||
logger.debug("Reading message");
|
||||
getLogger().trace("Reading message");
|
||||
return new byte[][] {messageNum, message};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* This future contains extra information regarding the success of a browser
|
||||
* command on the browser side.
|
||||
*
|
||||
* @param <V> The return type.
|
||||
*/
|
||||
public interface QueryFuture<V> extends Future<V>
|
||||
{
|
||||
/**
|
||||
* Waits if necessary for the computation to complete, and then checks to see whether execution was successful.
|
||||
*
|
||||
* @return A status code on whether the command was successful or not.
|
||||
* @throws CancellationException – if the computation was cancelled
|
||||
* @throws ExecutionException – if the computation threw an exception
|
||||
* @throws InterruptedException – if the current thread was interrupted while waiting
|
||||
*/
|
||||
CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Waits if necessary for the computation to complete, and then checks to see whether execution was successful.
|
||||
*
|
||||
* @param timeout - How long to hang the thread.
|
||||
* @param unit - What units was specified in the timeout parameter.
|
||||
* @return A status code on whether the command was successful or not.
|
||||
* @throws TimeoutException – if the computation timed out
|
||||
* @throws ExecutionException – if the computation threw an exception
|
||||
* @throws InterruptedException – if the current thread was interrupted while waiting
|
||||
*/
|
||||
CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
/**
|
||||
* This query asks for the total length of the song.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class QueryLength implements BrowserQuery<Float>
|
||||
{
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return "length";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Float> getReturnType()
|
||||
{
|
||||
return Float.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
/**
|
||||
* This query asks for confirmation as to whether the browser playback is paused
|
||||
* or not.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class QueryPaused implements BrowserQuery<Boolean>
|
||||
{
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return "isPaused";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Boolean> getReturnType()
|
||||
{
|
||||
return Boolean.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
/**
|
||||
* This query asks for whether a song is currently playing or not. Note that
|
||||
* this is not the same as whether it is paused or not, as a song could have
|
||||
* run into a playback error.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class QueryPlaying implements BrowserQuery<Boolean>
|
||||
{
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return "isPlaying";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Boolean> getReturnType()
|
||||
{
|
||||
return Boolean.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* This query asks for the currently loaded song.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class QuerySong implements BrowserQuery<URL>
|
||||
{
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return "getSong";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<URL> getReturnType()
|
||||
{
|
||||
return URL.class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package edu.regis.universeplayer.browserCommands;
|
||||
|
||||
/**
|
||||
* This query asks for the current playback time.
|
||||
*
|
||||
* @author William Hubbard
|
||||
* @version 0.1
|
||||
*/
|
||||
public class QueryTime implements BrowserQuery<Float>
|
||||
{
|
||||
@Override
|
||||
public String getCommandName()
|
||||
{
|
||||
return "currentTime";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Float> getReturnType()
|
||||
{
|
||||
return Float.class;
|
||||
}
|
||||
}
|
||||
@@ -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 -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
</Console>
|
||||
<File name="File" fileName="interface.log" append="false">
|
||||
<PatternLayout>
|
||||
<Pattern>%d{HH:mm:ss.SSS} - %45c:%-4L - %-5level - %msg%n</Pattern>
|
||||
<Pattern>%d{HH:mm:ss.SSS} - %c.%M(%F:%L) - %-5level - %msg%n</Pattern>
|
||||
</PatternLayout>
|
||||
</File>
|
||||
</Appenders>
|
||||
@@ -17,8 +17,5 @@
|
||||
<Root level="debug">
|
||||
<AppenderRef ref="File"/>
|
||||
</Root>
|
||||
<Logger name="edu.regis.universeplayer.browser.Browser" level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Logger>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
Reference in New Issue
Block a user