Adds the ability to play youtube videos.
It still needs a bit of work, but playing and pausing are operational.
This commit is contained in:
@@ -1,10 +1,112 @@
|
|||||||
console.log("Hello from Universal Music addon!")
|
console.log("Hello from Universal Music addon!")
|
||||||
|
const tabMessages = new Map();
|
||||||
|
var numTabMessages = 0;
|
||||||
/*
|
/*
|
||||||
On startup, connect to the "ping_pong" app.
|
On startup, connect to the "ping_pong" app.
|
||||||
*/
|
*/
|
||||||
var port = browser.runtime.connectNative("universalmusic");
|
var port = browser.runtime.connectNative("universalmusic");
|
||||||
|
|
||||||
|
var tabPort = null;
|
||||||
|
|
||||||
|
function setupTab(port)
|
||||||
|
{
|
||||||
|
console.info("Tab " + port.sender.tab.url + " loaded.");
|
||||||
|
tabPort = port;
|
||||||
|
tabPort.onDisconnect.addListener(e => {
|
||||||
|
tabPort = null;
|
||||||
|
if (e.error)
|
||||||
|
{
|
||||||
|
console.error("Tab error: ", e.error)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.info("Tab " + e.sender.tab.url + " disconnected.");
|
||||||
|
}
|
||||||
|
browser.tabs.remove(e.sender.tab.id);
|
||||||
|
});
|
||||||
|
tabPort.onMessage.addListener(message => {
|
||||||
|
if (typeof message == "object" && message.type == "response")
|
||||||
|
{
|
||||||
|
tabMessages.set(message.num, message.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryTab(message)
|
||||||
|
{
|
||||||
|
let num = numTabMessages++;
|
||||||
|
let promise = new Promise((resolve, reject) => {
|
||||||
|
tabPort.postMessage({
|
||||||
|
num: num,
|
||||||
|
data: message
|
||||||
|
});
|
||||||
|
(function awaitResponse() {
|
||||||
|
if (tabMessages.has(num))
|
||||||
|
{
|
||||||
|
response = tabMessages.get(num);
|
||||||
|
tabMessages.delete(num);
|
||||||
|
return resolve(response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setTimeout(awaitResponse, 30);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
tabMessages.set(numTabMessages, promise);
|
||||||
|
numTabMessages++;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
var listeners = [function (message, returnValue) {
|
var listeners = [function (message, returnValue) {
|
||||||
|
if (typeof message == "object" && "type" in message)
|
||||||
|
{
|
||||||
|
let type = message.type;
|
||||||
|
lastPeriod = type.lastIndexOf(".");
|
||||||
|
if (lastPeriod != -1)
|
||||||
|
{
|
||||||
|
type = type.substring(lastPeriod + 1);
|
||||||
|
}
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "CommandSong":
|
||||||
|
returnValue = () => browser.tabs.create({url: message.song}).then(setupTab);
|
||||||
|
if (tabPort)
|
||||||
|
{
|
||||||
|
console.log("Replacing tab");
|
||||||
|
/*
|
||||||
|
* Closes the existing tab first
|
||||||
|
*/
|
||||||
|
returnValue = browser.tabs.remove(e.sender.tab.id).then(returnValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.log("Opening tab");
|
||||||
|
returnValue = returnValue();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "QueryStatus":
|
||||||
|
if (!tabPort)
|
||||||
|
{
|
||||||
|
returnValue = 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "QueryTime":
|
||||||
|
case "QueryLength":
|
||||||
|
case "CommandSetPlayback":
|
||||||
|
case "CommandSeek":
|
||||||
|
returnValue = queryTab(message);
|
||||||
|
break;
|
||||||
|
case "CommandQuit":
|
||||||
|
returnValue = quit();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (message == "quit")
|
||||||
|
{
|
||||||
|
returnValue = quit();
|
||||||
|
}
|
||||||
if (returnValue == null)
|
if (returnValue == null)
|
||||||
{
|
{
|
||||||
returnValue = "pong";
|
returnValue = "pong";
|
||||||
@@ -12,6 +114,17 @@ var listeners = [function (message, returnValue) {
|
|||||||
return returnValue;
|
return returnValue;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
function quit()
|
||||||
|
{
|
||||||
|
console.log("Quitting browser");
|
||||||
|
return browser.tabs.query({}).then(tabs => {
|
||||||
|
for (let tab of tabs)
|
||||||
|
{
|
||||||
|
browser.tabs.remove(tab.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@@ -20,11 +133,25 @@ function handleMessage(message)
|
|||||||
{
|
{
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
var returnValue = null;
|
var returnValue = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
for (index in listeners)
|
for (index in listeners)
|
||||||
{
|
{
|
||||||
returnValue = listeners[index](message, returnValue);
|
returnValue = listeners[index](message, returnValue);
|
||||||
}
|
}
|
||||||
|
if (returnValue instanceof Promise)
|
||||||
|
{
|
||||||
|
returnValue.then(resolve).catch(reject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
resolve(returnValue);
|
resolve(returnValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,14 +159,26 @@ function handleMessage(message)
|
|||||||
* Listen for messages from the app.
|
* Listen for messages from the app.
|
||||||
*/
|
*/
|
||||||
port.onMessage.addListener((message) => {
|
port.onMessage.addListener((message) => {
|
||||||
console.log("Received: ", message);
|
console.log("Received from interface: ", message);
|
||||||
|
|
||||||
handleMessage(message.message).then((response) => {
|
handleMessage(message.message).then((response) => {
|
||||||
returnValue = {
|
returnValue = {
|
||||||
"messageNum": message.messageNum,
|
"messageNum": message.messageNum,
|
||||||
"message": response
|
"message": {
|
||||||
|
"type": "edu.regis.universeplayer.browserCommands.CommandReturn",
|
||||||
|
"returnValue": response,
|
||||||
|
"confirmation": {
|
||||||
|
type: "edu.regis.universeplayer.browserCommands.CommandConfirmation",
|
||||||
|
message: "Done",
|
||||||
|
errorCode: null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.log("Sending ", returnValue)
|
console.log("Sending ", returnValue)
|
||||||
port.postMessage(returnValue);
|
port.postMessage(returnValue);
|
||||||
|
}).catch(error => {
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
browser.runtime.onConnect.addListener(setupTab);
|
||||||
|
|||||||
82
add-on/foreground.js
Normal file
82
add-on/foreground.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
console.debug("Loading foreground.js")
|
||||||
|
let background;
|
||||||
|
|
||||||
|
function handleMessage(message)
|
||||||
|
{
|
||||||
|
if (typeof message == "object" && "type" in message)
|
||||||
|
{
|
||||||
|
let type = message.type;
|
||||||
|
lastPeriod = type.lastIndexOf(".");
|
||||||
|
if (lastPeriod != -1)
|
||||||
|
{
|
||||||
|
type = type.substring(lastPeriod + 1);
|
||||||
|
}
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "QueryStatus":
|
||||||
|
return getState();
|
||||||
|
case "QueryTime":
|
||||||
|
return getTime();
|
||||||
|
case "QueryLength":
|
||||||
|
return getLength();
|
||||||
|
case "CommandSetPlayback":
|
||||||
|
switch (message.status)
|
||||||
|
{
|
||||||
|
case "PLAY":
|
||||||
|
return play();
|
||||||
|
case "PAUSE":
|
||||||
|
return pause();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case "CommandSeek":
|
||||||
|
return seek(message.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendUpdate(response)
|
||||||
|
{
|
||||||
|
post = data => background.postMessage({
|
||||||
|
type: "update",
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
if (response instanceof Promise)
|
||||||
|
{
|
||||||
|
response.then(post);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
post(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background = browser.runtime.connect({name:"universalMusic"});
|
||||||
|
background.onMessage.addListener(message => {
|
||||||
|
let num = message.num;
|
||||||
|
response = handleMessage(message.data);
|
||||||
|
post = data => background.postMessage({
|
||||||
|
type: "response",
|
||||||
|
num: num,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
if (response instanceof Promise)
|
||||||
|
{
|
||||||
|
response.then(post);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
post(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (document.readyState === "complete")
|
||||||
|
{
|
||||||
|
sendUpdate("loaded");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sendUpdate("loaded");
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
sendUpdate("loaded");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -22,6 +22,13 @@
|
|||||||
"scripts": ["background.js"]
|
"scripts": ["background.js"]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["*://*.youtube.com/watch?v=*"],
|
||||||
|
"js": ["foreground.js", "youtube.js"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_icon": "icon.svg"
|
"default_icon": "icon.svg"
|
||||||
},
|
},
|
||||||
|
|||||||
76
add-on/youtube.js
Normal file
76
add-on/youtube.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
console.debug("Loading youtube.js")
|
||||||
|
var video;
|
||||||
|
|
||||||
|
function onload()
|
||||||
|
{
|
||||||
|
video = document.getElementsByClassName('video-stream html5-main-video')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getState()
|
||||||
|
{
|
||||||
|
if (!video)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
else if (video.ended)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
else if (video.paused)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTime()
|
||||||
|
{
|
||||||
|
return video.currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLength()
|
||||||
|
{
|
||||||
|
return video.duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
function play()
|
||||||
|
{
|
||||||
|
if (video != null)
|
||||||
|
{
|
||||||
|
video.play();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pause()
|
||||||
|
{
|
||||||
|
if (video != null)
|
||||||
|
{
|
||||||
|
video.pause();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function seek(time)
|
||||||
|
{
|
||||||
|
if (video != null)
|
||||||
|
{
|
||||||
|
video.currentTime = time;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "complete")
|
||||||
|
{
|
||||||
|
onload();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.addEventListener("load", onload);
|
||||||
|
}
|
||||||
@@ -20,6 +20,9 @@ plugins {
|
|||||||
// In this section you declare where to find the dependencies of your project
|
// In this section you declare where to find the dependencies of your project
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
flatDir {
|
||||||
|
dirs new File(rootDir, 'libs')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In this section you declare the dependencies for your production and test code
|
// In this section you declare the dependencies for your production and test code
|
||||||
@@ -31,6 +34,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.7'
|
implementation 'com.google.code.gson:gson:2.8.7'
|
||||||
|
|
||||||
|
implementation 'net.harawata:appdirs:1.2.1'
|
||||||
|
|
||||||
implementation project(":browserCommands")
|
implementation project(":browserCommands")
|
||||||
|
|
||||||
// Declare the dependency for your favourite test framework you want to use in your tests.
|
// Declare the dependency for your favourite test framework you want to use in your tests.
|
||||||
|
|||||||
@@ -27,8 +27,16 @@ import java.util.HashMap;
|
|||||||
*/
|
*/
|
||||||
public class BrowserLink extends MessageRunner
|
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();
|
public static final Gson gson;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
builder.registerTypeAdapter(StackTraceElement.class, new StackTraceElementSerializer());
|
||||||
|
builder.registerTypeAdapter(Throwable.class, new ThrowableSerializer());
|
||||||
|
gson = builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a message runner.
|
* Creates a message runner.
|
||||||
@@ -41,8 +49,12 @@ public class BrowserLink extends MessageRunner
|
|||||||
@Override
|
@Override
|
||||||
public byte[] serializeObject(Object message)
|
public byte[] serializeObject(Object message)
|
||||||
{
|
{
|
||||||
String val = gson.toJson(message);
|
JsonElement val = gson.toJsonTree(message);
|
||||||
return val.getBytes(StandardCharsets.UTF_8);
|
if (val.isJsonObject())
|
||||||
|
{
|
||||||
|
((JsonObject) val).addProperty("type", message.getClass().getName());
|
||||||
|
}
|
||||||
|
return val.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -197,6 +209,7 @@ public class BrowserLink extends MessageRunner
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
clazz = Class.forName(message.getAsJsonPrimitive("type").getAsString());
|
clazz = Class.forName(message.getAsJsonPrimitive("type").getAsString());
|
||||||
|
message.remove("type");
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException | ClassCastException e)
|
catch (ClassNotFoundException | ClassCastException e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@@ -22,23 +21,33 @@ public class Main
|
|||||||
|
|
||||||
public static void main(String[] args) throws IOException, InterruptedException
|
public static void main(String[] args) throws IOException, InterruptedException
|
||||||
{
|
{
|
||||||
ServerSocket socketSource = null;
|
|
||||||
Socket socket = null;
|
|
||||||
MessageHandler interfaceLink;
|
MessageHandler interfaceLink;
|
||||||
BrowserLink browserLink;
|
BrowserLink browserLink;
|
||||||
Thread interfaceThread, browserThread;
|
Thread interfaceThread, browserThread;
|
||||||
try
|
Socket socket;
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.debug("Connecting to browser");
|
logger.debug("Connecting to browser");
|
||||||
browserLink = new BrowserLink("BrowserLink");
|
browserLink = new BrowserLink("BrowserLink");
|
||||||
logger.debug("Setting up server");
|
try
|
||||||
socketSource = new ServerSocket(BrowserConstants.PORT);
|
{
|
||||||
logger.debug("Server started");
|
logger.debug("Setting up connection");
|
||||||
socket = socketSource.accept();
|
socket = new Socket(BrowserConstants.IP,BrowserConstants.PORT);
|
||||||
|
logger.debug("Connection established");
|
||||||
|
|
||||||
|
interfaceLink = new MessageHandler("InterfaceHandler", socket.getInputStream(), socket.getOutputStream()) {
|
||||||
|
@Override
|
||||||
|
protected boolean onRun()
|
||||||
|
{
|
||||||
|
if (!socket.isConnected() || socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown())
|
||||||
|
{
|
||||||
|
logger.debug("Socket closed, shutting down");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
logger.debug("Connection received");
|
logger.debug("Connection received");
|
||||||
interfaceLink = new MessageHandler("InterfaceHandler", socket.getInputStream(), socket.getOutputStream());
|
|
||||||
/*
|
/*
|
||||||
* Pretty much just forwards any messages to the browser and
|
* Pretty much just forwards any messages to the browser and
|
||||||
* returns their value.
|
* returns their value.
|
||||||
@@ -64,6 +73,7 @@ public class Main
|
|||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
logger.error("Could not initialize socket", e);
|
logger.error("Could not initialize socket", e);
|
||||||
|
browserLink.sendObject("quit");
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,29 +85,6 @@ public class Main
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
logger.debug("Shutting down interface link");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package edu.regis.universeplayer.addon;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
public class StackTraceElementSerializer implements JsonSerializer<StackTraceElement>, JsonDeserializer<StackTraceElement>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(StackTraceElement src, Type typeOfSrc, JsonSerializationContext context)
|
||||||
|
{
|
||||||
|
JsonObject ob = new JsonObject();
|
||||||
|
ob.addProperty("loader", src.getClassLoaderName());
|
||||||
|
ob.addProperty("class", src.getClassName());
|
||||||
|
ob.addProperty("file", src.getFileName());
|
||||||
|
ob.addProperty("line", src.getLineNumber());
|
||||||
|
ob.addProperty("method", src.getMethodName());
|
||||||
|
ob.addProperty("module", src.getModuleName());
|
||||||
|
ob.addProperty("version", src.getModuleVersion());
|
||||||
|
return ob;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StackTraceElement deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
|
||||||
|
{
|
||||||
|
JsonObject ob = json.getAsJsonObject();
|
||||||
|
return new StackTraceElement(ob.get("loader").getAsString(), ob.get("module").getAsString(), ob.get("version").getAsString(), ob.get("class").getAsString(), ob.get("method").getAsString(), ob.get("file").getAsString(), ob.get("line").getAsInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package edu.regis.universeplayer.addon;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
public class ThrowableSerializer implements JsonSerializer<Throwable>, JsonDeserializer<Throwable>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Throwable src, Type typeOfSrc, JsonSerializationContext context)
|
||||||
|
{
|
||||||
|
JsonObject ob = new JsonObject();
|
||||||
|
ob.addProperty("message", src.getMessage());
|
||||||
|
ob.add("cause", context.serialize(src.getCause()));
|
||||||
|
JsonArray stack = new JsonArray();
|
||||||
|
for (StackTraceElement trace: src.getStackTrace())
|
||||||
|
{
|
||||||
|
stack.add(context.serialize(trace));
|
||||||
|
}
|
||||||
|
ob.add("trace", stack);
|
||||||
|
JsonArray suppressed = new JsonArray();
|
||||||
|
for (Throwable throwable: src.getSuppressed())
|
||||||
|
{
|
||||||
|
suppressed.add(context.serialize(throwable));
|
||||||
|
}
|
||||||
|
ob.add("suppressed", suppressed);
|
||||||
|
return ob;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
|
||||||
|
{
|
||||||
|
JsonObject ob = json.getAsJsonObject();
|
||||||
|
Throwable throwable = new Throwable(ob.get("message").getAsString(), context.deserialize(ob.get("cause"), typeOfT));
|
||||||
|
throwable.initCause(context.deserialize(ob.get("cause"), Throwable.class));
|
||||||
|
JsonArray traceJson = ob.getAsJsonArray("trace");
|
||||||
|
StackTraceElement[] trace = new StackTraceElement[traceJson.size()];
|
||||||
|
for (int i = 0; i < trace.length; i++)
|
||||||
|
{
|
||||||
|
trace[i] = context.deserialize(traceJson.get(i), StackTraceElement.class);
|
||||||
|
}
|
||||||
|
throwable.setStackTrace(trace);
|
||||||
|
JsonArray suppressed = ob.getAsJsonArray("suppressed");
|
||||||
|
for (JsonElement el: suppressed)
|
||||||
|
{
|
||||||
|
throwable.addSuppressed(context.deserialize(el, Throwable.class));
|
||||||
|
}
|
||||||
|
return throwable;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,5 +18,9 @@
|
|||||||
<AppenderRef ref="File"/>
|
<AppenderRef ref="File"/>
|
||||||
</Root>
|
</Root>
|
||||||
<Logger name="edu.regis.universeplayer.browserCommands.MessageRunner" level="trace"/>
|
<Logger name="edu.regis.universeplayer.browserCommands.MessageRunner" level="trace"/>
|
||||||
|
<Logger name="edu.regis.universeplayer.browserCommands.MessageHandler" level="trace"/>
|
||||||
|
<Logger name="edu.regis.universeplayer.browserCommands.memoryMapping.MemoryRunner" level="trace"/>
|
||||||
|
<Logger name="edu.regis.universeplayer.browserCommands.memoryMapping.MemoryHandler" level="trace"/>
|
||||||
|
<Logger name="edu.regis.universeplayer.addon.BrowserLink" level="trace"/>
|
||||||
</Loggers>
|
</Loggers>
|
||||||
</Configuration>
|
</Configuration>
|
||||||
@@ -9,6 +9,9 @@ plugins {
|
|||||||
// In this section you declare where to find the dependencies of your project
|
// In this section you declare where to find the dependencies of your project
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
flatDir {
|
||||||
|
dirs new File(rootDir, 'libs')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In this section you declare the dependencies for your production and test code
|
// In this section you declare the dependencies for your production and test code
|
||||||
@@ -19,6 +22,8 @@ dependencies {
|
|||||||
implementation 'org.apache.logging.log4j:log4j-core:2.13.3'
|
implementation 'org.apache.logging.log4j:log4j-core:2.13.3'
|
||||||
implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.13.3'
|
implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.13.3'
|
||||||
|
|
||||||
|
implementation 'net.harawata:appdirs:1.2.1'
|
||||||
|
|
||||||
// Declare the dependency for your favourite test framework you want to use in your tests.
|
// Declare the dependency for your favourite test framework you want to use in your tests.
|
||||||
// TestNG is also supported by the Gradle Test task. Just change the
|
// TestNG is also supported by the Gradle Test task. Just change the
|
||||||
// testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
|
// testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
|
||||||
|
|||||||
@@ -4,14 +4,51 @@
|
|||||||
|
|
||||||
package edu.regis.universeplayer.browserCommands;
|
package edu.regis.universeplayer.browserCommands;
|
||||||
|
|
||||||
|
import net.harawata.appdirs.AppDirsFactory;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class BrowserConstants
|
public class BrowserConstants
|
||||||
{
|
{
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BrowserConstants.class);
|
||||||
/**
|
/**
|
||||||
* The IP that the connection will be hosted on.
|
* The IP that the connection will be hosted on.
|
||||||
*/
|
*/
|
||||||
public static final String IP = "127.0.0.1";
|
public static final String IP = "localhost";
|
||||||
/**
|
/**
|
||||||
* The port both processes use for connection.
|
* The port both processes use for connection.
|
||||||
*/
|
*/
|
||||||
public static final int PORT = 3000;
|
public static final int PORT = 3000;
|
||||||
|
|
||||||
|
private static File commDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the directory for memory mapped files
|
||||||
|
*
|
||||||
|
* @return The communications storage directory.
|
||||||
|
*/
|
||||||
|
public static File getCommDir()
|
||||||
|
{
|
||||||
|
if (commDir == null)
|
||||||
|
{
|
||||||
|
commDir = new File(AppDirsFactory.getInstance().getSharedDir("universalmusic", null, null), "comm");
|
||||||
|
if (!commDir.getParentFile().exists())
|
||||||
|
{
|
||||||
|
if (!commDir.getParentFile().mkdir())
|
||||||
|
{
|
||||||
|
logger.error("Could not create shared directory {}", commDir.getParent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!commDir.exists())
|
||||||
|
{
|
||||||
|
if (!commDir.mkdir())
|
||||||
|
{
|
||||||
|
logger.error("Could not create data directory {}", commDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commDir;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package edu.regis.universeplayer.browserCommands;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The quit command tells the browser to shut down.
|
||||||
|
*
|
||||||
|
* @author William Hubbard
|
||||||
|
* @version 0.1
|
||||||
|
*/
|
||||||
|
public class CommandQuit implements BrowserCommand
|
||||||
|
{
|
||||||
|
public CommandQuit()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandName()
|
||||||
|
{
|
||||||
|
return "quit";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,20 +19,20 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Dispatches messages sent from a corresponding {@link MessageRunner}.
|
||||||
*/
|
*/
|
||||||
public class MessageHandler implements Runnable, MessageSerializer
|
public class MessageHandler implements Runnable, MessageSerializer
|
||||||
{
|
{
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
|
||||||
private final String name;
|
public final String name;
|
||||||
|
|
||||||
private final InputStream input;
|
private final InputStream input;
|
||||||
private final OutputStream output;
|
private final OutputStream output;
|
||||||
|
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
private final LinkedList<MessageListener> listeners = new LinkedList<>();
|
private final LinkedList<MessageListener> listeners = new LinkedList<>();
|
||||||
private final HashMap<Integer, Future<Object>> messageResponses = new HashMap<>();
|
protected final HashMap<Integer, Future<Object>> messageResponses = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a message handler.
|
* Creates a message handler.
|
||||||
@@ -56,14 +56,22 @@ public class MessageHandler implements Runnable, MessageSerializer
|
|||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called at the beginning of every loop to do extra processing and check to see if we can still run.
|
||||||
|
*
|
||||||
|
* @return True if we should close the runner, false otherwise.
|
||||||
|
*/
|
||||||
|
protected boolean onRun()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
BufferedInputStream browserIn = null;
|
BufferedInputStream browserIn = null;
|
||||||
BufferedOutputStream browserOut = null;
|
BufferedOutputStream browserOut = null;
|
||||||
|
|
||||||
boolean running = true;
|
|
||||||
|
|
||||||
byte[][] message;
|
byte[][] message;
|
||||||
byte[] messageByte;
|
byte[] messageByte;
|
||||||
Object messageOb;
|
Object messageOb;
|
||||||
@@ -76,7 +84,7 @@ public class MessageHandler implements Runnable, MessageSerializer
|
|||||||
browserIn = new BufferedInputStream(this.input);
|
browserIn = new BufferedInputStream(this.input);
|
||||||
browserOut = new BufferedOutputStream(this.output);
|
browserOut = new BufferedOutputStream(this.output);
|
||||||
|
|
||||||
while (running)
|
while (!this.onRun())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -86,7 +94,7 @@ public class MessageHandler implements Runnable, MessageSerializer
|
|||||||
if (message == null)
|
if (message == null)
|
||||||
{
|
{
|
||||||
logger.info("Connection closed.");
|
logger.info("Connection closed.");
|
||||||
running = false;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,20 +24,20 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
{
|
{
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
|
||||||
private final String name;
|
public final String name;
|
||||||
private final InputStream input;
|
private final InputStream input;
|
||||||
private final OutputStream output;
|
private final OutputStream output;
|
||||||
private final Object readLock = new Object();
|
protected final Object readLock = new Object();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This queue serves as a cache for objects we need to send.
|
* This queue serves as a cache for objects we need to send.
|
||||||
*/
|
*/
|
||||||
private final LinkedList<MessagePacket> sendQueue = new LinkedList<>();
|
protected final LinkedList<MessagePacket> sendQueue = new LinkedList<>();
|
||||||
/**
|
/**
|
||||||
* This queue serves as a cache for objects that are waiting for a response.
|
* This queue serves as a cache for objects that are waiting for a response.
|
||||||
*/
|
*/
|
||||||
private final HashMap<Integer, MessagePacket> sentQueue = new HashMap<>();
|
protected final HashMap<Integer, MessagePacket> sentQueue = new HashMap<>();
|
||||||
private int messagesSent = 0;
|
protected int messagesSent = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a message runner.
|
* Creates a message runner.
|
||||||
@@ -60,6 +60,16 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called at the beginning of every loop to do extra processing and check to see if we can still run.
|
||||||
|
*
|
||||||
|
* @return True if we should close the runner, false otherwise.
|
||||||
|
*/
|
||||||
|
protected boolean onRun()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
@@ -67,8 +77,6 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
BufferedOutputStream browserOut = null;
|
BufferedOutputStream browserOut = null;
|
||||||
MessagePacket packet;
|
MessagePacket packet;
|
||||||
|
|
||||||
boolean running = true;
|
|
||||||
|
|
||||||
byte[][] returnMessage;
|
byte[][] returnMessage;
|
||||||
ByteBuffer numBuffer = ByteBuffer.allocate(4);
|
ByteBuffer numBuffer = ByteBuffer.allocate(4);
|
||||||
|
|
||||||
@@ -77,7 +85,7 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
browserIn = new BufferedInputStream(this.input);
|
browserIn = new BufferedInputStream(this.input);
|
||||||
browserOut = new BufferedOutputStream(this.output);
|
browserOut = new BufferedOutputStream(this.output);
|
||||||
|
|
||||||
while (running)
|
while (!this.onRun())
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Sends a messages
|
* Sends a messages
|
||||||
@@ -121,7 +129,7 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
if (returnMessage == null)
|
if (returnMessage == null)
|
||||||
{
|
{
|
||||||
logger.info("Connection closed.");
|
logger.info("Connection closed.");
|
||||||
running = false;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -138,7 +146,7 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
{
|
{
|
||||||
packet.returnMessage = returnMessage[1];
|
packet.returnMessage = returnMessage[1];
|
||||||
numBuffer.clear();
|
numBuffer.clear();
|
||||||
logger.debug("Reading message {} {}", numBuffer.getInt(), packet.returnMessage);
|
logger.debug("Reading message {} {}", numBuffer.getInt(), new String(packet.returnMessage, StandardCharsets.UTF_8));
|
||||||
synchronized (this.readLock)
|
synchronized (this.readLock)
|
||||||
{
|
{
|
||||||
logger.trace("Received message {}, notifying futures.", packet.returnValue.index);
|
logger.trace("Received message {}, notifying futures.", packet.returnValue.index);
|
||||||
@@ -254,9 +262,9 @@ public abstract class MessageRunner implements Runnable, MessageSerializer
|
|||||||
*/
|
*/
|
||||||
public class MessageFuture implements Future<Object>
|
public class MessageFuture implements Future<Object>
|
||||||
{
|
{
|
||||||
private final MessagePacket packetEntry;
|
protected final MessagePacket packetEntry;
|
||||||
private int index;
|
public int index;
|
||||||
private boolean canceled = false;
|
protected boolean canceled = false;
|
||||||
|
|
||||||
private MessageFuture(MessagePacket packet)
|
private MessageFuture(MessagePacket packet)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 QueryStatus implements BrowserQuery<Integer>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public String getCommandName()
|
||||||
|
{
|
||||||
|
return "getStatus";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Integer> getReturnType()
|
||||||
|
{
|
||||||
|
return Integer.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,9 @@ plugins {
|
|||||||
// In this section you declare where to find the dependencies of your project
|
// In this section you declare where to find the dependencies of your project
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
flatDir {
|
||||||
|
dirs new File(rootDir, 'libs')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In this section you declare the dependencies for your production and test code
|
// In this section you declare the dependencies for your production and test code
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ import edu.regis.universeplayer.data.Song;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
@@ -30,23 +31,19 @@ import java.util.concurrent.*;
|
|||||||
public class Browser extends MessageRunner implements Player<InternetSong>
|
public class Browser extends MessageRunner implements Player<InternetSong>
|
||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory.getLogger(Browser.class);
|
private static final Logger logger = LoggerFactory.getLogger(Browser.class);
|
||||||
private final Socket socket;
|
|
||||||
private final Process process;
|
private final Process process;
|
||||||
|
private final Socket socket;
|
||||||
|
|
||||||
|
private final LinkedList<PlaybackListener> listeners = new LinkedList<>();
|
||||||
|
|
||||||
|
private InternetSong currentSong;
|
||||||
|
|
||||||
public static Browser createBrowser() throws IOException, InterruptedException
|
public static Browser createBrowser() throws IOException, InterruptedException
|
||||||
{
|
{
|
||||||
/*
|
ServerSocket server = new ServerSocket(BrowserConstants.PORT, 50, InetAddress.getByName(null));
|
||||||
* The maximum number of attempts that will be made to establish a
|
logger.debug("Server started.");
|
||||||
* connection.
|
|
||||||
*/
|
|
||||||
final int MAX_ATTEMPTS = 20;
|
|
||||||
/*
|
|
||||||
* How long the thread will sleep between connection attempts, in
|
|
||||||
* milliseconds.
|
|
||||||
*/
|
|
||||||
final long SLEEP_TIME = 500;
|
|
||||||
int startExit;
|
int startExit;
|
||||||
Socket socket = null;
|
|
||||||
Browser browser;
|
Browser browser;
|
||||||
Process browserProcess = launchBrowser();
|
Process browserProcess = launchBrowser();
|
||||||
/*
|
/*
|
||||||
@@ -69,24 +66,31 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
|
|
||||||
ConnectException connErr = null;
|
ConnectException connErr = null;
|
||||||
logger.debug("Attempting connection");
|
logger.debug("Attempting connection");
|
||||||
for (int attempts = 0; socket == null && attempts < MAX_ATTEMPTS; attempts++)
|
Socket socket = server.accept();
|
||||||
|
if (!socket.isBound())
|
||||||
{
|
{
|
||||||
try
|
logger.error("Socket not bound");
|
||||||
|
}
|
||||||
|
else if (!socket.isConnected())
|
||||||
{
|
{
|
||||||
socket = new Socket(BrowserConstants.IP, BrowserConstants.PORT);
|
logger.error("Socket not connected");
|
||||||
}
|
}
|
||||||
catch (ConnectException e)
|
else if (socket.isClosed())
|
||||||
{
|
{
|
||||||
connErr = e;
|
logger.error("Socket prematurely closed");
|
||||||
logger.debug("Connection attempt {} failed, trying again", attempts);
|
|
||||||
Thread.sleep(SLEEP_TIME);
|
|
||||||
}
|
}
|
||||||
}
|
else if (socket.isInputShutdown())
|
||||||
if (socket == null)
|
|
||||||
{
|
{
|
||||||
throw connErr;
|
logger.error("Socket input prematurely closed.");
|
||||||
|
}
|
||||||
|
else if (socket.isOutputShutdown())
|
||||||
|
{
|
||||||
|
logger.error("Socket input prematurely closed.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.debug("Connection established.");
|
||||||
}
|
}
|
||||||
logger.debug("Browser connection established.");
|
|
||||||
browser = new Browser(socket, browserProcess);
|
browser = new Browser(socket, browserProcess);
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
@@ -98,6 +102,17 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
this.process = process;
|
this.process = process;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onRun()
|
||||||
|
{
|
||||||
|
if (!socket.isConnected() || socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown())
|
||||||
|
{
|
||||||
|
logger.debug("Socket closed, shutting down");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method for launching a browser instance
|
* Utility method for launching a browser instance
|
||||||
*
|
*
|
||||||
@@ -155,7 +170,7 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
@Override
|
@Override
|
||||||
public Song getCurrentSong()
|
public Song getCurrentSong()
|
||||||
{
|
{
|
||||||
return null;
|
return this.currentSong;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -178,23 +193,28 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> close()
|
public QueryFuture<Void> close()
|
||||||
{
|
{
|
||||||
if (process != null)
|
|
||||||
{
|
|
||||||
logger.info("Destroying browser processes {}, {}", process, process.descendants().toArray(ProcessHandle[]::new));
|
|
||||||
process.descendants().forEach(ProcessHandle::destroy);
|
|
||||||
process.destroy();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.info("Process already destroyed.");
|
|
||||||
}
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.socket.close();
|
QueryFuture<Void> future = new ForwardedFuture(this.sendObject(new CommandQuit()));
|
||||||
|
return future;
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
logger.error("Could not close browser socket", e);
|
logger.error("Could not deliver quit command", e);
|
||||||
|
logger.error("Destroying browser processes {}, {}", process, process.descendants().toArray(ProcessHandle[]::new));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
logger.error("Could not close socket", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
process.descendants().forEach(ProcessHandle::destroy);
|
||||||
|
process.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -236,20 +256,44 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> play()
|
public QueryFuture<Void> play()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new ForwardedFuture(this.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY)));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not send message", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> pause()
|
public QueryFuture<Void> pause()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new ForwardedFuture(this.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE)));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not send message", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> togglePlayback()
|
public QueryFuture<Void> togglePlayback()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new ForwardedFuture(this.sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE)));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not send message", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops playback of the current song.
|
* Stops playback of the current song.
|
||||||
@@ -263,8 +307,16 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> seek(float time)
|
public QueryFuture<Void> seek(float time)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new ForwardedFuture(this.sendObject(new CommandSeek(time)));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not send message", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the player's current playback status.
|
* Obtains the player's current playback status.
|
||||||
@@ -274,8 +326,72 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
@Override
|
@Override
|
||||||
public QueryFuture<PlaybackStatus> getStatus()
|
public QueryFuture<PlaybackStatus> getStatus()
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Future future = this.sendObject(new QueryStatus());
|
||||||
|
return new QueryFuture<>() {
|
||||||
|
|
||||||
|
private CommandReturn<Double> getVal() throws ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
return ((CommandReturn<Double>) future.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandReturn<Double> getVal(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException
|
||||||
|
{
|
||||||
|
return ((CommandReturn<Double>) 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 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
|
||||||
|
{
|
||||||
|
Double value = getVal().getReturnValue();
|
||||||
|
return PlaybackStatus.values()[value.intValue()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlaybackStatus get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||||
|
{
|
||||||
|
Double value = getVal(timeout, unit).getReturnValue();
|
||||||
|
return PlaybackStatus.values()[value.intValue()];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not send message", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Float> getCurrentTime()
|
public QueryFuture<Float> getCurrentTime()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package edu.regis.universeplayer.localPlayer;
|
package edu.regis.universeplayer.localPlayer;
|
||||||
|
|
||||||
import com.intervigil.wave.WaveReader;
|
import com.intervigil.wave.WaveReader;
|
||||||
|
|
||||||
import edu.regis.universeplayer.PlaybackListener;
|
import edu.regis.universeplayer.PlaybackListener;
|
||||||
import edu.regis.universeplayer.PlaybackStatus;
|
import edu.regis.universeplayer.PlaybackStatus;
|
||||||
import edu.regis.universeplayer.Player;
|
import edu.regis.universeplayer.Player;
|
||||||
@@ -12,6 +11,8 @@ import edu.regis.universeplayer.browserCommands.CommandConfirmation;
|
|||||||
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
||||||
import edu.regis.universeplayer.data.LocalSong;
|
import edu.regis.universeplayer.data.LocalSong;
|
||||||
import edu.regis.universeplayer.data.Song;
|
import edu.regis.universeplayer.data.Song;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import uk.co.caprica.vlcj.factory.MediaPlayerFactory;
|
import uk.co.caprica.vlcj.factory.MediaPlayerFactory;
|
||||||
import uk.co.caprica.vlcj.media.MediaRef;
|
import uk.co.caprica.vlcj.media.MediaRef;
|
||||||
import uk.co.caprica.vlcj.media.TrackType;
|
import uk.co.caprica.vlcj.media.TrackType;
|
||||||
@@ -19,17 +20,12 @@ import uk.co.caprica.vlcj.player.base.MediaPlayer;
|
|||||||
import uk.co.caprica.vlcj.player.base.MediaPlayerEventListener;
|
import uk.co.caprica.vlcj.player.base.MediaPlayerEventListener;
|
||||||
import uk.co.caprica.vlcj.player.base.StatusApi;
|
import uk.co.caprica.vlcj.player.base.StatusApi;
|
||||||
import uk.co.caprica.vlcj.player.component.AudioPlayerComponent;
|
import uk.co.caprica.vlcj.player.component.AudioPlayerComponent;
|
||||||
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package edu.regis.universeplayer.player;
|
|||||||
|
|
||||||
import edu.regis.universeplayer.Player;
|
import edu.regis.universeplayer.Player;
|
||||||
import edu.regis.universeplayer.browser.Browser;
|
import edu.regis.universeplayer.browser.Browser;
|
||||||
|
import edu.regis.universeplayer.browser.InternetSong;
|
||||||
import edu.regis.universeplayer.data.Queue;
|
import edu.regis.universeplayer.data.Queue;
|
||||||
import edu.regis.universeplayer.data.*;
|
import edu.regis.universeplayer.data.*;
|
||||||
import edu.regis.universeplayer.localPlayer.LocalPlayer;
|
import edu.regis.universeplayer.localPlayer.LocalPlayer;
|
||||||
@@ -36,6 +37,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(Interface.class);
|
private static final Logger logger = LoggerFactory.getLogger(Interface.class);
|
||||||
|
|
||||||
private static File dataDir;
|
private static File dataDir;
|
||||||
|
private static File commDir;
|
||||||
private static File configDir;
|
private static File configDir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,29 +96,32 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
inter.players.add(browser = Browser.createBrowser());
|
inter.players.add(browser = Browser.createBrowser());
|
||||||
browserThread = new Thread(browser);
|
browserThread = new Thread(browser);
|
||||||
browserThread.start();
|
browserThread.start();
|
||||||
|
logger.debug("Sending ping");
|
||||||
|
browser.sendObject("ping");
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(browser::close));
|
Runtime.getRuntime().addShutdownHook(new Thread(browser::close));
|
||||||
|
Player.REGISTERED_PLAYERS.put(InternetSong.class, browser);
|
||||||
|
|
||||||
LinkedList<Future<Object>> pingRequests = new LinkedList<>();
|
// LinkedList<Future<Object>> pingRequests = new LinkedList<>();
|
||||||
for (int i = 0; i < 20; i++)
|
// for (int i = 0; i < 20; i++)
|
||||||
{
|
// {
|
||||||
logger.info("Sending ping");
|
// logger.info("Sending ping");
|
||||||
pingRequests.add(browser.sendObject("ping"));
|
// pingRequests.add(browser.sendObject("ping"));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
LinkedList<Future<Object>> toRemove = new LinkedList<>();
|
// LinkedList<Future<Object>> toRemove = new LinkedList<>();
|
||||||
while (pingRequests.size() > 0)
|
// while (pingRequests.size() > 0)
|
||||||
{
|
// {
|
||||||
for (Future<Object> future : pingRequests)
|
// for (Future<Object> future : pingRequests)
|
||||||
{
|
// {
|
||||||
if (future.isDone())
|
// if (future.isDone())
|
||||||
{
|
// {
|
||||||
logger.info("Receiving {}", future.get());
|
// logger.info("Receiving {}", future.get());
|
||||||
toRemove.add(future);
|
// toRemove.add(future);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
pingRequests.removeAll(toRemove);
|
// pingRequests.removeAll(toRemove);
|
||||||
toRemove.clear();
|
// toRemove.clear();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
@@ -173,6 +178,33 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
return configDir;
|
return configDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the directory for memory mapped files
|
||||||
|
* @return The communications storage directory.
|
||||||
|
*/
|
||||||
|
public static File getCommDir()
|
||||||
|
{
|
||||||
|
if (commDir == null)
|
||||||
|
{
|
||||||
|
commDir = new File(AppDirsFactory.getInstance().getSharedDir("universalmusic", null, null), "comm");
|
||||||
|
if (!commDir.getParentFile().exists())
|
||||||
|
{
|
||||||
|
if (!commDir.getParentFile().mkdir())
|
||||||
|
{
|
||||||
|
logger.error("Could not create shared directory {}", commDir.getParent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!commDir.exists())
|
||||||
|
{
|
||||||
|
if (!commDir.mkdir())
|
||||||
|
{
|
||||||
|
logger.error("Could not create data directory {}", commDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commDir;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an interface
|
* Creates an interface
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import edu.regis.universeplayer.data.Song;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.FocusAdapter;
|
import java.awt.event.FocusAdapter;
|
||||||
import java.awt.event.FocusEvent;
|
import java.awt.event.FocusEvent;
|
||||||
@@ -21,8 +22,6 @@ import java.util.LinkedList;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This panel contains the buttons necessary for controlling the playback of audio.
|
* This panel contains the buttons necessary for controlling the playback of audio.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
<Logger name="edu.regis.universeplayer.data.LocalSongProvider" level="debug">
|
<Logger name="edu.regis.universeplayer.data.LocalSongProvider" level="debug">
|
||||||
<AppenderRef ref="Console"/>
|
<AppenderRef ref="Console"/>
|
||||||
</Logger>
|
</Logger>
|
||||||
|
<Logger name="edu.regis.universeplayer.browserCommands.memoryMapping.MemoryRunner" level="trace"/>
|
||||||
|
<Logger name="edu.regis.universeplayer.browserCommands.memoryMapping.MemoryHandler" level="trace"/>
|
||||||
<!-- <Logger name="edu.regis.universeplayer.localPlayer.LocalPlayer" level="debug">-->
|
<!-- <Logger name="edu.regis.universeplayer.localPlayer.LocalPlayer" level="debug">-->
|
||||||
<!-- <AppenderRef ref="Console"/>-->
|
<!-- <AppenderRef ref="Console"/>-->
|
||||||
<!-- </Logger>-->
|
<!-- </Logger>-->
|
||||||
|
|||||||
Reference in New Issue
Block a user