Gives the interface the ability to pull information from the YouTube page.
This will make filling in song data much easier.
This commit is contained in:
@@ -16,7 +16,7 @@ logger.pushUpdate = function (message)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.log("Hello from Universal Music addon!")
|
console.log("Hello from Universal Music addon!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This variable contains a mapping of message IDs to the message promises they correspond to, as
|
* This variable contains a mapping of message IDs to the message promises they correspond to, as
|
||||||
@@ -178,11 +178,6 @@ function loadTab(url, pinned)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, reject);
|
}, reject);
|
||||||
}).finally(() => {
|
|
||||||
if (!pinned)
|
|
||||||
{
|
|
||||||
browser.tabs.remove(chosen);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
class Album
|
class Album
|
||||||
{
|
{
|
||||||
|
type = "edu.regis.universeplayer.data.Album";
|
||||||
name;
|
name;
|
||||||
artists;
|
artists;
|
||||||
year;
|
year;
|
||||||
@@ -10,6 +11,7 @@ class Album
|
|||||||
|
|
||||||
class Song
|
class Song
|
||||||
{
|
{
|
||||||
|
type = "edu.regis.universeplayer.data.Song";
|
||||||
/**
|
/**
|
||||||
* The name of the song.
|
* The name of the song.
|
||||||
*/
|
*/
|
||||||
@@ -34,11 +36,12 @@ class Song
|
|||||||
/**
|
/**
|
||||||
* A reference to the album this song is part of.
|
* A reference to the album this song is part of.
|
||||||
*/
|
*/
|
||||||
album;
|
album = new Album();
|
||||||
}
|
}
|
||||||
|
|
||||||
class InternetSong extends Song
|
class InternetSong extends Song
|
||||||
{
|
{
|
||||||
|
type = "edu.regis.universeplayer.data.InternetSong";
|
||||||
/**
|
/**
|
||||||
* The location of the song.
|
* The location of the song.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
let logger = new Logger("foreground");
|
let logger = new Logger("foreground");
|
||||||
logger.debug("Loading foreground.js");
|
console.debug("Loading foreground.js");
|
||||||
let background;
|
let background;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,7 +124,7 @@ $(function () {
|
|||||||
background.onMessage.addListener(message => {
|
background.onMessage.addListener(message => {
|
||||||
let num = message.num;
|
let num = message.num;
|
||||||
post = data => {
|
post = data => {
|
||||||
logger.log("Sending to interface %o", data);
|
logger.trace("Sending to interface %o", data);
|
||||||
background.postMessage({
|
background.postMessage({
|
||||||
type: "response",
|
type: "response",
|
||||||
num: num,
|
num: num,
|
||||||
|
|||||||
@@ -57,19 +57,44 @@ class Logger {
|
|||||||
passLog(level, message)
|
passLog(level, message)
|
||||||
{
|
{
|
||||||
let messageData;
|
let messageData;
|
||||||
if (message.length > 1 && typeof message[0] == "string")
|
if (message.length > 1)
|
||||||
{
|
{
|
||||||
/*
|
if (typeof message[0] == "string")
|
||||||
* Format the initial string in a way that SLF4J can understand it.
|
{
|
||||||
*/
|
/*
|
||||||
message[0] = message[0].replaceAll(/%[a-z]/gi, "{}");
|
* Format the initial string in a way that SLF4J can understand it.
|
||||||
|
*/
|
||||||
|
message[0] = message[0].replaceAll(/%[a-z]/gi, "{}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < message.length; i++)
|
||||||
|
{
|
||||||
|
if (message[i] instanceof Error)
|
||||||
|
{
|
||||||
|
message[i] = {
|
||||||
|
"type": "edu.regis.universeplayer.browserCommands.BrowserError",
|
||||||
|
"name": message[i].name,
|
||||||
|
"message": message[i].message,
|
||||||
|
"fileName": message[i].fileName,
|
||||||
|
"lineNumber": message[i].lineNumber,
|
||||||
|
"columnNumber": message[i].columnNumber,
|
||||||
|
"stack": message[i].stack
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.queuedMessages.push(new MessageData(this.name, level, message));
|
this.queuedMessages.push(new MessageData(this.name, level, message));
|
||||||
message = this.queuedMessages.pop();
|
message = this.queuedMessages.pop();
|
||||||
|
try
|
||||||
|
{
|
||||||
while (message && this.pushUpdate(message))
|
while (message && this.pushUpdate(message))
|
||||||
{
|
{
|
||||||
message = this.queuedMessages.pop();
|
message = this.queuedMessages.pop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
console.error("Could not post log message", e)
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* If we popped a message but got here, then a message was not sent. Put it back.
|
* If we popped a message but got here, then a message was not sent. Put it back.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ logger.pushUpdate = function (message)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ylogger.debug("Loading youtube.js")
|
console.debug("Loading youtube.js")
|
||||||
var video;
|
var video;
|
||||||
|
|
||||||
function onload()
|
function onload()
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package edu.regis.universeplayer.addon;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.browserCommands.BrowserError;
|
||||||
|
|
||||||
|
public class BrowserErrorSerializer implements JsonSerializer<BrowserError>, JsonDeserializer<BrowserError>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(BrowserError src, Type typeOfSrc,
|
||||||
|
JsonSerializationContext context)
|
||||||
|
{
|
||||||
|
JsonObject ob = new JsonObject();
|
||||||
|
ob.addProperty("message", src.getMessage());
|
||||||
|
String stack =
|
||||||
|
Arrays.stream(src.getStackTrace())
|
||||||
|
.map(trace -> trace.getMethodName() + "@" + trace
|
||||||
|
.getFileName() + ":" + trace
|
||||||
|
.getLineNumber() + ":0").reduce("",
|
||||||
|
(s1, s2) -> s1.isEmpty() ? s2 : s1 + "\n" + s2);
|
||||||
|
ob.addProperty("stack", stack);
|
||||||
|
JsonArray suppressed = new JsonArray();
|
||||||
|
for (Throwable throwable : src.getSuppressed())
|
||||||
|
{
|
||||||
|
suppressed.add(context.serialize(throwable));
|
||||||
|
}
|
||||||
|
return ob;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BrowserError deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
|
||||||
|
{
|
||||||
|
JsonObject ob = json.getAsJsonObject();
|
||||||
|
return new BrowserError(ob.get("name").getAsString(), ob.get(
|
||||||
|
"message").getAsString(), ob.get("stack").getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,7 @@ package edu.regis.universeplayer.addon;
|
|||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
|
|
||||||
import edu.regis.universeplayer.browserCommands.CommandConfirmation;
|
import edu.regis.universeplayer.browserCommands.*;
|
||||||
import edu.regis.universeplayer.browserCommands.MessageRunner;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -37,6 +36,8 @@ public class BrowserLink extends MessageRunner
|
|||||||
GsonBuilder builder = new GsonBuilder();
|
GsonBuilder builder = new GsonBuilder();
|
||||||
builder.registerTypeAdapter(StackTraceElement.class, new StackTraceElementSerializer());
|
builder.registerTypeAdapter(StackTraceElement.class, new StackTraceElementSerializer());
|
||||||
builder.registerTypeAdapter(Throwable.class, new ThrowableSerializer());
|
builder.registerTypeAdapter(Throwable.class, new ThrowableSerializer());
|
||||||
|
builder.registerTypeAdapter(BrowserError.class, new BrowserErrorSerializer());
|
||||||
|
builder.registerTypeAdapter(CommandReturn.class, new CommandReturnSerializer());
|
||||||
gson = builder.create();
|
gson = builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +145,7 @@ public class BrowserLink extends MessageRunner
|
|||||||
*/
|
*/
|
||||||
private Object getMessage(JsonElement message) throws IOException
|
private Object getMessage(JsonElement message) throws IOException
|
||||||
{
|
{
|
||||||
|
logger.debug("Deserializing {}", message);
|
||||||
if (message.isJsonPrimitive())
|
if (message.isJsonPrimitive())
|
||||||
{
|
{
|
||||||
return getPrimitiveMessage((JsonPrimitive) message);
|
return getPrimitiveMessage((JsonPrimitive) message);
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package edu.regis.universeplayer.addon;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import edu.regis.universeplayer.browserCommands.CommandConfirmation;
|
||||||
|
import edu.regis.universeplayer.browserCommands.CommandReturn;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CommandReturnSerializer implements JsonSerializer<CommandReturn<?>>, JsonDeserializer<CommandReturn<?>>
|
||||||
|
{
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(CommandReturnSerializer.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandReturn<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
|
||||||
|
{
|
||||||
|
logger.debug("Deserializing return value {}", json);
|
||||||
|
CommandReturn<?> returnVal;
|
||||||
|
CommandConfirmation confirmation;
|
||||||
|
Object value = null;
|
||||||
|
JsonObject jsonOb = json.getAsJsonObject();
|
||||||
|
JsonElement jsonVal = jsonOb.get("returnValue");
|
||||||
|
confirmation = context.deserialize(jsonOb.getAsJsonObject("confirmation"), CommandConfirmation.class);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = deserializeObject(jsonVal, context);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not deserialize object {}.", json, e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
returnVal = new CommandReturn<>(value, confirmation);
|
||||||
|
}
|
||||||
|
return returnVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to determine what type of object is provided and deserializes
|
||||||
|
* it.
|
||||||
|
*
|
||||||
|
* @param jsonVal The element to deserialize. If it is an object, a "type"
|
||||||
|
* field will be used to obtain the class.
|
||||||
|
* @param context The deserialization context.
|
||||||
|
* @return The deserialized object.
|
||||||
|
* @throws ClassNotFoundException If the provided "type" field did not
|
||||||
|
* contain a known class.
|
||||||
|
*/
|
||||||
|
private Object deserializeObject(JsonElement jsonVal, JsonDeserializationContext context) throws ClassNotFoundException
|
||||||
|
{
|
||||||
|
Object value;
|
||||||
|
if (jsonVal.isJsonPrimitive())
|
||||||
|
{
|
||||||
|
JsonPrimitive primVal = jsonVal.getAsJsonPrimitive();
|
||||||
|
if (primVal.isString())
|
||||||
|
{
|
||||||
|
value = primVal.getAsString();
|
||||||
|
}
|
||||||
|
else if (primVal.isBoolean())
|
||||||
|
{
|
||||||
|
value = primVal.getAsBoolean();
|
||||||
|
}
|
||||||
|
else if (primVal.isNumber())
|
||||||
|
{
|
||||||
|
value = primVal.getAsNumber();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (jsonVal.isJsonArray())
|
||||||
|
{
|
||||||
|
JsonArray arrVal = jsonVal.getAsJsonArray();
|
||||||
|
if (arrVal.size() > 0)
|
||||||
|
{
|
||||||
|
value = new ArrayList<>();
|
||||||
|
for (JsonElement el : arrVal)
|
||||||
|
{
|
||||||
|
((ArrayList) value).add(deserializeObject(el, context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (jsonVal.isJsonObject())
|
||||||
|
{
|
||||||
|
JsonObject obVal = jsonVal.getAsJsonObject();
|
||||||
|
Class type;
|
||||||
|
if (obVal.has("type"))
|
||||||
|
{
|
||||||
|
type = Class.forName(obVal.get("type").getAsString());
|
||||||
|
value = context.deserialize(obVal, type);
|
||||||
|
logger.debug("Deserializing object of type {} {}", obVal.get("type").getAsString(), value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = context.deserialize(obVal, Object.class);
|
||||||
|
logger.debug("Deserializing object of type {} {}", value.getClass(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
if (!jsonVal.isJsonObject())
|
||||||
|
{
|
||||||
|
logger.debug("Deserializing non-object {}", jsonVal);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(CommandReturn<?> src, Type typeOfSrc, JsonSerializationContext context)
|
||||||
|
{
|
||||||
|
return serializeObject(src, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to determine what type of object is provided and deserializes
|
||||||
|
* it.
|
||||||
|
*
|
||||||
|
* @param value The element to deserialize. If it is an object, a "type"
|
||||||
|
* field will be used to obtain the class.
|
||||||
|
* @param context The serialization context.
|
||||||
|
* @return The serialized object.
|
||||||
|
*/
|
||||||
|
private JsonElement serializeObject(Object value, JsonSerializationContext context)
|
||||||
|
{
|
||||||
|
JsonElement jsonVal;
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
jsonVal = JsonNull.INSTANCE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (value.getClass().isArray())
|
||||||
|
{
|
||||||
|
value = Arrays.stream((Object[]) value).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
if (value instanceof String || value instanceof Number || value instanceof Boolean)
|
||||||
|
{
|
||||||
|
jsonVal = context.serialize(value);
|
||||||
|
}
|
||||||
|
else if (value instanceof Collection)
|
||||||
|
{
|
||||||
|
jsonVal = new JsonArray();
|
||||||
|
for (Object val : (Collection) value)
|
||||||
|
{
|
||||||
|
jsonVal.getAsJsonArray().add(serializeObject(val, context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
jsonVal = context.serialize(value, value.getClass());
|
||||||
|
jsonVal.getAsJsonObject().addProperty("type", value.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package edu.regis.universeplayer.addon;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.browserCommands.BrowserError;
|
||||||
|
|
||||||
|
public class GenericSerializer implements JsonSerializer<Object>,
|
||||||
|
JsonDeserializer<Object>
|
||||||
|
{
|
||||||
|
private static final Logger logger =
|
||||||
|
LoggerFactory.getLogger(GenericSerializer.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Object src, Type typeOfSrc,
|
||||||
|
JsonSerializationContext context)
|
||||||
|
{
|
||||||
|
JsonObject val = new JsonObject();
|
||||||
|
for (Field field : src.getClass().getFields())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Modifier.isTransient(field.getModifiers()))
|
||||||
|
{
|
||||||
|
val.add(field.getName(), context.serialize(field.get(src)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalAccessException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not store field " + field.getName() + " " +
|
||||||
|
"of class " + src.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val.addProperty("type", src.getClass().getName());
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
|
||||||
|
{
|
||||||
|
Class<?> clazz;
|
||||||
|
Field field;
|
||||||
|
Object ob = null;
|
||||||
|
if (json.isJsonObject() && json.getAsJsonObject().has("type"))
|
||||||
|
{
|
||||||
|
JsonObject jsonOb = json.getAsJsonObject();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clazz = Class.forName(jsonOb.remove("type").getAsString());
|
||||||
|
ob = clazz.getConstructor().newInstance();
|
||||||
|
for (Map.Entry<String, JsonElement> fields: jsonOb.entrySet())
|
||||||
|
{
|
||||||
|
field = clazz.getField(fields.getKey());
|
||||||
|
if (field.getType().isAssignableFrom(String.class))
|
||||||
|
{
|
||||||
|
field.set(ob, fields.getValue().getAsString());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(int.class))
|
||||||
|
{
|
||||||
|
field.setInt(ob, fields.getValue().getAsInt());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(double.class))
|
||||||
|
{
|
||||||
|
field.setDouble(ob, fields.getValue().getAsDouble());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(byte.class))
|
||||||
|
{
|
||||||
|
field.setByte(ob, fields.getValue().getAsByte());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(String.class))
|
||||||
|
{
|
||||||
|
field.set(ob, fields.getValue().getAsString());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(boolean.class))
|
||||||
|
{
|
||||||
|
field.setBoolean(ob, fields.getValue().getAsBoolean());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(float.class))
|
||||||
|
{
|
||||||
|
field.setFloat(ob, fields.getValue().getAsFloat());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(long.class))
|
||||||
|
{
|
||||||
|
field.setLong(ob, fields.getValue().getAsLong());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(short.class))
|
||||||
|
{
|
||||||
|
field.setShort(ob, fields.getValue().getAsShort());
|
||||||
|
}
|
||||||
|
else if (field.getType().isAssignableFrom(URL.class))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
field.set(ob, new URL(fields.getValue().getAsString()));
|
||||||
|
}
|
||||||
|
catch (MalformedURLException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not create url {}",
|
||||||
|
fields.getValue().getAsString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (fields.getValue().isJsonObject())
|
||||||
|
{
|
||||||
|
// clazz = fields.getValue().getAsJsonObject().
|
||||||
|
// field.set(ob, context.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not deserialize {} ", json, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ob;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,6 +140,10 @@ public class Browser extends MessageRunner
|
|||||||
String methodName;
|
String methodName;
|
||||||
Class<?>[] types = null;
|
Class<?>[] types = null;
|
||||||
Object[] params = null;
|
Object[] params = null;
|
||||||
|
if (log.message.length == 1 && !(log.message[0] instanceof String))
|
||||||
|
{
|
||||||
|
log.message = new Object[] {"{}", log.message[0]};
|
||||||
|
}
|
||||||
if (log.message.length == 1)
|
if (log.message.length == 1)
|
||||||
{
|
{
|
||||||
types = new Class<?>[]{String.class};
|
types = new Class<?>[]{String.class};
|
||||||
@@ -185,6 +189,11 @@ public class Browser extends MessageRunner
|
|||||||
logger.error("Could not process logger message {}", log,
|
logger.error("Could not process logger message {}", log,
|
||||||
e);
|
e);
|
||||||
}
|
}
|
||||||
|
catch (IllegalArgumentException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not call logger {} with arguments {}", method,
|
||||||
|
params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (object instanceof LogEvent)
|
else if (object instanceof LogEvent)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package edu.regis.universeplayer;
|
|
||||||
|
|
||||||
public class AutoGson
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -4,11 +4,13 @@
|
|||||||
|
|
||||||
package edu.regis.universeplayer.data;
|
package edu.regis.universeplayer.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
|
||||||
public class Album implements Comparable<Album>
|
public class Album implements Comparable<Album>, Serializable
|
||||||
{
|
{
|
||||||
public int id;
|
public int id;
|
||||||
public String name;
|
public String name;
|
||||||
@@ -19,6 +21,29 @@ public class Album implements Comparable<Album>
|
|||||||
public int totalTracks;
|
public int totalTracks;
|
||||||
public int totalDiscs;
|
public int totalDiscs;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof Album album))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return year == album.year && totalTracks == album.totalTracks && totalDiscs == album.totalDiscs && name.equals(album.name) && Arrays.equals(artists, album.artists) && Arrays.equals(genres, album.genres);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = Objects.hash(name, year, totalTracks, totalDiscs);
|
||||||
|
result = 31 * result + Arrays.hashCode(artists);
|
||||||
|
result = 31 * result + Arrays.hashCode(genres);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Album o)
|
public int compareTo(Album o)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An internet song is a specific type of song that is accessed from a webpage.
|
||||||
|
*/
|
||||||
public class InternetSong extends Song
|
public class InternetSong extends Song
|
||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory
|
private static final Logger logger = LoggerFactory
|
||||||
@@ -21,6 +25,30 @@ public class InternetSong extends Song
|
|||||||
*/
|
*/
|
||||||
public URL location;
|
public URL location;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof InternetSong that))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!super.equals(o))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Objects.equals(location, that.location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return Objects.hash(super.hashCode(), location);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Song o)
|
public int compareTo(Song o)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package edu.regis.universeplayer.data;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This song represents a song found on the local file system.
|
* This song represents a song found on the local file system.
|
||||||
@@ -30,6 +31,30 @@ public class LocalSong extends Song
|
|||||||
*/
|
*/
|
||||||
public long lastMod;
|
public long lastMod;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof LocalSong localSong))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!super.equals(o))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return file.equals(localSong.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return Objects.hash(super.hashCode(), file);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Song o)
|
public int compareTo(Song o)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package edu.regis.universeplayer.data;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains data for a song.
|
* Contains data for a song.
|
||||||
@@ -42,6 +43,28 @@ public class Song implements Comparable<Song>, Serializable
|
|||||||
*/
|
*/
|
||||||
public Album album;
|
public Album album;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if (this == o)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof Song song))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return trackNum == song.trackNum && disc == song.disc && title.equals(song.title) && Arrays.equals(artists, song.artists) && album.equals(song.album);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
int result = Objects.hash(title, trackNum, disc, album);
|
||||||
|
result = 31 * result + Arrays.hashCode(artists);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Song o)
|
public int compareTo(Song o)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
package edu.regis.universeplayer.player;
|
package edu.regis.universeplayer.player;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.AbstractTask;
|
||||||
|
import edu.regis.universeplayer.NumberPing;
|
||||||
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;
|
||||||
@@ -32,6 +34,20 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
private static final Logger logger = LoggerFactory
|
private static final Logger logger = LoggerFactory
|
||||||
.getLogger(BrowserPlayer.class);
|
.getLogger(BrowserPlayer.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The player instance. This should only be used for debugging..
|
||||||
|
*/
|
||||||
|
private static BrowserPlayer INSTANCE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the player instance. This should only be used for debugging.
|
||||||
|
*/
|
||||||
|
public static BrowserPlayer getInstance()
|
||||||
|
{
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ForkJoinPool service = new ForkJoinPool();
|
||||||
private final LinkedList<PlaybackListener> listeners = new LinkedList<>();
|
private final LinkedList<PlaybackListener> listeners = new LinkedList<>();
|
||||||
private boolean error = false;
|
private boolean error = false;
|
||||||
|
|
||||||
@@ -65,6 +81,7 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
|
|
||||||
public BrowserPlayer()
|
public BrowserPlayer()
|
||||||
{
|
{
|
||||||
|
INSTANCE = this;
|
||||||
Thread browserThread = new Thread(() -> {
|
Thread browserThread = new Thread(() -> {
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -87,37 +104,119 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
return this.currentSong;
|
return this.currentSong;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public QueryFuture<Void> loadSong(InternetSong song)
|
* Obtains data on a specified song.
|
||||||
|
*
|
||||||
|
* @param url - The url to get the data from.
|
||||||
|
* @return A confirmation of command success.
|
||||||
|
*/
|
||||||
|
public ForkJoinTask<InternetSong> getSongData(URL url)
|
||||||
{
|
{
|
||||||
try
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
return new ForwardedFuture(getBrowser()
|
@Override
|
||||||
.sendObject(new CommandLoadSong(song.location)));
|
protected boolean exec()
|
||||||
}
|
{
|
||||||
catch (IOException e)
|
try
|
||||||
|
{
|
||||||
|
Future<?> command =
|
||||||
|
getBrowser()
|
||||||
|
.sendObject(new QuerySongData(url));
|
||||||
|
CommandReturn<InternetSong> returnOb =
|
||||||
|
(CommandReturn<InternetSong>) command
|
||||||
|
.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.complete(returnOb.getReturnValue());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ForkJoinTask<Void> loadSong(InternetSong song)
|
||||||
|
{
|
||||||
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
logger.error("Could not send message", e);
|
@Override
|
||||||
return null;
|
protected boolean exec()
|
||||||
}
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Future<?> command =
|
||||||
|
getBrowser()
|
||||||
|
.sendObject(new CommandLoadSong(song.location));
|
||||||
|
CommandReturn<Boolean> returnOb = (CommandReturn<Boolean>) command
|
||||||
|
.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells the browser process to shut down.
|
* Tells the browser process to shut down.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> close()
|
public ForkJoinTask<Void> close()
|
||||||
{
|
{
|
||||||
try
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
return (QueryFuture<Void>) new ForwardedFuture(getBrowser()
|
@Override
|
||||||
.sendObject(new CommandQuit()));
|
protected boolean exec()
|
||||||
}
|
{
|
||||||
catch (IOException e)
|
try
|
||||||
{
|
{
|
||||||
logger.error("Could not send message", e);
|
Future<?> command =
|
||||||
return null;
|
getBrowser()
|
||||||
}
|
.sendObject(new CommandQuit());
|
||||||
|
CommandReturn<Boolean> returnOb = (CommandReturn<Boolean>) command
|
||||||
|
.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,92 +254,201 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> play()
|
public ForkJoinTask<Void> play()
|
||||||
{
|
{
|
||||||
try
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
return new ForwardedFuture(getBrowser()
|
@Override
|
||||||
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY)));
|
protected boolean exec()
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
logger.error("Could not send message", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public QueryFuture<Void> pause()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new ForwardedFuture(getBrowser()
|
|
||||||
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE)));
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
logger.error("Could not send message", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public QueryFuture<Void> togglePlayback()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
QueryFuture<PlaybackStatus> future = this.getStatus();
|
|
||||||
switch (future.get())
|
|
||||||
{
|
{
|
||||||
case PLAYING -> {
|
try
|
||||||
return new ForwardedFuture(getBrowser()
|
{
|
||||||
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE)));
|
Future<?> command =
|
||||||
|
getBrowser()
|
||||||
|
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY));
|
||||||
|
CommandReturn<Boolean> returnOb = (CommandReturn<Boolean>) command
|
||||||
|
.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case PAUSED, STOPPED, FINISHED -> {
|
});
|
||||||
return new ForwardedFuture(getBrowser()
|
}
|
||||||
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY)));
|
|
||||||
}
|
@Override
|
||||||
}
|
public ForkJoinTask<Void> pause()
|
||||||
return null;
|
{
|
||||||
}
|
return this.service.submit(new AbstractTask<>()
|
||||||
catch (IOException | InterruptedException | ExecutionException e)
|
|
||||||
{
|
{
|
||||||
logger.error("Could not send message", e);
|
@Override
|
||||||
return null;
|
protected boolean exec()
|
||||||
}
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Future<?> command =
|
||||||
|
getBrowser()
|
||||||
|
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE));
|
||||||
|
CommandReturn<Boolean> returnOb = (CommandReturn<Boolean>) command
|
||||||
|
.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ForkJoinTask<Void> togglePlayback()
|
||||||
|
{
|
||||||
|
return this.service.submit(new AbstractTask<Void>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected boolean exec()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Future<?> command = getBrowser()
|
||||||
|
.sendObject(new QueryStatus());
|
||||||
|
CommandReturn<String> returnOb =
|
||||||
|
(CommandReturn<String>) command.get();
|
||||||
|
CommandReturn<?> confirmation;
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (PlaybackStatus
|
||||||
|
.valueOf(returnOb.getReturnValue()))
|
||||||
|
{
|
||||||
|
case PLAYING -> command =
|
||||||
|
getBrowser()
|
||||||
|
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE));
|
||||||
|
case PAUSED, STOPPED, FINISHED -> command = getBrowser()
|
||||||
|
.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY));
|
||||||
|
}
|
||||||
|
confirmation =
|
||||||
|
(CommandReturn<?>) command.get();
|
||||||
|
if (!confirmation.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb
|
||||||
|
.getConfirmation().getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops playback of the current song.
|
* Stops playback of the current song.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> stopSong()
|
public ForkJoinTask<Void> stopSong()
|
||||||
{
|
{
|
||||||
try
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
return new ForwardedFuture(getBrowser()
|
@Override
|
||||||
.sendObject(new CommandLoadSong((URL) null)));
|
protected boolean exec()
|
||||||
}
|
{
|
||||||
catch (IOException e)
|
try
|
||||||
{
|
{
|
||||||
logger.error("Could not send message", e);
|
Future<?> command =
|
||||||
return null;
|
getBrowser()
|
||||||
}
|
.sendObject(new CommandLoadSong((URL) null));
|
||||||
|
CommandReturn<Boolean> returnOb = (CommandReturn<Boolean>) command
|
||||||
|
.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> seek(float time)
|
public ForkJoinTask<Void> seek(float time)
|
||||||
{
|
{
|
||||||
try
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
return new ForwardedFuture(getBrowser()
|
@Override
|
||||||
.sendObject(new CommandSeek(time)));
|
protected boolean exec()
|
||||||
}
|
{
|
||||||
catch (IOException e)
|
try
|
||||||
{
|
{
|
||||||
logger.error("Could not send message", e);
|
Future<?> command =
|
||||||
return null;
|
getBrowser()
|
||||||
}
|
.sendObject(new CommandSeek(time));
|
||||||
|
CommandReturn<Boolean> returnOb = (CommandReturn<Boolean>) command
|
||||||
|
.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -249,84 +457,49 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
* @return A future for the request.
|
* @return A future for the request.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<PlaybackStatus> getStatus()
|
public ForkJoinTask<PlaybackStatus> getStatus()
|
||||||
{
|
{
|
||||||
try
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
Future future = getBrowser().sendObject(new QueryStatus());
|
@Override
|
||||||
return new QueryFuture<>()
|
protected boolean exec()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
private CommandReturn<String> getVal() throws ExecutionException, InterruptedException
|
|
||||||
{
|
{
|
||||||
return ((CommandReturn<String>) future.get());
|
Future<?> command = getBrowser()
|
||||||
|
.sendObject(new QueryStatus());
|
||||||
|
CommandReturn<String> returnOb =
|
||||||
|
(CommandReturn<String>) command.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.complete(PlaybackStatus
|
||||||
|
.valueOf(returnOb.getReturnValue()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
private CommandReturn<String> getVal(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException
|
|
||||||
{
|
{
|
||||||
return ((CommandReturn<String>) future.get(timeout, unit));
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
});
|
||||||
public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException
|
|
||||||
{
|
|
||||||
return this.getVal().getConfirmation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException
|
|
||||||
{
|
|
||||||
return this.getVal(timeout, unit).getConfirmation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean cancel(boolean mayInterruptIfRunning)
|
|
||||||
{
|
|
||||||
return future.cancel(mayInterruptIfRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCancelled()
|
|
||||||
{
|
|
||||||
return future.isCancelled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDone()
|
|
||||||
{
|
|
||||||
return future.isDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PlaybackStatus get() throws InterruptedException, ExecutionException
|
|
||||||
{
|
|
||||||
String value = getVal().getReturnValue();
|
|
||||||
return PlaybackStatus.valueOf(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PlaybackStatus get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
|
||||||
{
|
|
||||||
String value = getVal(timeout, unit).getReturnValue();
|
|
||||||
return PlaybackStatus.valueOf(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
logger.error("Could not send message", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Float> getCurrentTime()
|
public ForkJoinTask<Float> getCurrentTime()
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Float> getLength()
|
public ForkJoinTask<Float> getLength()
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -338,18 +511,90 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
* script or the background script.
|
* script or the background script.
|
||||||
* @return The return value containing error details.
|
* @return The return value containing error details.
|
||||||
*/
|
*/
|
||||||
public QueryFuture<Void> throwError(boolean forward)
|
public ForkJoinTask<Void> throwError(boolean forward)
|
||||||
{
|
{
|
||||||
try
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
return new ForwardedFuture(getBrowser()
|
@Override
|
||||||
.sendObject(new CommandError(forward)));
|
protected boolean exec()
|
||||||
}
|
{
|
||||||
catch (IOException e)
|
try
|
||||||
|
{
|
||||||
|
Future<?> command =
|
||||||
|
getBrowser()
|
||||||
|
.sendObject(new CommandError(forward));
|
||||||
|
CommandReturn<Boolean> returnOb = (CommandReturn<Boolean>) command
|
||||||
|
.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pings the browser background.
|
||||||
|
*
|
||||||
|
* @param i - The number to send.
|
||||||
|
* @return A future hopefully returning the same value.
|
||||||
|
*/
|
||||||
|
public ForkJoinTask<Double> ping(double i)
|
||||||
|
{
|
||||||
|
return this.ping(null, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pings the browser foreground.
|
||||||
|
*
|
||||||
|
* @param url - The url to open.
|
||||||
|
* @param i - The number to send.
|
||||||
|
* @return A future hopefully returning the same value.
|
||||||
|
*/
|
||||||
|
public ForkJoinTask<Double> ping(URL url, double i)
|
||||||
|
{
|
||||||
|
return this.service.submit(new AbstractTask<>()
|
||||||
{
|
{
|
||||||
logger.error("Could not send message", e);
|
@Override
|
||||||
return null;
|
protected boolean exec()
|
||||||
}
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Future<?> command = getBrowser()
|
||||||
|
.sendObject(new NumberPing(url, i));
|
||||||
|
CommandReturn<Number> returnOb =
|
||||||
|
(CommandReturn<Number>) command.get();
|
||||||
|
if (!returnOb.getConfirmation().wasSuccessful())
|
||||||
|
{
|
||||||
|
this.completeExceptionally(returnOb.getConfirmation()
|
||||||
|
.getError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.complete(returnOb.getReturnValue().doubleValue());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException | InterruptedException | ExecutionException e)
|
||||||
|
{
|
||||||
|
this.completeExceptionally(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -363,66 +608,4 @@ public class BrowserPlayer implements Player<InternetSong>, UpdateListener
|
|||||||
this.listeners.forEach(l -> l.onPlaybackChanged(status));
|
this.listeners.forEach(l -> l.onPlaybackChanged(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ForwardedFuture<T> implements QueryFuture<T>
|
|
||||||
{
|
|
||||||
private final Future<T> future;
|
|
||||||
|
|
||||||
ForwardedFuture(Future<T> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
87
interface/src/test/java/BrowserTest.java
Normal file
87
interface/src/test/java/BrowserTest.java
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.PlayerEnvironment;
|
||||||
|
import edu.regis.universeplayer.browser.Browser;
|
||||||
|
import edu.regis.universeplayer.browserCommands.QuerySongData;
|
||||||
|
import edu.regis.universeplayer.data.Album;
|
||||||
|
import edu.regis.universeplayer.data.InternetSong;
|
||||||
|
import edu.regis.universeplayer.player.BrowserPlayer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class BrowserTest
|
||||||
|
{
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BrowserTest.class);
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupBrowser()
|
||||||
|
{
|
||||||
|
HashMap<String, Object> props = new HashMap<>();
|
||||||
|
props.put("headless", true);
|
||||||
|
PlayerEnvironment.init(props, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPing()
|
||||||
|
{
|
||||||
|
logger.info("Pinging background");
|
||||||
|
logger.info("Background test 1");
|
||||||
|
assertEquals(20.0,
|
||||||
|
BrowserPlayer.getInstance().ping(20).join(), 0.01);
|
||||||
|
logger.info("Background test 2");
|
||||||
|
assertEquals(43.1,
|
||||||
|
BrowserPlayer.getInstance().ping(43.1).join(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPingForeground() throws MalformedURLException
|
||||||
|
{
|
||||||
|
logger.info("Pinging foreground");
|
||||||
|
logger.info("Forground test 1");
|
||||||
|
assertEquals(20.0,
|
||||||
|
BrowserPlayer.getInstance()
|
||||||
|
.ping(new URL("https://www.youtube.com/watch?v=FtutLA63Cp8"), 20)
|
||||||
|
.join(), 0.01);
|
||||||
|
logger.info("Forground test 2");
|
||||||
|
assertEquals(39.5,
|
||||||
|
BrowserPlayer.getInstance()
|
||||||
|
.ping(new URL("https://www.youtube" +
|
||||||
|
".com/watch?v=FtutLA63Cp8"), 39.5)
|
||||||
|
.join(), 0.01);
|
||||||
|
logger.info("Foreground test 3");
|
||||||
|
assertEquals(5.1,
|
||||||
|
BrowserPlayer.getInstance()
|
||||||
|
.ping(new URL("https://www.youtube.com/watch?v=grMqiZKmUeE"), 5.1)
|
||||||
|
.join(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDataQuery() throws IOException
|
||||||
|
{
|
||||||
|
logger.info("Retrieving song data");
|
||||||
|
InternetSong song = new InternetSong();
|
||||||
|
URL url = new URL("https://www.youtube.com/watch?v=cvX4B7GjU6s");
|
||||||
|
song.location = url;
|
||||||
|
song.title = "First Wave";
|
||||||
|
song.artists = new String[]{"Trocadero"};
|
||||||
|
song.duration = 224541;
|
||||||
|
song.album = new Album();
|
||||||
|
song.album.name = "Ghosts That Linger";
|
||||||
|
song.album.year = 2009;
|
||||||
|
song.album.artists = new String[]{"Rooster Teeth Records / Trocadero"};
|
||||||
|
|
||||||
|
assertEquals(song,
|
||||||
|
BrowserPlayer.getInstance()
|
||||||
|
.getSongData(url).join());
|
||||||
|
}
|
||||||
|
}
|
||||||
16
interface/src/test/resources/log4j2.xml
Normal file
16
interface/src/test/resources/log4j2.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
-->
|
||||||
|
<Configuration packages="edu.regis.universeplayer" status="WARN">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%c:%L %-5level - %msg%n"/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root level="debug">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
Reference in New Issue
Block a user